Skip to content

Commit

Permalink
Merge pull request #13109 from andfoy/outline_explorer_lsp
Browse files Browse the repository at this point in the history
PR: Migrate the Outline Explorer to use LSP information
  • Loading branch information
ccordoba12 committed Sep 16, 2020
2 parents 66e98a5 + a3258a3 commit ce9756a
Show file tree
Hide file tree
Showing 24 changed files with 1,474 additions and 705 deletions.
4 changes: 2 additions & 2 deletions external-deps/python-language-server/.gitrepo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/palantir/python-language-server.git
branch = develop
commit = eb479ff2b214a13fd1c2fb1dcf2993a45c3bb830
parent = 1aeb5896872898f765a124a69fe6bcfa96a266e3
commit = d81c7ba14d54b8e52192b0e00cbb4dacbb6f414d
parent = 772425a82808da813eca028f9fbb02a3d239e933
method = merge
cmdver = 0.4.1
12 changes: 12 additions & 0 deletions external-deps/python-language-server/pyls/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ def find_parents(root, path, names):
return []


def path_to_dot_name(path):
"""Given a path to a module, derive its dot-separated full name."""
directory = os.path.dirname(path)
module_name, _ = os.path.splitext(os.path.basename(path))
full_name = [module_name]
while os.path.exists(os.path.join(directory, '__init__.py')):
this_directory = os.path.basename(directory)
directory = os.path.dirname(directory)
full_name = [this_directory] + full_name
return '.'.join(full_name)


def match_uri_to_workspace(uri, workspaces):
if uri is None:
return None
Expand Down
74 changes: 64 additions & 10 deletions external-deps/python-language-server/pyls/plugins/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,65 @@

@hookimpl
def pyls_document_symbols(config, document):
all_scopes = config.plugin_settings('jedi_symbols').get('all_scopes', True)
# pylint: disable=broad-except
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
symbols_settings = config.plugin_settings('jedi_symbols')
all_scopes = symbols_settings.get('all_scopes', True)
add_import_symbols = symbols_settings.get('include_import_symbols', True)
definitions = document.jedi_names(all_scopes=all_scopes)
return [{
'name': d.name,
'containerName': _container(d),
'location': {
'uri': document.uri,
'range': _range(d),
},
'kind': _kind(d),
} for d in definitions if _include_def(d)]
module_name = document.dot_path
symbols = []
exclude = set({})
redefinitions = {}
while definitions != []:
d = definitions.pop(0)
if not add_import_symbols:
sym_full_name = d.full_name
if sym_full_name is not None:
if (not sym_full_name.startswith(module_name) and
not sym_full_name.startswith('__main__')):
continue

if _include_def(d) and document.path == d.module_path:
tuple_range = _tuple_range(d)
if tuple_range in exclude:
continue

kind = redefinitions.get(tuple_range, None)
if kind is not None:
exclude |= {tuple_range}

if d.type == 'statement':
if d.description.startswith('self'):
kind = 'field'

symbol = {
'name': d.name,
'containerName': _container(d),
'location': {
'uri': document.uri,
'range': _range(d),
},
'kind': _kind(d) if kind is None else _SYMBOL_KIND_MAP[kind],
}
symbols.append(symbol)

if d.type == 'class':
try:
defined_names = list(d.defined_names())
for method in defined_names:
if method.type == 'function':
redefinitions[_tuple_range(method)] = 'method'
elif method.type == 'statement':
redefinitions[_tuple_range(method)] = 'field'
else:
redefinitions[_tuple_range(method)] = method.type
definitions = list(defined_names) + definitions
except Exception:
pass
return symbols


def _include_def(definition):
Expand Down Expand Up @@ -56,6 +104,11 @@ def _range(definition):
}


def _tuple_range(definition):
definition = definition._name.tree_name.get_definition()
return (definition.start_pos, definition.end_pos)


_SYMBOL_KIND_MAP = {
'none': SymbolKind.Variable,
'type': SymbolKind.Class,
Expand Down Expand Up @@ -95,6 +148,7 @@ def _range(definition):
'string': SymbolKind.String,
'unicode': SymbolKind.String,
'list': SymbolKind.Array,
'field': SymbolKind.Field
}


