From f38106305315c20d015d7d0b2f7de27315df63a9 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Thu, 1 Jul 2021 22:46:20 +0200 Subject: [PATCH 1/2] Initialize workspaces from the initialize request Don't ignore workspace folders provided through the initialize request expecting that the client sends the workspace/didChangeWorkspaceFolders notification. Expecting that notification is a bug since it only needs to be sent when folders change, relative to the ones reported in initialize. Fixes #48 --- .gitignore | 3 +++ README.md | 4 ++-- pylsp/python_lsp.py | 16 +++++++++++++++- test/fixtures.py | 28 ++++++++++++++++++++++++++++ test/test_workspace.py | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ac609b32..4f47251d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ __pycache__/ *.py[cod] *$py.class +# Mypy cache +.mypy_cache/ + # IntelliJ *.iml *.ipr diff --git a/README.md b/README.md index 80fcfe03..ba1c27f8 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ To enable pydocstyle for linting docstrings add the following setting in your LS To run the test suite: -``` -pip install .[test] && pytest +```sh +pip install '.[test]' && pytest ``` ## License diff --git a/pylsp/python_lsp.py b/pylsp/python_lsp.py index f15ccd66..009feaf7 100644 --- a/pylsp/python_lsp.py +++ b/pylsp/python_lsp.py @@ -199,7 +199,8 @@ def capabilities(self): log.info('Server capabilities: %s', server_capabilities) return server_capabilities - def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, **_kwargs): + def m_initialize(self, processId=None, rootUri=None, rootPath=None, + initializationOptions=None, workspaceFolders=None, **_kwargs): log.debug('Language server initialized with %s %s %s %s', processId, rootUri, rootPath, initializationOptions) if rootUri is None: rootUri = uris.from_fs_path(rootPath) if rootPath is not None else '' @@ -210,6 +211,19 @@ def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializati processId, _kwargs.get('capabilities', {})) self.workspace = Workspace(rootUri, self._endpoint, self.config) self.workspaces[rootUri] = self.workspace + if workspaceFolders: + for folder in workspaceFolders: + uri = folder['uri'] + if uri == rootUri: + # Already created + continue + workspace_config = config.Config( + uri, self.config._init_opts, + self.config._process_id, self.config._capabilities) + workspace_config.update(self.config._settings) + self.workspaces[uri] = Workspace( + uri, self._endpoint, workspace_config) + self._dispatchers = self._hook('pylsp_dispatchers') self._hook('pylsp_initialize') diff --git a/test/fixtures.py b/test/fixtures.py index c50c1c10..cce5b328 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -34,6 +34,34 @@ def pylsp(tmpdir): return ls +@pytest.fixture +def pylsp_w_workspace_folders(tmpdir): + """ Return an initialized python LS """ + ls = PythonLSPServer(StringIO, StringIO) + + folder1 = tmpdir.mkdir('folder1') + folder2 = tmpdir.mkdir('folder2') + + ls.m_initialize( + processId=1, + rootUri=uris.from_fs_path(str(folder1)), + initializationOptions={}, + workspaceFolders=[ + { + 'uri': uris.from_fs_path(str(folder1)), + 'name': 'folder1' + }, + { + 'uri': uris.from_fs_path(str(folder2)), + 'name': 'folder2' + } + ] + ) + + workspace_folders = [folder1, folder2] + return (ls, workspace_folders) + + @pytest.fixture def workspace(tmpdir): """Return a workspace.""" diff --git a/test/test_workspace.py b/test/test_workspace.py index e8851e05..a008e7eb 100644 --- a/test/test_workspace.py +++ b/test/test_workspace.py @@ -69,6 +69,45 @@ def test_root_project_with_no_setup_py(pylsp): assert workspace_root in test_doc.sys_path() +def test_multiple_workspaces_from_initialize(pylsp_w_workspace_folders): + pylsp, workspace_folders = pylsp_w_workspace_folders + + assert len(pylsp.workspaces) == 2 + + folders_uris = [uris.from_fs_path(str(folder)) for folder in workspace_folders] + + for folder_uri in folders_uris: + assert folder_uri in pylsp.workspaces + + assert folders_uris[0] == pylsp.root_uri + + # Create file in the first workspace folder. + file1 = workspace_folders[0].join('file1.py') + file1.write('import os') + msg1 = { + 'uri': path_as_uri(str(file1)), + 'version': 1, + 'text': 'import os' + } + + pylsp.m_text_document__did_open(textDocument=msg1) + assert msg1['uri'] in pylsp.workspace._docs + assert msg1['uri'] in pylsp.workspaces[folders_uris[0]]._docs + + # Create file in the second workspace folder. + file2 = workspace_folders[1].join('file2.py') + file2.write('import sys') + msg2 = { + 'uri': path_as_uri(str(file2)), + 'version': 1, + 'text': 'import sys' + } + + pylsp.m_text_document__did_open(textDocument=msg2) + assert msg2['uri'] not in pylsp.workspace._docs + assert msg2['uri'] in pylsp.workspaces[folders_uris[1]]._docs + + def test_multiple_workspaces(tmpdir, pylsp): workspace1_dir = tmpdir.mkdir('workspace1') workspace2_dir = tmpdir.mkdir('workspace2') From 7cd691feb4ed9aec36d62b0d6914bc618d20514d Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Sat, 10 Jul 2021 17:57:51 +0200 Subject: [PATCH 2/2] fix reporter pylint issues --- pylsp/plugins/jedi_completion.py | 2 +- pylsp/python_lsp.py | 6 ++---- test/test_utils.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pylsp/plugins/jedi_completion.py b/pylsp/plugins/jedi_completion.py index 5b07f56b..a04d2101 100644 --- a/pylsp/plugins/jedi_completion.py +++ b/pylsp/plugins/jedi_completion.py @@ -114,7 +114,7 @@ def use_snippets(document, position): break if '(' in act_lines[-1].strip(): last_character = ')' - code = '\n'.join(act_lines).split(';')[-1].strip() + last_character + code = '\n'.join(act_lines).rsplit(';', maxsplit=1)[-1].strip() + last_character tokens = parso.parse(code) expr_type = tokens.children[0].type return (expr_type not in _IMPORTS and diff --git a/pylsp/python_lsp.py b/pylsp/python_lsp.py index 009feaf7..6717d272 100644 --- a/pylsp/python_lsp.py +++ b/pylsp/python_lsp.py @@ -377,8 +377,7 @@ def m_text_document__signature_help(self, textDocument=None, position=None, **_k def m_workspace__did_change_configuration(self, settings=None): self.config.update((settings or {}).get('pylsp', {})) - for workspace_uri in self.workspaces: - workspace = self.workspaces[workspace_uri] + for _, workspace in self.workspaces.items(): workspace.update_config(settings) for doc_uri in workspace.documents: self.lint(doc_uri, is_saved=False) @@ -447,8 +446,7 @@ def m_workspace__did_change_watched_files(self, changes=None, **_kwargs): # Only externally changed python files and lint configs may result in changed diagnostics. return - for workspace_uri in self.workspaces: - workspace = self.workspaces[workspace_uri] + for _, workspace in self.workspaces.items(): for doc_uri in workspace.documents: # Changes in doc_uri are already handled by m_text_document__did_save if doc_uri not in changed_py_files: diff --git a/test/test_utils.py b/test/test_utils.py index 61e4f1f5..cdc41506 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -3,7 +3,7 @@ import time -import unittest.mock as mock +from unittest import mock from flaky import flaky from pylsp import _utils