Skip to content

Commit d81c7ba

Browse files
authored
Correct method and attribute detection on document/symbols call (#846)
1 parent 7dbd812 commit d81c7ba

File tree

4 files changed

+79
-12
lines changed

4 files changed

+79
-12
lines changed

pyls/_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ def find_parents(root, path, names):
8282
return []
8383

8484

85+
def path_to_dot_name(path):
86+
"""Given a path to a module, derive its dot-separated full name."""
87+
directory = os.path.dirname(path)
88+
module_name, _ = os.path.splitext(os.path.basename(path))
89+
full_name = [module_name]
90+
while os.path.exists(os.path.join(directory, '__init__.py')):
91+
this_directory = os.path.basename(directory)
92+
directory = os.path.dirname(directory)
93+
full_name = [this_directory] + full_name
94+
return '.'.join(full_name)
95+
96+
8597
def match_uri_to_workspace(uri, workspaces):
8698
if uri is None:
8799
return None

pyls/plugins/symbols.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,65 @@
88

99
@hookimpl
1010
def pyls_document_symbols(config, document):
11-
all_scopes = config.plugin_settings('jedi_symbols').get('all_scopes', True)
11+
# pylint: disable=broad-except
12+
# pylint: disable=too-many-nested-blocks
13+
# pylint: disable=too-many-locals
14+
# pylint: disable=too-many-branches
15+
symbols_settings = config.plugin_settings('jedi_symbols')
16+
all_scopes = symbols_settings.get('all_scopes', True)
17+
add_import_symbols = symbols_settings.get('include_import_symbols', True)
1218
definitions = document.jedi_names(all_scopes=all_scopes)
13-
return [{
14-
'name': d.name,
15-
'containerName': _container(d),
16-
'location': {
17-
'uri': document.uri,
18-
'range': _range(d),
19-
},
20-
'kind': _kind(d),
21-
} for d in definitions if _include_def(d)]
19+
module_name = document.dot_path
20+
symbols = []
21+
exclude = set({})
22+
redefinitions = {}
23+
while definitions != []:
24+
d = definitions.pop(0)
25+
if not add_import_symbols:
26+
sym_full_name = d.full_name
27+
if sym_full_name is not None:
28+
if (not sym_full_name.startswith(module_name) and
29+
not sym_full_name.startswith('__main__')):
30+
continue
31+
32+
if _include_def(d) and document.path == d.module_path:
33+
tuple_range = _tuple_range(d)
34+
if tuple_range in exclude:
35+
continue
36+
37+
kind = redefinitions.get(tuple_range, None)
38+
if kind is not None:
39+
exclude |= {tuple_range}
40+
41+
if d.type == 'statement':
42+
if d.description.startswith('self'):
43+
kind = 'field'
44+
45+
symbol = {
46+
'name': d.name,
47+
'containerName': _container(d),
48+
'location': {
49+
'uri': document.uri,
50+
'range': _range(d),
51+
},
52+
'kind': _kind(d) if kind is None else _SYMBOL_KIND_MAP[kind],
53+
}
54+
symbols.append(symbol)
55+
56+
if d.type == 'class':
57+
try:
58+
defined_names = list(d.defined_names())
59+
for method in defined_names:
60+
if method.type == 'function':
61+
redefinitions[_tuple_range(method)] = 'method'
62+
elif method.type == 'statement':
63+
redefinitions[_tuple_range(method)] = 'field'
64+
else:
65+
redefinitions[_tuple_range(method)] = method.type
66+
definitions = list(defined_names) + definitions
67+
except Exception:
68+
pass
69+
return symbols
2270

2371

2472
def _include_def(definition):
@@ -56,6 +104,11 @@ def _range(definition):
56104
}
57105

58106

107+
def _tuple_range(definition):
108+
definition = definition._name.tree_name.get_definition()
109+
return (definition.start_pos, definition.end_pos)
110+
111+
59112
_SYMBOL_KIND_MAP = {
60113
'none': SymbolKind.Variable,
61114
'type': SymbolKind.Class,
@@ -95,6 +148,7 @@ def _range(definition):
95148
'string': SymbolKind.String,
96149
'unicode': SymbolKind.String,
97150
'list': SymbolKind.Array,
151+
'field': SymbolKind.Field
98152
}
99153

100154

pyls/workspace.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def __init__(self, uri, workspace, source=None, version=None, local=True, extra_
122122
self.uri = uri
123123
self.version = version
124124
self.path = uris.to_fs_path(uri)
125+
self.dot_path = _utils.path_to_dot_name(self.path)
125126
self.filename = os.path.basename(self.path)
126127

127128
self._config = workspace._config

test/plugins/test_symbols.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def sym(name):
4040
# Check we have some sane mappings to VSCode constants
4141
assert sym('a')['kind'] == SymbolKind.Variable
4242
assert sym('B')['kind'] == SymbolKind.Class
43-
assert sym('__init__')['kind'] == SymbolKind.Function
43+
assert sym('__init__')['kind'] == SymbolKind.Method
4444
assert sym('main')['kind'] == SymbolKind.Function
4545

4646
# Not going to get too in-depth here else we're just testing Jedi
@@ -54,7 +54,7 @@ def test_symbols(config, workspace):
5454

5555
# All four symbols (import sys, a, B, main)
5656
# y is not in the root scope, it shouldn't be returned
57-
assert len(symbols) == 4
57+
assert len(symbols) == 5
5858

5959
def sym(name):
6060
return [s for s in symbols if s['name'] == name][0]

0 commit comments

Comments
 (0)