Skip to content

Commit 54a25ba

Browse files
committed
WIP go to definition support for notebook cell documents
1 parent 5434d1f commit 54a25ba

File tree

5 files changed

+167
-10
lines changed

5 files changed

+167
-10
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ Dev install
165165
166166
```
167167
# create conda env
168-
cc python-lsp-server
169-
ca python-lsp-server
168+
conda create python-lsp-server
169+
conda activate python-lsp-server
170170

171171
pip install ".[all]"
172172
pip install ".[websockets]"

pylsp/python_lsp.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from . import lsp, _utils, uris
1818
from .config import config
19-
from .workspace import Workspace, Document, Notebook
19+
from .workspace import Workspace, Document, Notebook, Cell
2020
from ._version import __version__
2121

2222
log = logging.getLogger(__name__)
@@ -480,7 +480,7 @@ def m_notebook_document__did_open(self, notebookDocument=None, cellTextDocuments
480480
cells=notebookDocument['cells'], version=notebookDocument.get('version'),
481481
metadata=notebookDocument.get('metadata'))
482482
for cell in (cellTextDocuments or []):
483-
workspace.put_cell_document(cell['uri'], cell['languageId'], cell['text'], version=cell.get('version'))
483+
workspace.put_cell_document(cell['uri'], notebookDocument['uri'], cell['languageId'], cell['text'], version=cell.get('version'))
484484
self.lint(notebookDocument['uri'], is_saved=True)
485485

486486
def m_notebook_document__did_close(self, notebookDocument=None, cellTextDocuments=None, **_kwargs):
@@ -521,7 +521,7 @@ def m_notebook_document__did_change(self, notebookDocument=None, change=None, **
521521
# Case 2
522522
# Cell documents
523523
for cell_document in structure['didOpen']:
524-
workspace.put_cell_document(cell_document['uri'], cell_document['languageId'],
524+
workspace.put_cell_document(cell_document['uri'], notebookDocument['uri'], cell_document['languageId'],
525525
cell_document['text'], cell_document.get('version'))
526526
# Cell metadata which is added to Notebook
527527
workspace.add_notebook_cells(notebookDocument['uri'], notebook_cell_array_change['cells'], start)
@@ -585,7 +585,66 @@ def m_text_document__code_lens(self, textDocument=None, **_kwargs):
585585
def m_text_document__completion(self, textDocument=None, position=None, **_kwargs):
586586
return self.completions(textDocument['uri'], position)
587587

588+
def m_notebook_document__definition(self, cellDocument=None, position=None, **_kwargs):
589+
# First, we create a temp TextDocument that represents the whole notebook
590+
# contents. We'll use this to send to the hook.
591+
workspace = self._match_uri_to_workspace(notebookDocument['uri'])
592+
593+
random_uri = str(uuid.uuid4())
594+
# cell_list helps us map the diagnostics back to the correct cell later.
595+
cell_list: List[Dict[str, Any]] = []
596+
597+
offset = 0
598+
total_source = ""
599+
for cell in notebookDocument.cells:
600+
cell_uri = cell['document']
601+
cell_document = workspace.get_cell_document(cell_uri)
602+
603+
num_lines = cell_document.line_count
604+
605+
data = {
606+
'uri': cell_uri,
607+
'line_start': offset,
608+
'line_end': offset + num_lines - 1,
609+
'source': cell_document.source
610+
}
611+
612+
cell_list.append(data)
613+
if offset == 0:
614+
total_source = cell_document.source
615+
else:
616+
total_source += ("\n" + cell_document.source)
617+
618+
offset += num_lines
619+
620+
# TODO: make a workspace temp document context manager that yields the random uri and cleans up afterwards
621+
workspace.put_document(random_uri, total_source)
622+
623+
try:
624+
# TODO: adjust position to temp document
625+
# position =
626+
definitions = self.definitions(random_uri, position)
627+
628+
# {
629+
# 'uri': uris.uri_with(document.uri, path=str(d.module_path)),
630+
# 'range': {
631+
# 'start': {'line': d.line - 1, 'character': d.column},
632+
# 'end': {'line': d.line - 1, 'character': d.column + len(d.name)},
633+
# }
634+
# }
635+
for definition in definitions:
636+
if definition['uri'] == random_uri:
637+
# find what cell the start is in
638+
# make sure the end is inside the cell's line_end
639+
# subtract that cell's line_start from both definition start and end
640+
pass
641+
finally:
642+
workspace.rm_document(random_uri)
643+
588644
def m_text_document__definition(self, textDocument=None, position=None, **_kwargs):
645+
if isinstance(textDocument, Cell):
646+
# actually, test to see if the document is a cell document
647+
return self.m_notebook_document__definition(textDocument, position, **_kwargs)
589648
return self.definitions(textDocument['uri'], position)
590649

591650
def m_text_document__document_highlight(self, textDocument=None, position=None, **_kwargs):

pylsp/workspace.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ def remove_notebook_cells(self, doc_uri, start, delete_count):
128128
def update_notebook_metadata(self, doc_uri, metadata):
129129
self._docs[doc_uri].metadata = metadata
130130

131-
def put_cell_document(self, doc_uri, language_id, source, version=None):
132-
self._docs[doc_uri] = self._create_cell_document(doc_uri, language_id, source, version)
131+
def put_cell_document(self, doc_uri, notebook_uri, language_id, source, version=None):
132+
self._docs[doc_uri] = self._create_cell_document(doc_uri, notebook_uri, language_id, source, version)
133133

134134
def rm_document(self, doc_uri):
135135
self._docs.pop(doc_uri)
@@ -288,11 +288,12 @@ def _create_notebook_document(self, doc_uri, notebook_type, cells, version=None,
288288
metadata=metadata
289289
)
290290

291-
def _create_cell_document(self, doc_uri, language_id, source=None, version=None):
291+
def _create_cell_document(self, doc_uri, notebook_uri, language_id, source=None, version=None):
292292
# TODO: remove what is unnecessary here.
293293
path = uris.to_fs_path(doc_uri)
294294
return Cell(
295295
doc_uri,
296+
notebook_uri=notebook_uri,
296297
language_id=language_id,
297298
workspace=self,
298299
source=source,
@@ -511,10 +512,11 @@ def remove_cells(self, start: int, delete_count: int) -> None:
511512
class Cell(Document):
512513
"""Represents a cell in a notebook."""
513514

514-
def __init__(self, uri, language_id, workspace, source=None, version=None, local=True, extra_sys_path=None,
515+
def __init__(self, uri, notebook_uri, language_id, workspace, source=None, version=None, local=True, extra_sys_path=None,
515516
rope_project_builder=None):
516517
super().__init__(uri, workspace, source, version, local, extra_sys_path, rope_project_builder)
517518
self.language_id = language_id
519+
self.notebook_uri = notebook_uri
518520

519521
@property
520522
@lock

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ test = [
5656
"numpy",
5757
"pandas",
5858
"matplotlib",
59-
"pyqt5",
59+
# "pyqt5",
6060
"flaky",
6161
]
6262

test/test_notebook_document.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,99 @@ def test_notebook__did_close(
582582
)
583583
wait_for_condition(lambda: mock_notify.call_count >= 2)
584584
assert len(server.workspace.documents) == 0
585+
586+
def test_notebook_definition(
587+
client_server_pair,
588+
): # pylint: disable=redefined-outer-name
589+
client, server = client_server_pair
590+
client._endpoint.request(
591+
"initialize",
592+
{
593+
"processId": 1234,
594+
"rootPath": os.path.dirname(__file__),
595+
"initializationOptions": {},
596+
},
597+
).result(timeout=CALL_TIMEOUT_IN_SECONDS)
598+
599+
# Open notebook
600+
with patch.object(server._endpoint, "notify") as mock_notify:
601+
client._endpoint.notify(
602+
"notebookDocument/didOpen",
603+
{
604+
"notebookDocument": {
605+
"uri": "notebook_uri",
606+
"notebookType": "jupyter-notebook",
607+
"cells": [
608+
{
609+
"kind": NotebookCellKind.Code,
610+
"document": "cell_1_uri",
611+
},
612+
{
613+
"kind": NotebookCellKind.Code,
614+
"document": "cell_2_uri",
615+
},
616+
],
617+
},
618+
"cellTextDocuments": [
619+
{
620+
"uri": "cell_1_uri",
621+
"languageId": "python",
622+
"text": "x = 1",
623+
},
624+
{
625+
"uri": "cell_2_uri",
626+
"languageId": "python",
627+
"text": "x",
628+
},
629+
],
630+
},
631+
)
632+
wait_for_condition(lambda: mock_notify.call_count >= 2)
633+
assert len(server.workspace.documents) == 3
634+
for uri in ["cell_1_uri", "cell_2_uri", "notebook_uri"]:
635+
assert uri in server.workspace.documents
636+
637+
638+
# TODO: send definition request
639+
with patch.object(server._endpoint, "notify") as mock_notify:
640+
client._endpoint.notify(
641+
"textDocument/definition",
642+
{
643+
"textDocument": {
644+
"uri": "cell_2_uri",
645+
},
646+
"position": {
647+
"line": 0,
648+
"character": 1
649+
}
650+
},
651+
)
652+
wait_for_condition(lambda: mock_notify.call_count >= 2)
653+
654+
655+
# TODO: definition return
656+
expected_call_args = [
657+
call(
658+
"textDocument/publishDiagnostics",
659+
params={"uri": "cell_1_uri", "diagnostics": []},
660+
),
661+
call(
662+
"textDocument/publishDiagnostics",
663+
params={
664+
"uri": "cell_3_uri",
665+
"diagnostics": [
666+
{
667+
"source": "pycodestyle",
668+
"range": {
669+
"start": {"line": 0, "character": 8},
670+
"end": {"line": 0, "character": 8},
671+
},
672+
"message": "W292 no newline at end of file",
673+
"code": "W292",
674+
"severity": 2,
675+
}
676+
],
677+
},
678+
),
679+
]
680+
mock_notify.assert_has_calls(expected_call_args)

0 commit comments

Comments
 (0)