microsoft / microsoft/vscode-mypy

Template Sync: Add Jupyter notebook cell support via LSP 3.17 Notebook Document Sync

Open
#474 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
150
Forks
39
Avg merge
21h 45m
Merged PRs (30d)
8

Description

### 🔄 Template Sync Required

Changes from the upstream [vscode-python-tools-extension-template](https://github.com/microsoft/vscode-python-tools-extension-template) have not yet been incorporated into this repository.

#### Source PR
- microsoft/vscode-python-tools-extension-template#265 — Add Jupyter notebook cell support via LSP 3.17 Notebook Document Sync

#### Summary
The template now ships with built-in support for linting Python cells inside Jupyter notebooks (`.ipynb`) and the VS Code Interactive Window via the LSP 3.17 Notebook Document Sync specification. This adds `NOTEBOOK_SYNC_OPTIONS` to the server initialization, a `_get_document_path` helper for resolving `vscode-notebook-cell:` URIs to the parent notebook's filesystem path, and four notebook lifecycle handlers (`notebookDocument/didOpen`, `/didChange`, `/didSave`, `/didClose`).

vscode-mypy currently **explicitly skips** notebook cells in `_linting_helper` with a "Not Supported" log warning. Adopting the template's pattern would enable mypy type checking in Jupyter notebook Python cells.

#### Files with missing changes

- **`bundled/tool/lsp_server.py`** — Missing the notebook infrastructure:
- `import urllib.parse` not present
- `NOTEBOOK_SYNC_OPTIONS` constant not present
- `LanguageServer` constructor does not include `notebook_document_sync`
- `_get_document_path` helper not present
- No `notebook_did_open`, `notebook_did_change`, `notebook_did_save`, or `notebook_did_close` handlers
- `_linting_helper` explicitly rejects `vscode-notebook-cell:` URIs (lines 213–216) rather than handling them

#### Suggested fix

Below are the recommended additions to `bundled/tool/lsp_server.py`. Mypy-specific logic (e.g., `reportingScope`, daemon handling) should be preserved inside each new handler, mirroring how `_linting_helper` works for regular text documents.

**1. Add the `urllib.parse` import** (near the top with other stdlib imports):

````diff
+import urllib.parse
````

**2. Add `NOTEBOOK_SYNC_OPTIONS`** (before or after `MAX_WORKERS`):

````diff
+import lsprotocol.types as lsp
+
+NOTEBOOK_SYNC_OPTIONS = lsp.NotebookDocumentSyncOptions(
+ notebook_selector=[
+ lsp.NotebookDocumentFilterWithNotebook(
+ notebook="jupyter-notebook",
+ cells=[lsp.NotebookCellLanguage(language="python")],
+ ),
+ lsp.NotebookDocumentFilterWithNotebook(
+ notebook="interactive",
+ cells=[lsp.NotebookCellLanguage(language="python")],
+ ),
+ ],
+ save=True,
+)
````

**3. Add `notebook_document_sync` to the `LanguageServer` constructor**:

````diff
-LSP_SERVER = LanguageServer(name="Mypy", version="v0.1.0", max_workers=MAX_WORKERS)
+LSP_SERVER = LanguageServer(
+ name="Mypy",
+ version="v0.1.0",
+ max_workers=MAX_WORKERS,
+ notebook_document_sync=NOTEBOOK_SYNC_OPTIONS,
+)
````

**4. Add the `_get_document_path` helper** (near other document helpers):

````diff
+def _get_document_path(cell_uri: str) -> str:
+ """Resolve a vscode-notebook-cell: URI to the parent notebook's filesystem path."""
+ parsed = urllib.parse.urlparse(cell_uri)
+ if parsed.scheme == "vscode-notebook-cell":
+ nb_uri = parsed._replace(scheme="file", fragment="").geturl()
+ return LSP_SERVER.workspace.get_text_document(nb_uri).path
+ return LSP_SERVER.workspace.get_text_document(cell_uri).path
````

**5. Add notebook lifecycle handlers** (alongside the existing `did_open`, `did_save`, `did_close` handlers):

````diff
+`@LSP_SERVER`.feature(lsp.NOTEBOOK_DOCUMENT_DID_OPEN)
+def notebook_did_open(params: lsp.DidOpenNotebookDocumentParams) -> None:
+ """LSP handler for notebookDocument/didOpen request."""
+ nb = LSP_SERVER.workspace.get_notebook_document(
+ notebook_uri=params.notebook_document.uri
+ )
+ if nb is None:
+ return
+ for cell in nb.cells:
+ if cell.kind == lsp.NotebookCellKind.Code:
+ doc = LSP_SERVER.workspace.get_text_document(cell.document)
+ _linting_helper(doc)
+
+
+`@LSP_SERVER`.feature(lsp.NOTEBOOK_DOCUMENT_DID_SAVE)
+def notebook_did_save(params: lsp.DidSaveNotebookDocumentParams) -> None:
+ """LSP handler for notebookDocument/didSave request."""
+ nb = LSP_SERVER.workspace.get_notebook_document(
+ notebook_uri=params.notebook_document.uri
+ )
+ if nb is None:
+ return
+ for cell in nb.cells:
+ if cell.kind == lsp.NotebookCellKind.Code:
+ doc = LSP_SERVER.workspace.get_text_document(cell.document)
+ _linting_helper(doc)
+
+
+`@LSP_SERVER`.feature(lsp.NOTEBOOK_DOCUMENT_DID_CLOSE)
+def notebook_did_close(params: lsp.DidCloseNotebookDocumentParams) -> None:
+ """LSP handler for notebookDocument/didClose request."""
+ for cell_doc in params.cell_text_documents:
+ doc = LSP_SERVER.workspace.get_text_document(cell_doc.uri)
+ _clear_diagnostics(doc)
+
+
+`@LSP_SERVER`.feature(lsp.NOTEBOOK_DOCUMENT_DID_CHANGE)
+def notebook_did_change(params: lsp.DidChangeNotebookDocumentParams) -> None:
+ """LSP handler for notebookDocument/didChange request."""
+ if params.change.cells is None:
+ return
+ cell_changes = params.change.cells
+ if cell_changes.text_content:
+ for changed in cell_changes.text_content:
+ doc = LSP_SERVER.workspace.get_text_document(changed.document.uri)
+ _linting_helper(doc)
+ if cell_changes.structure:
+ for removed in (cell_changes.structure.did_close or []):
+ doc = LSP_SERVER.workspace.get_text_document(removed.uri)
+ _clear_diagnostics(doc)
````

**6. Remove the "Not Supported" skip** from `_linting_helper` (lines 213–216):

````diff
- if str(document.uri).startswith("vscode-notebook-cell"):
- # We don't support running mypy on notebook cells.
- log_warning(f"Skipping notebook cells [Not Supported]: {str(document.uri)}")
- _clear_diagnostics(document)
- return None
-
````

> **Note:** The mypy tool runs against a file path. For notebook cells, `_get_document_path` resolves the cell URI to the parent notebook's `.ipynb` path. You may want to verify how mypy behaves when given a `.ipynb` file path and adjust the implementation accordingly.

#### Files skipped

- `README.md` — Template-specific documentation; vscode-mypy has its own README.
- `src/test/python_tests/test_notebook.py` — New test file added by the template. vscode-mypy should add its own notebook integration tests covering mypy-specific behavior.

---
🤖 This issue was auto-generated by the [`extension-template-sync`](.github/workflows/extension-template-sync.md) workflow.

> Generated by [Extension Template Sync](https://github.com/microsoft/vscode-mypy/actions/runs/22928519858)

Contributor guide

Open the contributing guide

Research direction

Start in bundled/tool/lsp_server.py, compare its existing document handlers and _linting_helper with upstream PR #265, and inspect the skipped notebook test path src/test/python_tests/test_notebook.py for relevant coverage. Verify notebook lifecycle handling, parent-path resolution, and mypy-specific behavior for Python cells; done means notebook cells are linted without the existing Not Supported skip and integration tests cover that behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
jupyter-notebook, python, vscode
Domain
devtools, testing-qa
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.