Skip to content

Add Jupyter notebook cell support via LSP 3.17 Notebook Document Sync#265

Open
Copilot wants to merge 7 commits intomainfrom
copilot/add-jupyter-notebook-support
Open

Add Jupyter notebook cell support via LSP 3.17 Notebook Document Sync#265
Copilot wants to merge 7 commits intomainfrom
copilot/add-jupyter-notebook-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 4, 2026

  • Add NOTEBOOK_SYNC_OPTIONS constant and pass to LanguageServer constructor in lsp_server.py
  • Add _get_document_path helper in lsp_server.py
  • Add four notebook lifecycle handlers in lsp_server.py
  • Update _run_tool_on_document to use _get_document_path and remove the skip guard
  • Add notebook activation events to package.json
  • Add four notebook notify methods to session.py
  • Add notebook constants/types to test_get_cwd.py mock setup
  • Create test_notebook.py with notebook lifecycle tests
  • Create sample.ipynb test data file
  • Add notebook support section to README.md
  • Remove vscode-isort PR reference line from README
  • Filter newly-added cells by NotebookCellKind.Code in notebook_did_change
  • Add test_notebook_did_change_new_cell_kind_filter test
  • Fix _get_document_path to check vscode-notebook-cell: explicitly so other schemes fall through safely
  • Remove unused _collect_diagnostics helper from test_notebook.py
  • Replace raw string ops in _get_document_path with urllib.parse.urlparse/urlunparse
  • Replace private _replace() with explicit tuple in urlunparse call
  • Simplify notebook_did_change cell-kind filter from dict to a set of code-cell URIs
Original prompt

Summary

Add Jupyter notebook cell support to the VS Code Python tools extension template, enabling linting/formatting diagnostics and code actions for Python cells within .ipynb notebooks. This follows the same approach as microsoft/vscode-isort#565.

Context

The template currently explicitly skips notebook cells in _run_tool_on_document (line 439-442 of bundled/tool/lsp_server.py):

if str(document.uri).startswith("vscode-notebook-cell"):
    # TODO: Decide on if you want to skip notebook cells.
    # Skip notebook cells
    return None

With pygls supporting LSP 3.17's notebook document sync protocol, the template should provide built-in notebook support as a starting point for extension developers.

Changes Required

1. LSP Server (bundled/tool/lsp_server.py)

1.1 Declare NotebookDocumentSyncOptions

Add after MAX_WORKERS and pass to the LanguageServer constructor:

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,
)

LSP_SERVER = server.LanguageServer(
    name="<pytool-display-name>",
    version="<server version>",
    max_workers=MAX_WORKERS,
    notebook_document_sync=NOTEBOOK_SYNC_OPTIONS,
)

1.2 Add _get_document_path helper

Resolves vscode-notebook-cell: URIs to filesystem paths:

def _get_document_path(document: workspace.Document) -> str:
    """Returns the file path for a document, handling notebook cell URIs.

    Examples:
        file:///path/to/file.py -> /path/to/file.py
        vscode-notebook-cell:/path/to/notebook.ipynb#C00001 -> /path/to/notebook.ipynb
    """
    if not document.uri.startswith("file:"):
        return uris.to_fs_path(
            document.uri.split("#")[0].replace("vscode-notebook-cell:", "file:", 1)
        )
    return uris.to_fs_path(document.uri)

1.3 Add four notebook lifecycle handlers

After the existing did_close handler, add handlers for:

  • NOTEBOOK_DOCUMENT_DID_OPEN — iterate all code cells, run _linting_helper, publish diagnostics per cell
  • NOTEBOOK_DOCUMENT_DID_CHANGE — re-lint cells with text changes, lint newly added cells (structure.did_open), clear diagnostics for removed cells (structure.did_close)
  • NOTEBOOK_DOCUMENT_DID_SAVE — re-lint all code cells
  • NOTEBOOK_DOCUMENT_DID_CLOSE — clear diagnostics for all cells

Each handler should:

  • Use LSP_SERVER.workspace.get_notebook_document() and get_text_document()
  • Filter cells on lsp.NotebookCellKind.Code
  • Use LSP_SERVER.text_document_publish_diagnostics() with lsp.PublishDiagnosticsParams

Example pattern for notebook_did_open:

@LSP_SERVER.feature(lsp.NOTEBOOK_DOCUMENT_DID_OPEN)
def notebook_did_open(params: lsp.DidOpenNotebookDocumentParams) -> None:
    """Run diagnostics on each code cell when a notebook is opened."""
    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 or cell.document is None:
            continue
        document = LSP_SERVER.workspace.get_text_document(cell.document)
        diagnostics: list[lsp.Diagnostic] = _linting_helper(document)
        LSP_SERVER.text_document_publish_diagnostics(
            lsp.PublishDiagnosticsParams(uri=document.uri, diagnostics=diagnostics)
        )

Follow this same pattern for did_change, did_save, and did_close. See vscode-isort PR #565 diff for lsp_server.py for the full implementation of all four handlers.

1.4 Remove/update the notebook-cell skip guard in _run_tool_on_document

Replace the current skip guard (lines 439-442) with a TODO comment that indicates notebook cells are now supported, and update document.path references in that function to use _get_document_path(document) where the tool needs a filesystem path.

2. Extension Manifest (package.json)

Add notebook activation events:

"activationEvents": [
    "onLanguage:python",
    "workspaceContains:*.py",
    "onNotebook:jupyter-notebook",
    "onNotebook:interactive"
],

3. Test Infrastructure

3.1 LSP Test Client (src/test/python_tests/lsp_test_client/session.py)

Add four new methods to LspSession (after notify_did_close):

  • notify_notebook_did_open(self, params) → sends "notebookDocument/didOpen"
  • notify_notebook_did_change(self, params) → sends "notebookDocument/didChange"
  • `notify_notebook_did_save(self, params)...

This pull request was created from Copilot chat.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Jupyter notebook cell support to Python tools extension Add Jupyter notebook cell support via LSP 3.17 Notebook Document Sync Mar 4, 2026
Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
@edvilme edvilme marked this pull request as ready for review March 4, 2026 18:22
@edvilme
Copy link
Contributor

edvilme commented Mar 4, 2026

This follows the same approach as microsoft/vscode-isort#565

Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com>
Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com>
Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
…I set

Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
@edvilme edvilme requested a review from rchiodo March 4, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants