Skip to content

Commit cc0efee

Browse files
authored
Port custom changes from old fork of python language server (#1)
* Port custom changes from old fork of python language server * Adjust format of returned hover info to match previous language server * Fix typo * Follow old format for signature help
1 parent cc6d398 commit cc0efee

File tree

9 files changed

+110
-72
lines changed

9 files changed

+110
-72
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,5 @@ ENV/
119119
# Special files
120120
.DS_Store
121121
*.temp
122+
123+
.venv

pylsp/__main__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ def main() -> None:
7474
_configure_logger(args.verbose, args.log_config, args.log_file)
7575

7676
if args.tcp:
77-
start_tcp_lang_server(
78-
args.host, args.port, args.check_parent_process, PythonLSPServer
79-
)
77+
while True:
78+
start_tcp_lang_server(
79+
args.host, args.port, args.check_parent_process, PythonLSPServer
80+
)
81+
time.sleep(0.500)
8082
elif args.ws:
8183
start_ws_lang_server(args.port, args.check_parent_process, PythonLSPServer)
8284
else:

pylsp/hookspecs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ def pylsp_completions(config, workspace, document, position, ignored_names) -> N
2828
pass
2929

3030

31+
@hookspec
32+
def pylsp_completion_detail(config, item) -> None:
33+
pass
34+
35+
3136
@hookspec(firstresult=True)
3237
def pylsp_completion_item_resolve(config, workspace, document, completion_item) -> None:
3338
pass

pylsp/plugins/hover.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def pylsp_hover(config, document, position):
3030
supported_markup_kinds = hover_capabilities.get("contentFormat", ["markdown"])
3131
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
3232

33+
doc = _utils.format_docstring(definition.docstring(raw=True), preferred_markup_kind)["value"]
34+
3335
# Find first exact matching signature
3436
signature = next(
3537
(
@@ -40,11 +42,16 @@ def pylsp_hover(config, document, position):
4042
"",
4143
)
4244

45+
contents = []
46+
if signature:
47+
contents.append({
48+
'language': 'python',
49+
'value': signature,
50+
})
51+
52+
if doc:
53+
contents.append(doc)
54+
4355
return {
44-
"contents": _utils.format_docstring(
45-
# raw docstring returns only doc, without signature
46-
definition.docstring(raw=True),
47-
preferred_markup_kind,
48-
signatures=[signature] if signature else None,
49-
)
56+
"contents": contents or ''
5057
}

pylsp/plugins/jedi_completion.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"statement": lsp.CompletionItemKind.Variable,
2929
}
3030

31+
COMPLETION_CACHE = {}
32+
3133
# Types of parso nodes for which snippet is not included in the completion
3234
_IMPORTS = ("import_name", "import_from")
3335

@@ -135,6 +137,22 @@ def pylsp_completions(config, document, position):
135137

136138
return ready_completions or None
137139

140+
@hookimpl
141+
def pylsp_completion_detail(config, item):
142+
d = COMPLETION_CACHE.get(item)
143+
if d:
144+
completion = {
145+
'label': '', #_label(d),
146+
'kind': _TYPE_MAP.get(d.type),
147+
'detail': '', #_detail(d),
148+
'documentation': _utils.format_docstring(d.docstring()),
149+
'sortText': '', #_sort_text(d),
150+
'insertText': d.name
151+
}
152+
return completion
153+
else:
154+
log.info('Completion missing')
155+
return None
138156

139157
@hookimpl
140158
def pylsp_completion_item_resolve(config, completion_item, document):
@@ -229,6 +247,7 @@ def _format_completion(
229247
resolve_label_or_snippet=False,
230248
snippet_support=False,
231249
):
250+
COMPLETION_CACHE[d.name] = d
232251
completion = {
233252
"label": _label(d, resolve_label_or_snippet),
234253
"kind": _TYPE_MAP.get(d.type),

pylsp/plugins/preload_imports.py

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,11 @@
88
log = logging.getLogger(__name__)
99

1010
MODULES = [
11-
"OpenGL",
12-
"PIL",
13-
"array",
14-
"audioop",
15-
"binascii",
16-
"cPickle",
17-
"cStringIO",
18-
"cmath",
19-
"collections",
20-
"datetime",
21-
"errno",
22-
"exceptions",
23-
"gc",
24-
"imageop",
25-
"imp",
26-
"itertools",
27-
"marshal",
28-
"math",
29-
"matplotlib",
30-
"mmap",
31-
"mpmath",
32-
"msvcrt",
33-
"networkx",
34-
"nose",
35-
"nt",
36-
"numpy",
37-
"operator",
38-
"os",
39-
"os.path",
40-
"pandas",
41-
"parser",
42-
"rgbimg",
43-
"scipy",
44-
"signal",
45-
"skimage",
46-
"sklearn",
47-
"statsmodels",
48-
"strop",
49-
"sympy",
50-
"sys",
51-
"thread",
52-
"time",
53-
"wx",
54-
"xxsubtype",
55-
"zipimport",
56-
"zlib",
11+
"numpy", "tensorflow", "sklearn", "array", "binascii", "cmath", "collections",
12+
"datetime", "errno", "exceptions", "gc", "imageop", "imp", "itertools",
13+
"marshal", "math", "matplotlib", "mmap", "mpmath", "msvcrt", "networkx", "nose", "nt",
14+
"operator", "os", "os.path", "pandas", "parser", "scipy", "signal",
15+
"skimage", "statsmodels", "strop", "sympy", "sys", "thread", "time", "wx", "zlib"
5716
]
5817

5918

pylsp/plugins/signature.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def pylsp_signature_help(config, document, position):
4545
"label": function_sig,
4646
"documentation": _utils.format_docstring(
4747
s.docstring(raw=True), markup_kind=preferred_markup_kind
48-
),
48+
)["value"],
4949
}
5050

