Skip to content

Commit 5480e5d

Browse files
Add support for type definition (#645)
* Add textDocument/typeDefinition plugin * Add tests for textDocument/typeDefinition * Add jedi_ plugin prefix for consistency. * Fix order in CONFIGURATION.md. --------- Co-authored-by: Michał Krassowski <[email protected]>
1 parent e0b5fcf commit 5480e5d

File tree

7 files changed

+155
-0
lines changed

7 files changed

+155
-0
lines changed

CONFIGURATION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This server can be configured using the `workspace/didChangeConfiguration` metho
4242
| `pylsp.plugins.jedi_symbols.enabled` | `boolean` | Enable or disable the plugin. | `true` |
4343
| `pylsp.plugins.jedi_symbols.all_scopes` | `boolean` | If True lists the names of all scopes instead of only the module namespace. | `true` |
4444
| `pylsp.plugins.jedi_symbols.include_import_symbols` | `boolean` | If True includes symbols imported from other libraries. | `true` |
45+
| `pylsp.plugins.jedi_type_definition.enabled` | `boolean` | Enable or disable the plugin. | `true` |
4546
| `pylsp.plugins.mccabe.enabled` | `boolean` | Enable or disable the plugin. | `true` |
4647
| `pylsp.plugins.mccabe.threshold` | `integer` | The minimum threshold that triggers warnings about cyclomatic complexity. | `15` |
4748
| `pylsp.plugins.preload.enabled` | `boolean` | Enable or disable the plugin. | `true` |

pylsp/config/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@
270270
"default": true,
271271
"description": "If True includes symbols imported from other libraries."
272272
},
273+
"pylsp.plugins.jedi_type_definition.enabled": {
274+
"type": "boolean",
275+
"default": true,
276+
"description": "Enable or disable the plugin."
277+
},
273278
"pylsp.plugins.mccabe.enabled": {
274279
"type": "boolean",
275280
"default": true,

pylsp/hookspecs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ def pylsp_definitions(config, workspace, document, position) -> None:
3838
pass
3939

4040

41+
@hookspec(firstresult=True)
42+
def pylsp_type_definition(config, document, position):
43+
pass
44+
45+
4146
@hookspec
4247
def pylsp_dispatchers(config, workspace) -> None:
4348
pass

pylsp/plugins/type_definition.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2021- Python Language Server Contributors.
2+
3+
import logging
4+
5+
from pylsp import _utils, hookimpl
6+
7+
log = logging.getLogger(__name__)
8+
9+
10+
def lsp_location(name):
11+
module_path = name.module_path
12+
if module_path is None or name.line is None or name.column is None:
13+
return None
14+
uri = module_path.as_uri()
15+
return {
16+
"uri": str(uri),
17+
"range": {
18+
"start": {"line": name.line - 1, "character": name.column},
19+
"end": {"line": name.line - 1, "character": name.column + len(name.name)},
20+
},
21+
}
22+
23+
24+
@hookimpl
25+
def pylsp_type_definition(config, document, position):
26+
try:
27+
kwargs = _utils.position_to_jedi_linecolumn(document, position)
28+
script = document.jedi_script()
29+
names = script.infer(**kwargs)
30+
definitions = [
31+
definition
32+
for definition in [lsp_location(name) for name in names]
33+
if definition is not None
34+
]
35+
return definitions
36+
except Exception as e:
37+
log.debug("Failed to run type_definition: %s", e)
38+
return []

pylsp/python_lsp.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def capabilities(self):
284284
"documentRangeFormattingProvider": True,
285285
"documentSymbolProvider": True,
286286
"definitionProvider": True,
287+
"typeDefinitionProvider": True,
287288
"executeCommandProvider": {
288289
"commands": flatten(self._hook("pylsp_commands"))
289290
},
@@ -420,6 +421,9 @@ def completion_item_resolve(self, completion_item):
420421
def definitions(self, doc_uri, position):
421422
return flatten(self._hook("pylsp_definitions", doc_uri, position=position))
422423

424+
def type_definition(self, doc_uri, position):
425+
return self._hook("pylsp_type_definition", doc_uri, position=position)
426+
423427
def document_symbols(self, doc_uri):
424428
return flatten(self._hook("pylsp_document_symbols", doc_uri))
425429

@@ -770,6 +774,11 @@ def m_text_document__definition(self, textDocument=None, position=None, **_kwarg
770774
return self._cell_document__definition(document, position, **_kwargs)
771775
return self.definitions(textDocument["uri"], position)
772776

777+
def m_text_document__type_definition(
778+
self, textDocument=None, position=None, **_kwargs
779+
):
780+
return self.type_definition(textDocument["uri"], position)
781+
773782
def m_text_document__document_highlight(
774783
self, textDocument=None, position=None, **_kwargs
775784
):

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ folding = "pylsp.plugins.folding"
6868
flake8 = "pylsp.plugins.flake8_lint"
6969
jedi_completion = "pylsp.plugins.jedi_completion"
7070
jedi_definition = "pylsp.plugins.definition"
71+
jedi_type_definition = "pylsp.plugins.type_definition"
7172
jedi_hover = "pylsp.plugins.hover"
7273
jedi_highlight = "pylsp.plugins.highlight"
7374
jedi_references = "pylsp.plugins.references"

test/plugins/test_type_definition.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright 2021- Python Language Server Contributors.
2+
3+
from pylsp import uris
4+
from pylsp.plugins.type_definition import pylsp_type_definition
5+
from pylsp.workspace import Document
6+
7+
DOC_URI = uris.from_fs_path(__file__)
8+
DOC = """\
9+
from dataclasses import dataclass
10+
11+
@dataclass
12+
class IntPair:
13+
a: int
14+
b: int
15+
16+
def main() -> None:
17+
l0 = list(1, 2)
18+
19+
my_pair = IntPair(a=10, b=20)
20+
print(f"Original pair: {my_pair}")
21+
"""
22+
23+
24+
def test_type_definitions(config, workspace) -> None:
25+
# Over 'IntPair' in 'main'
26+
cursor_pos = {"line": 10, "character": 14}
27+
28+
# The definition of 'IntPair'
29+
def_range = {
30+
"start": {"line": 3, "character": 6},
31+
"end": {"line": 3, "character": 13},
32+
}
33+
34+
doc = Document(DOC_URI, workspace, DOC)
35+
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_type_definition(
36+
config, doc, cursor_pos
37+
)
38+
39+
40+
def test_builtin_definition(config, workspace) -> None:
41+
# Over 'list' in main
42+
cursor_pos = {"line": 8, "character": 9}
43+
44+
doc = Document(DOC_URI, workspace, DOC)
45+
46+
defns = pylsp_type_definition(config, doc, cursor_pos)
47+
assert len(defns) == 1
48+
assert defns[0]["uri"].endswith("builtins.pyi")
49+
50+
51+
def test_mutli_file_type_definitions(config, workspace, tmpdir) -> None:
52+
# Create a dummy module out of the workspace's root_path and try to get
53+
# a definition on it in another file placed next to it.
54+
module_content = """\
55+
from dataclasses import dataclass
56+
57+
@dataclass
58+
class IntPair:
59+
a: int
60+
b: int
61+
"""
62+
p1 = tmpdir.join("intpair.py")
63+
p1.write(module_content)
64+
# The uri for intpair.py
65+
module_path = str(p1)
66+
module_uri = uris.from_fs_path(module_path)
67+
68+
# Content of doc to test type definition
69+
doc_content = """\
70+
from intpair import IntPair
71+
72+
def main() -> None:
73+
l0 = list(1, 2)
74+
75+
my_pair = IntPair(a=10, b=20)
76+
print(f"Original pair: {my_pair}")
77+
"""
78+
p2 = tmpdir.join("main.py")
79+
p2.write(doc_content)
80+
doc_path = str(p2)
81+
doc_uri = uris.from_fs_path(doc_path)
82+
83+
doc = Document(doc_uri, workspace, doc_content)
84+
85+
# The range where IntPair is defined in intpair.py
86+
def_range = {
87+
"start": {"line": 3, "character": 6},
88+
"end": {"line": 3, "character": 13},
89+
}
90+
91+
# The position where IntPair is called in main.py
92+
cursor_pos = {"line": 5, "character": 14}
93+
94+
assert [{"uri": module_uri, "range": def_range}] == pylsp_type_definition(
95+
config, doc, cursor_pos
96+
)

0 commit comments

Comments
 (0)