Expand Down
1 change: 1 addition & 0 deletions external-deps/python-language-server/pyls/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def __init__(self, uri, workspace, source=None, version=None, local=True, extra_
self.uri = uri
self.version = version
self.path = uris.to_fs_path(uri)
self.dot_path = _utils.path_to_dot_name(self.path)
self.filename = os.path.basename(self.path)

self._config = workspace._config
Expand Down
10 changes: 3 additions & 7 deletions external-deps/python-language-server/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@
'backports.functools_lru_cache; python_version<"3.2"',
'jedi>=0.17.0,<0.18.0',
'python-jsonrpc-server>=0.4.0',
'pluggy']

if sys.version_info[0] == 2:
install_requires.append('ujson<=2.0.3; platform_system!="Windows"')
else:
install_requires.append('ujson>=3.0.0')

'pluggy',
'ujson<=2.0.3 ; platform_system!="Windows" and python_version<"3.0"',
'ujson>=3.0.0 ; python_version>"3"']

setup(
name='python-language-server',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def sym(name):
# Check we have some sane mappings to VSCode constants
assert sym('a')['kind'] == SymbolKind.Variable
assert sym('B')['kind'] == SymbolKind.Class
assert sym('__init__')['kind'] == SymbolKind.Function
assert sym('__init__')['kind'] == SymbolKind.Method
assert sym('main')['kind'] == SymbolKind.Function

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

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

def sym(name):
return [s for s in symbols if s['name'] == name][0]
Expand Down
3 changes: 2 additions & 1 deletion spyder/config/lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
},
'jedi_symbols': {
'enabled': True,
'all_scopes': True
'all_scopes': True,
'include_import_symbols': False
},
'mccabe': {
'enabled': False,
Expand Down
33 changes: 33 additions & 0 deletions spyder/plugins/completion/languageserver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,39 @@ class InsertTextFormat:
PLAIN_TEXT = 1
SNIPPET = 2


# ----------------- DOCUMENT SYMBOL RELATED VALUES ------------------


class SymbolKind:
FILE = 1
MODULE = 2
NAMESPACE = 3
PACKAGE = 4
CLASS = 5
METHOD = 6
PROPERTY = 7
FIELD = 8
CONSTRUCTOR = 9
ENUM = 10
INTERFACE = 11
FUNCTION = 12
VARIABLE = 13
CONSTANT = 14
STRING = 15
NUMBER = 16
BOOLEAN = 17
ARRAY = 18
OBJECT = 19
KEY = 20
NULL = 21
ENUM_MEMBER = 22
STRUCT = 23
EVENT = 24
OPERATOR = 25
TYPE_PARAMETER = 26


# ----------------- SAVING REQUEST RELATED VALUES -------------------


Expand Down
4 changes: 4 additions & 0 deletions spyder/plugins/editor/widgets/codeeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,8 @@ def document_did_open(self):
@request(method=LSPRequestTypes.DOCUMENT_SYMBOL)
def request_symbols(self):
"""Request document symbols."""
if self.oe_proxy is not None:
self.oe_proxy.emit_request_in_progress()
params = {'file': self.filename}
return params

Expand All @@ -1137,6 +1139,8 @@ def process_symbols(self, params):
symbols = params['params']
if symbols:
self.classfuncdropdown.update_data(symbols)
if self.oe_proxy is not None:
self.oe_proxy.update_outline_info(symbols)
except RuntimeError:
# This is triggered when a codeeditor instance was removed
# before the response can be processed.
Expand Down
55 changes: 55 additions & 0 deletions spyder/plugins/editor/widgets/tests/assets/text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""


import os
import sys


# %% functions
def d():
def inner():
return 2
return inner


# ---- func 1 and 2

def func1():
for i in range(3):
print(i)


def func2():
if True:
pass

# ---- other functions

def a():
pass

def b():
pass

def c():
pass

# %% classes
class Class1:
def __init__(self):
super(Class1, self).__init__()
self.x = 2

def method3(self):
pass

def method2(self):
pass

def method1(self):
pass
Loading

0 comments on commit ce9756a

Please sign in to comment.