Skip to content

Commit 4211502

Browse files
authored
Allow Jedi "goto" to perform multiple hops for "go to definition" (#443)
1 parent 6059aa3 commit 4211502

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

pylsp/plugins/definition.py

+37-5
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,54 @@
11
# Copyright 2017-2020 Palantir Technologies, Inc.
22
# Copyright 2021- Python Language Server Contributors.
3-
3+
from __future__ import annotations
44
import logging
5+
from typing import Any, Dict, List, TYPE_CHECKING
56
from pylsp import hookimpl, uris, _utils
67

8+
if TYPE_CHECKING:
9+
from jedi.api import Script
10+
from jedi.api.classes import Name
11+
from pylsp.config.config import Config
12+
from pylsp.workspace import Document
13+
714
log = logging.getLogger(__name__)
815

916

17+
MAX_JEDI_GOTO_HOPS = 100
18+
19+
20+
def _resolve_definition(
21+
maybe_defn: Name, script: Script, settings: Dict[str, Any]
22+
) -> Name:
23+
for _ in range(MAX_JEDI_GOTO_HOPS):
24+
if maybe_defn.is_definition() or maybe_defn.module_path != script.path:
25+
break
26+
defns = script.goto(
27+
follow_imports=settings.get("follow_imports", True),
28+
follow_builtin_imports=settings.get("follow_builtin_imports", True),
29+
line=maybe_defn.line,
30+
column=maybe_defn.column,
31+
)
32+
if len(defns) == 1:
33+
maybe_defn = defns[0]
34+
else:
35+
break
36+
return maybe_defn
37+
38+
1039
@hookimpl
11-
def pylsp_definitions(config, document, position):
40+
def pylsp_definitions(
41+
config: Config, document: Document, position: Dict[str, int]
42+
) -> List[Dict[str, Any]]:
1243
settings = config.plugin_settings("jedi_definition")
1344
code_position = _utils.position_to_jedi_linecolumn(document, position)
14-
definitions = document.jedi_script(use_document_path=True).goto(
45+
script = document.jedi_script(use_document_path=True)
46+
definitions = script.goto(
1547
follow_imports=settings.get("follow_imports", True),
1648
follow_builtin_imports=settings.get("follow_builtin_imports", True),
1749
**code_position,
1850
)
19-
51+
definitions = [_resolve_definition(d, script, settings) for d in definitions]
2052
follow_builtin_defns = settings.get("follow_builtin_definitions", True)
2153
return [
2254
{
@@ -31,7 +63,7 @@ def pylsp_definitions(config, document, position):
3163
]
3264

3365

34-
def _not_internal_definition(definition):
66+
def _not_internal_definition(definition: Name) -> bool:
3567
return (
3668
definition.line is not None
3769
and definition.column is not None

test/plugins/test_definitions.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
DOC = """def a():
1313
pass
1414
15-
print a()
15+
print(a())
1616
1717
1818
class Directory(object):
@@ -21,6 +21,21 @@ def __init__(self):
2121
2222
def add_member(self, id, name):
2323
self.members[id] = name
24+
25+
26+
subscripted_before_reference = {}
27+
subscripted_before_reference[0] = 0
28+
subscripted_before_reference
29+
30+
31+
def my_func():
32+
print('called')
33+
34+
alias = my_func
35+
my_list = [1, None, alias]
36+
inception = my_list[2]
37+
38+
inception()
2439
"""
2540

2641

@@ -40,6 +55,40 @@ def test_definitions(config, workspace):
4055
)
4156

4257

58+
def test_indirect_definitions(config, workspace):
59+
# Over 'subscripted_before_reference'
60+
cursor_pos = {"line": 16, "character": 0}
61+
62+
# The definition of 'subscripted_before_reference',
63+
# skipping intermediate writes to the most recent definition
64+
def_range = {
65+
"start": {"line": 14, "character": 0},
66+
"end": {"line": 14, "character": len("subscripted_before_reference")},
67+
}
68+
69+
doc = Document(DOC_URI, workspace, DOC)
70+
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_definitions(
71+
config, doc, cursor_pos
72+
)
73+
74+
75+
def test_definition_with_multihop_inference_goto(config, workspace):
76+
# Over 'inception()'
77+
cursor_pos = {"line": 26, "character": 0}
78+
79+
# The most recent definition of 'inception',
80+
# ignoring alias hops
81+
def_range = {
82+
"start": {"line": 24, "character": 0},
83+
"end": {"line": 24, "character": len("inception")},
84+
}
85+
86+
doc = Document(DOC_URI, workspace, DOC)
87+
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_definitions(
88+
config, doc, cursor_pos
89+
)
90+
91+
4392
def test_builtin_definition(config, workspace):
4493
# Over 'i' in dict
4594
cursor_pos = {"line": 8, "character": 24}

0 commit comments

Comments
 (0)