|
11 | 11 | import sys |
12 | 12 | import sysconfig |
13 | 13 | import traceback |
| 14 | +import urllib.parse |
14 | 15 | from typing import Any, Optional, Sequence |
15 | 16 |
|
16 | 17 |
|
@@ -47,9 +48,25 @@ def update_sys_path(path_to_add: str, strategy: str) -> None: |
47 | 48 | RUNNER = pathlib.Path(__file__).parent / "lsp_runner.py" |
48 | 49 |
|
49 | 50 | MAX_WORKERS = 5 |
| 51 | +NOTEBOOK_SYNC_OPTIONS = lsp.NotebookDocumentSyncOptions( |
| 52 | + notebook_selector=[ |
| 53 | + lsp.NotebookDocumentFilterWithNotebook( |
| 54 | + notebook="jupyter-notebook", |
| 55 | + cells=[lsp.NotebookCellLanguage(language="python")], |
| 56 | + ), |
| 57 | + lsp.NotebookDocumentFilterWithNotebook( |
| 58 | + notebook="interactive", |
| 59 | + cells=[lsp.NotebookCellLanguage(language="python")], |
| 60 | + ), |
| 61 | + ], |
| 62 | + save=True, |
| 63 | +) |
50 | 64 | # TODO: Update the language server name and version. |
51 | | -LSP_SERVER = LanguageServer( |
52 | | - name="<pytool-display-name>", version="<server version>", max_workers=MAX_WORKERS |
| 65 | +LSP_SERVER = server.LanguageServer( |
| 66 | + name="<pytool-display-name>", |
| 67 | + version="<server version>", |
| 68 | + max_workers=MAX_WORKERS, |
| 69 | + notebook_document_sync=NOTEBOOK_SYNC_OPTIONS, |
53 | 70 | ) |
54 | 71 |
|
55 | 72 |
|
@@ -119,7 +136,113 @@ def did_close(params: lsp.DidCloseTextDocumentParams) -> None: |
119 | 136 | ) |
120 | 137 |
|
121 | 138 |
|
122 | | -def _linting_helper(document: workspace.TextDocument) -> list[lsp.Diagnostic]: |
| 139 | +@LSP_SERVER.feature(lsp.NOTEBOOK_DOCUMENT_DID_OPEN) |
| 140 | +def notebook_did_open(params: lsp.DidOpenNotebookDocumentParams) -> None: |
| 141 | + """LSP handler for notebookDocument/didOpen request.""" |
| 142 | + nb = LSP_SERVER.workspace.get_notebook_document( |
| 143 | + notebook_uri=params.notebook_document.uri |
| 144 | + ) |
| 145 | + if nb is None: |
| 146 | + return |
| 147 | + for cell in nb.cells: |
| 148 | + if cell.kind != lsp.NotebookCellKind.Code or cell.document is None: |
| 149 | + continue |
| 150 | + document = LSP_SERVER.workspace.get_text_document(cell.document) |
| 151 | + diagnostics: list[lsp.Diagnostic] = _linting_helper(document) |
| 152 | + LSP_SERVER.text_document_publish_diagnostics( |
| 153 | + lsp.PublishDiagnosticsParams(uri=document.uri, diagnostics=diagnostics) |
| 154 | + ) |
| 155 | + |
| 156 | + |
| 157 | +@LSP_SERVER.feature(lsp.NOTEBOOK_DOCUMENT_DID_CHANGE) |
| 158 | +def notebook_did_change(params: lsp.DidChangeNotebookDocumentParams) -> None: |
| 159 | + """LSP handler for notebookDocument/didChange request.""" |
| 160 | + nb = LSP_SERVER.workspace.get_notebook_document( |
| 161 | + notebook_uri=params.notebook_document.uri |
| 162 | + ) |
| 163 | + if nb is None: |
| 164 | + return |
| 165 | + |
| 166 | + change = params.change |
| 167 | + # Re-lint cells whose text content changed. |
| 168 | + if change.cells and change.cells.text_content: |
| 169 | + for text_change in change.cells.text_content: |
| 170 | + document = LSP_SERVER.workspace.get_text_document( |
| 171 | + text_change.document.uri |
| 172 | + ) |
| 173 | + diagnostics: list[lsp.Diagnostic] = _linting_helper(document) |
| 174 | + LSP_SERVER.text_document_publish_diagnostics( |
| 175 | + lsp.PublishDiagnosticsParams(uri=document.uri, diagnostics=diagnostics) |
| 176 | + ) |
| 177 | + |
| 178 | + # Lint newly added cells (code cells only). |
| 179 | + if change.cells and change.cells.structure and change.cells.structure.did_open: |
| 180 | + code_cell_uris = { |
| 181 | + cell.document |
| 182 | + for cell in nb.cells |
| 183 | + if cell.kind == lsp.NotebookCellKind.Code and cell.document is not None |
| 184 | + } |
| 185 | + for cell_doc in change.cells.structure.did_open: |
| 186 | + if cell_doc.uri not in code_cell_uris: |
| 187 | + continue |
| 188 | + document = LSP_SERVER.workspace.get_text_document(cell_doc.uri) |
| 189 | + diagnostics = _linting_helper(document) |
| 190 | + LSP_SERVER.text_document_publish_diagnostics( |
| 191 | + lsp.PublishDiagnosticsParams(uri=document.uri, diagnostics=diagnostics) |
| 192 | + ) |
| 193 | + |
| 194 | + # Clear diagnostics for removed cells. |
| 195 | + if change.cells and change.cells.structure and change.cells.structure.did_close: |
| 196 | + for cell_doc in change.cells.structure.did_close: |
| 197 | + LSP_SERVER.text_document_publish_diagnostics( |
| 198 | + lsp.PublishDiagnosticsParams(uri=cell_doc.uri, diagnostics=[]) |
| 199 | + ) |
| 200 | + |
| 201 | + |
| 202 | +@LSP_SERVER.feature(lsp.NOTEBOOK_DOCUMENT_DID_SAVE) |
| 203 | +def notebook_did_save(params: lsp.DidSaveNotebookDocumentParams) -> None: |
| 204 | + """LSP handler for notebookDocument/didSave request.""" |
| 205 | + nb = LSP_SERVER.workspace.get_notebook_document( |
| 206 | + notebook_uri=params.notebook_document.uri |
| 207 | + ) |
| 208 | + if nb is None: |
| 209 | + return |
| 210 | + for cell in nb.cells: |
| 211 | + if cell.kind != lsp.NotebookCellKind.Code or cell.document is None: |
| 212 | + continue |
| 213 | + document = LSP_SERVER.workspace.get_text_document(cell.document) |
| 214 | + diagnostics: list[lsp.Diagnostic] = _linting_helper(document) |
| 215 | + LSP_SERVER.text_document_publish_diagnostics( |
| 216 | + lsp.PublishDiagnosticsParams(uri=document.uri, diagnostics=diagnostics) |
| 217 | + ) |
| 218 | + |
| 219 | + |
| 220 | +@LSP_SERVER.feature(lsp.NOTEBOOK_DOCUMENT_DID_CLOSE) |
| 221 | +def notebook_did_close(params: lsp.DidCloseNotebookDocumentParams) -> None: |
| 222 | + """LSP handler for notebookDocument/didClose request.""" |
| 223 | + for cell_doc in params.cell_text_documents: |
| 224 | + LSP_SERVER.text_document_publish_diagnostics( |
| 225 | + lsp.PublishDiagnosticsParams(uri=cell_doc.uri, diagnostics=[]) |
| 226 | + ) |
| 227 | + |
| 228 | + |
| 229 | +def _get_document_path(document: workspace.Document) -> str: |
| 230 | + """Returns the file path for a document, handling notebook cell URIs. |
| 231 | +
|
| 232 | + Examples: |
| 233 | + file:///path/to/file.py -> /path/to/file.py |
| 234 | + vscode-notebook-cell:/path/to/notebook.ipynb#C00001 -> /path/to/notebook.ipynb |
| 235 | + """ |
| 236 | + parsed = urllib.parse.urlparse(document.uri) |
| 237 | + if parsed.scheme == "vscode-notebook-cell": |
| 238 | + file_uri = urllib.parse.urlunparse( |
| 239 | + ("file", parsed.netloc, parsed.path, parsed.params, parsed.query, "") |
| 240 | + ) |
| 241 | + return uris.to_fs_path(file_uri) |
| 242 | + return uris.to_fs_path(document.uri) |
| 243 | + |
| 244 | + |
| 245 | +def _linting_helper(document: workspace.Document) -> list[lsp.Diagnostic]: |
123 | 246 | # TODO: Determine if your tool supports passing file content via stdin. |
124 | 247 | # If you want to support linting on change then your tool will need to |
125 | 248 | # support linting over stdin to be effective. Read, and update |
@@ -443,12 +566,11 @@ def _run_tool_on_document( |
443 | 566 | """ |
444 | 567 | if extra_args is None: |
445 | 568 | extra_args = [] |
446 | | - if str(document.uri).startswith("vscode-notebook-cell"): |
447 | | - # TODO: Decide on if you want to skip notebook cells. |
448 | | - # Skip notebook cells |
449 | | - return None |
| 569 | + # TODO: Notebook cells are now supported via the notebookDocument/ handlers. |
| 570 | + # If you want to customize notebook cell handling, update the notebook handlers above. |
450 | 571 |
|
451 | | - if utils.is_stdlib_file(document.path): |
| 572 | + document_path = _get_document_path(document) |
| 573 | + if utils.is_stdlib_file(document_path): |
452 | 574 | # TODO: Decide on if you want to skip standard library files. |
453 | 575 | # Skip standard library python files. |
454 | 576 | return None |
@@ -493,7 +615,7 @@ def _run_tool_on_document( |
493 | 615 | # set use_stdin to False, or provide path, what ever is appropriate for your tool. |
494 | 616 | argv += [] |
495 | 617 | else: |
496 | | - argv += [document.path] |
| 618 | + argv += [document_path] |
497 | 619 |
|
498 | 620 | if use_path: |
499 | 621 | # This mode is used when running executables. |
|
0 commit comments