5151
# If there are params, add those
@@ -55,7 +55,7 @@ def pylsp_signature_help(config, document, position):
5555
"label": p.name,
5656
"documentation": _utils.format_docstring(
5757
_param_docs(docstring, p.name), markup_kind=preferred_markup_kind
58-
),
58+
)["value"],
5959
}
6060
for p in s.params
6161
]

pylsp/python_lsp.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import uuid
99
from functools import partial
1010
from typing import Any, Dict, List
11+
from hashlib import sha256
1112

1213
try:
1314
import ujson as json
@@ -43,17 +44,36 @@ def setup(self) -> None:
4344
self.delegate = self.DELEGATE_CLASS(self.rfile, self.wfile)
4445

4546
def handle(self) -> None:
46-
try:
47-
self.delegate.start()
48-
except OSError as e:
49-
if os.name == "nt":
50-
# Catch and pass on ConnectionResetError when parent process
51-
# dies
52-
if isinstance(e, WindowsError) and e.winerror == 10054:
53-
pass
47+
self.auth(self.delegate.start)
5448

5549
self.SHUTDOWN_CALL()
5650

51+
def auth(self, cb):
52+
token = ''
53+
if "JUPYTER_TOKEN" in os.environ:
54+
token = os.environ["JUPYTER_TOKEN"]
55+
else:
56+
log.warn('! Missing jupyter token !')
57+
58+
data = self.rfile.readline()
59+
try:
60+
auth_req = json.loads(data.decode().split('\n')[0])
61+
except:
62+
log.error('Error parsing authentication message')
63+
auth_error_msg = { 'msg': 'AUTH_ERROR' }
64+
self.wfile.write(json.dumps(auth_error_msg).encode())
65+
return
66+
67+
hashed_token = sha256(token.encode()).hexdigest()
68+
if auth_req.get('token') == hashed_token:
69+
auth_success_msg = { 'msg': 'AUTH_SUCCESS' }
70+
self.wfile.write(json.dumps(auth_success_msg).encode())
71+
cb()
72+
else:
73+
log.info('Failed to authenticate: invalid credentials')
74+
auth_invalid_msg = { 'msg': 'AUTH_INVALID_CRED' }
75+
self.wfile.write(json.dumps(auth_invalid_msg).encode())
76+
5777

5878
def start_tcp_lang_server(bind_addr, port, check_parent_process, handler_class) -> None:
5979
if not issubclass(handler_class, PythonLSPServer):
@@ -401,7 +421,11 @@ def completions(self, doc_uri, position):
401421
completions = self._hook(
402422
"pylsp_completions", doc_uri, position=position, ignored_names=ignored_names
403423
)
404-
return {"isIncomplete": False, "items": flatten(completions)}
424+
return {"isIncomplete": True, "items": flatten(completions)}
425+
426+
def completion_detail(self, item):
427+
detail = self._hook('pylsp_completion_detail', item=item)
428+
return detail
405429

