Skip to content

Port custom changes from old fork of python language server #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,5 @@ ENV/
# Special files
.DS_Store
*.temp

.venv
8 changes: 5 additions & 3 deletions pylsp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ def main() -> None:
_configure_logger(args.verbose, args.log_config, args.log_file)

if args.tcp:
start_tcp_lang_server(
args.host, args.port, args.check_parent_process, PythonLSPServer
)
while True:
start_tcp_lang_server(
args.host, args.port, args.check_parent_process, PythonLSPServer
)
time.sleep(0.500)
elif args.ws:
start_ws_lang_server(args.port, args.check_parent_process, PythonLSPServer)
else:
Expand Down
5 changes: 5 additions & 0 deletions pylsp/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def pylsp_completions(config, workspace, document, position, ignored_names) -> N
pass


@hookspec
def pylsp_completion_detail(config, item) -> None:
pass


@hookspec(firstresult=True)
def pylsp_completion_item_resolve(config, workspace, document, completion_item) -> None:
pass
Expand Down
19 changes: 13 additions & 6 deletions pylsp/plugins/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def pylsp_hover(config, document, position):
supported_markup_kinds = hover_capabilities.get("contentFormat", ["markdown"])
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)

doc = _utils.format_docstring(definition.docstring(raw=True), preferred_markup_kind)["value"]

# Find first exact matching signature
signature = next(
(
Expand All @@ -40,11 +42,16 @@ def pylsp_hover(config, document, position):
"",
)

contents = []
if signature:
contents.append({
'language': 'python',
'value': signature,
})

if doc:
contents.append(doc)

return {
"contents": _utils.format_docstring(
# raw docstring returns only doc, without signature
definition.docstring(raw=True),
preferred_markup_kind,
signatures=[signature] if signature else None,
)
"contents": contents or ''
}
19 changes: 19 additions & 0 deletions pylsp/plugins/jedi_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"statement": lsp.CompletionItemKind.Variable,
}

COMPLETION_CACHE = {}

# Types of parso nodes for which snippet is not included in the completion
_IMPORTS = ("import_name", "import_from")

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

return ready_completions or None

@hookimpl
def pylsp_completion_detail(config, item):
d = COMPLETION_CACHE.get(item)
if d:
completion = {
'label': '', #_label(d),
'kind': _TYPE_MAP.get(d.type),
'detail': '', #_detail(d),
'documentation': _utils.format_docstring(d.docstring()),
'sortText': '', #_sort_text(d),
'insertText': d.name
}
return completion
else:
log.info('Completion missing')
return None

@hookimpl
def pylsp_completion_item_resolve(config, completion_item, document):
Expand Down Expand Up @@ -229,6 +247,7 @@ def _format_completion(
resolve_label_or_snippet=False,
snippet_support=False,
):
COMPLETION_CACHE[d.name] = d
completion = {
"label": _label(d, resolve_label_or_snippet),
"kind": _TYPE_MAP.get(d.type),
Expand Down
51 changes: 5 additions & 46 deletions pylsp/plugins/preload_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,11 @@
log = logging.getLogger(__name__)

MODULES = [
"OpenGL",
"PIL",
"array",
"audioop",
"binascii",
"cPickle",
"cStringIO",
"cmath",
"collections",
"datetime",
"errno",
"exceptions",
"gc",
"imageop",
"imp",
"itertools",
"marshal",
"math",
"matplotlib",
"mmap",
"mpmath",
"msvcrt",
"networkx",
"nose",
"nt",
"numpy",
"operator",
"os",
"os.path",
"pandas",
"parser",
"rgbimg",
"scipy",
"signal",
"skimage",
"sklearn",
"statsmodels",
"strop",
"sympy",
"sys",
"thread",
"time",
"wx",
"xxsubtype",
"zipimport",
"zlib",
"numpy", "tensorflow", "sklearn", "array", "binascii", "cmath", "collections",
"datetime", "errno", "exceptions", "gc", "imageop", "imp", "itertools",
"marshal", "math", "matplotlib", "mmap", "mpmath", "msvcrt", "networkx", "nose", "nt",
"operator", "os", "os.path", "pandas", "parser", "scipy", "signal",
"skimage", "statsmodels", "strop", "sympy", "sys", "thread", "time", "wx", "zlib"
]


Expand Down
4 changes: 2 additions & 2 deletions pylsp/plugins/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def pylsp_signature_help(config, document, position):
"label": function_sig,
"documentation": _utils.format_docstring(
s.docstring(raw=True), markup_kind=preferred_markup_kind
),
)["value"],
}

# If there are params, add those
Expand All @@ -55,7 +55,7 @@ def pylsp_signature_help(config, document, position):
"label": p.name,
"documentation": _utils.format_docstring(
_param_docs(docstring, p.name), markup_kind=preferred_markup_kind
),
)["value"],
}
for p in s.params
]
Expand Down
44 changes: 34 additions & 10 deletions pylsp/python_lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import uuid
from functools import partial
from typing import Any, Dict, List
from hashlib import sha256