406430
def completion_item_resolve(self, completion_item):
407431
doc_uri = completion_item.get("data", {}).get("doc_uri", None)
@@ -543,7 +567,7 @@ def folding(self, doc_uri):
543567
return flatten(self._hook("pylsp_folding_range", doc_uri))
544568

545569
def m_completion_item__resolve(self, **completionItem):
546-
return self.completion_item_resolve(completionItem)
570+
return self.completion_detail(completionItem.get('label'))
547571

548572
def m_notebook_document__did_open(
549573
self, notebookDocument=None, cellTextDocuments=None, **_kwargs

pylsp/workspace.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from contextlib import contextmanager
1111
from threading import RLock
1212
from typing import Callable, Generator, List, Optional
13+
import importlib.metadata
1314

1415
import jedi
1516

@@ -392,6 +393,18 @@ def close(self) -> None:
392393

393394

394395
class Document:
396+
DO_NOT_PRELOAD_MODULES = ['attrs', 'backcall', 'bleach', 'certifi', 'chardet', 'cycler', 'decorator', 'defusedxml',
397+
'docopt', 'entrypoints', 'idna', 'importlib-metadata', 'ipykernel', 'ipython-genutils',
398+
'ipython', 'ipywidgets', 'jedi', 'jinja2', 'joblib', 'jsonschema', 'jupyter-client',
399+
'jupyter-core', 'markupsafe', 'mistune', 'nbconvert', 'nbformat', 'notebook', 'packaging',
400+
'pandocfilters', 'parso', 'pexpect', 'pickleshare', 'pip', 'pipreqs', 'pluggy',
401+
'prometheus-client', 'prompt-toolkit', 'ptyprocess', 'pygments', 'pyparsing',
402+
'pyrsistent', 'python-dateutil', 'python-jsonrpc-server', 'python-language-server',
403+
'pytz', 'pyzmq', 'send2trash', 'setuptools', 'six', 'terminado', 'testpath',
404+
'threadpoolctl', 'tornado', 'traitlets', 'ujson', 'wcwidth', 'webencodings', 'wheel',
405+
'widgetsnbextension', 'yarg', 'zipp']
406+
407+
395408
def __init__(
396409
self,
397410
uri,
@@ -417,6 +430,15 @@ def __init__(
417430
self._rope_project_builder = rope_project_builder
418431
self._lock = RLock()
419432

433+
jedi.settings.cache_directory = '.cache/jedi/'
434+
jedi.settings.use_filesystem_cache = True
435+
jedi.settings.auto_import_modules = self._get_auto_import_modules()
436+
437+
def _get_auto_import_modules(self):
438+
installed_packages_list = [dist.metadata['Name'] for dist in importlib.metadata.distributions()]
439+
auto_import_modules = [pkg for pkg in installed_packages_list if pkg not in self.DO_NOT_PRELOAD_MODULES]
440+
return auto_import_modules
441+
420442
def __str__(self):
421443
return str(self.uri)
422444

@@ -546,12 +568,11 @@ def jedi_script(self, position=None, use_document_path=False):
546568
env_vars = os.environ.copy()
547569
env_vars.pop("PYTHONPATH", None)
548570

549-
environment = self.get_enviroment(environment_path, env_vars=env_vars)
550571
sys_path = self.sys_path(
551572
environment_path, env_vars, prioritize_extra_paths, extra_paths
552573
)
553574

554-
project_path = self._workspace.root_path
575+
import __main__
555576

556577
# Extend sys_path with document's path if requested
557578
if use_document_path:
@@ -560,15 +581,14 @@ def jedi_script(self, position=None, use_document_path=False):
560581
kwargs = {
561582
"code": self.source,
562583
"path": self.path,
563-
"environment": environment if environment_path else None,
564-
"project": jedi.Project(path=project_path, sys_path=sys_path),
584+
'namespaces': [__main__.__dict__]
565585
}
566586

567587
if position:
568588
# Deprecated by Jedi to use in Script() constructor
569589
kwargs += _utils.position_to_jedi_linecolumn(self, position)
570590

571-
return jedi.Script(**kwargs)
591+
return jedi.Interpreter(**kwargs)
572592

573593
def get_enviroment(self, environment_path=None, env_vars=None):
574594
# TODO(gatesn): #339 - make better use of jedi environments, they seem pretty powerful

0 commit comments

Comments
 (0)