try:
import ujson as json
Expand Down Expand Up @@ -43,17 +44,36 @@ def setup(self) -> None:
self.delegate = self.DELEGATE_CLASS(self.rfile, self.wfile)

def handle(self) -> None:
try:
self.delegate.start()
except OSError as e:
if os.name == "nt":
# Catch and pass on ConnectionResetError when parent process
# dies
if isinstance(e, WindowsError) and e.winerror == 10054:
pass
self.auth(self.delegate.start)

self.SHUTDOWN_CALL()

def auth(self, cb):
token = ''
if "JUPYTER_TOKEN" in os.environ:
token = os.environ["JUPYTER_TOKEN"]
else:
log.warn('! Missing jupyter token !')

data = self.rfile.readline()
try:
auth_req = json.loads(data.decode().split('\n')[0])
except:
log.error('Error parsing authentication message')
auth_error_msg = { 'msg': 'AUTH_ERROR' }
self.wfile.write(json.dumps(auth_error_msg).encode())
return

hashed_token = sha256(token.encode()).hexdigest()
if auth_req.get('token') == hashed_token:
auth_success_msg = { 'msg': 'AUTH_SUCCESS' }
self.wfile.write(json.dumps(auth_success_msg).encode())
cb()
else:
log.info('Failed to authenticate: invalid credentials')
auth_invalid_msg = { 'msg': 'AUTH_INVALID_CRED' }
self.wfile.write(json.dumps(auth_invalid_msg).encode())


def start_tcp_lang_server(bind_addr, port, check_parent_process, handler_class) -> None:
if not issubclass(handler_class, PythonLSPServer):
Expand Down Expand Up @@ -401,7 +421,11 @@ def completions(self, doc_uri, position):
completions = self._hook(
"pylsp_completions", doc_uri, position=position, ignored_names=ignored_names
)
return {"isIncomplete": False, "items": flatten(completions)}
return {"isIncomplete": True, "items": flatten(completions)}

def completion_detail(self, item):
detail = self._hook('pylsp_completion_detail', item=item)
return detail

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

def m_completion_item__resolve(self, **completionItem):
return self.completion_item_resolve(completionItem)
return self.completion_detail(completionItem.get('label'))

def m_notebook_document__did_open(
self, notebookDocument=None, cellTextDocuments=None, **_kwargs
Expand Down
30 changes: 25 additions & 5 deletions pylsp/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from contextlib import contextmanager
from threading import RLock
from typing import Callable, Generator, List, Optional
import importlib.metadata

import jedi

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


class Document:
DO_NOT_PRELOAD_MODULES = ['attrs', 'backcall', 'bleach', 'certifi', 'chardet', 'cycler', 'decorator', 'defusedxml',
'docopt', 'entrypoints', 'idna', 'importlib-metadata', 'ipykernel', 'ipython-genutils',
'ipython', 'ipywidgets', 'jedi', 'jinja2', 'joblib', 'jsonschema', 'jupyter-client',
'jupyter-core', 'markupsafe', 'mistune', 'nbconvert', 'nbformat', 'notebook', 'packaging',
'pandocfilters', 'parso', 'pexpect', 'pickleshare', 'pip', 'pipreqs', 'pluggy',
'prometheus-client', 'prompt-toolkit', 'ptyprocess', 'pygments', 'pyparsing',
'pyrsistent', 'python-dateutil', 'python-jsonrpc-server', 'python-language-server',
'pytz', 'pyzmq', 'send2trash', 'setuptools', 'six', 'terminado', 'testpath',
'threadpoolctl', 'tornado', 'traitlets', 'ujson', 'wcwidth', 'webencodings', 'wheel',
'widgetsnbextension', 'yarg', 'zipp']


def __init__(
self,
uri,
Expand All @@ -417,6 +430,15 @@ def __init__(
self._rope_project_builder = rope_project_builder
self._lock = RLock()

jedi.settings.cache_directory = '.cache/jedi/'
jedi.settings.use_filesystem_cache = True
jedi.settings.auto_import_modules = self._get_auto_import_modules()

def _get_auto_import_modules(self):
installed_packages_list = [dist.metadata['Name'] for dist in importlib.metadata.distributions()]
auto_import_modules = [pkg for pkg in installed_packages_list if pkg not in self.DO_NOT_PRELOAD_MODULES]
return auto_import_modules

def __str__(self):
return str(self.uri)

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

environment = self.get_enviroment(environment_path, env_vars=env_vars)
sys_path = self.sys_path(
environment_path, env_vars, prioritize_extra_paths, extra_paths
)

project_path = self._workspace.root_path
import __main__

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

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

return jedi.Script(**kwargs)
return jedi.Interpreter(**kwargs)

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