Skip to content
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

Implement support for workspace/symbol #564

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
7 changes: 4 additions & 3 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ This server can be configured using the `workspace/didChangeConfiguration` metho
| `pylsp.plugins.jedi_hover.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_references.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_signature_help.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_symbols.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_symbols.all_scopes` | `boolean` | If True lists the names of all scopes instead of only the module namespace. | `true` |
| `pylsp.plugins.jedi_symbols.include_import_symbols` | `boolean` | If True includes symbols imported from other libraries. | `true` |
| `pylsp.plugins.jedi_document_symbols.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.jedi_document_symbols.all_scopes` | `boolean` | If True lists the names of all scopes instead of only the module namespace. | `true` |
| `pylsp.plugins.jedi_document_symbols.include_import_symbols` | `boolean` | If True includes symbols imported from other libraries. | `true` |
| `pylsp.plugins.jedi_workspace_symbols.enabled` | `boolean` | If True includes workspace symbols. | `true` |
| `pylsp.plugins.mccabe.enabled` | `boolean` | Enable or disable the plugin. | `true` |
| `pylsp.plugins.mccabe.threshold` | `integer` | The minimum threshold that triggers warnings about cyclomatic complexity. | `15` |
| `pylsp.plugins.preload.enabled` | `boolean` | Enable or disable the plugin. | `true` |
Expand Down
11 changes: 8 additions & 3 deletions pylsp/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,26 @@
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_symbols.enabled": {
"pylsp.plugins.jedi_document_symbols.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the plugin."
},
"pylsp.plugins.jedi_symbols.all_scopes": {
"pylsp.plugins.jedi_document_symbols.all_scopes": {
"type": "boolean",
"default": true,
"description": "If True lists the names of all scopes instead of only the module namespace."
},
"pylsp.plugins.jedi_symbols.include_import_symbols": {
"pylsp.plugins.jedi_document_symbols.include_import_symbols": {
"type": "boolean",
"default": true,
"description": "If True includes symbols imported from other libraries."
},
"pylsp.plugins.jedi_workspace_symbols.enabled": {
"type": "boolean",
"default": true,
"description": "If True includes workspace symbols."
},
"pylsp.plugins.mccabe.enabled": {
"type": "boolean",
"default": true,
Expand Down
5 changes: 5 additions & 0 deletions pylsp/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def pylsp_execute_command(config, workspace, command, arguments):
pass


@hookspec
def pylsp_workspace_symbol(workspace, query):
pass


@hookspec
def pylsp_experimental_capabilities(config, workspace):
pass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@hookimpl
def pylsp_document_symbols(config, document):
symbols_settings = config.plugin_settings("jedi_symbols")
symbols_settings = config.plugin_settings("jedi_document_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)
Expand Down
42 changes: 42 additions & 0 deletions pylsp/plugins/workspace_symbol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

import logging

from pylsp import hookimpl
from pylsp.lsp import SymbolKind

log = logging.getLogger(__name__)


@hookimpl
def pylsp_workspace_symbol(workspace, query):
if not query or not workspace:
return []

return [
_jedi_name_to_symbol(jedi_name)
for jedi_name in workspace.complete_search(query)
]


def _jedi_name_to_symbol(jedi_name):
return {
"name": jedi_name.name,
"kind": _jedi_type_to_symbol_kind(jedi_name.type),
"location": {
"uri": "file://" + str(jedi_name.module_path),
"range": {
"start": {"line": jedi_name.line - 1, "character": jedi_name.column},
"end": {"line": jedi_name.line - 1, "character": jedi_name.column},
},
},
}


def _jedi_type_to_symbol_kind(jedi_type):
return {
"module": SymbolKind.Module,
"class": SymbolKind.Class,
"function": SymbolKind.Function,
}.get(jedi_type, SymbolKind.Variable)
9 changes: 9 additions & 0 deletions pylsp/python_lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ def capabilities(self):
"workspaceFolders": {"supported": True, "changeNotifications": True}
},
"experimental": merge(self._hook("pylsp_experimental_capabilities")),
"workspaceSymbolProvider": True,
}
log.info("Server capabilities: %s", server_capabilities)
return server_capabilities
Expand Down Expand Up @@ -433,6 +434,11 @@ def highlight(self, doc_uri, position):
or None
)

def workspace_symbol(self, query):
response = self._hook("pylsp_workspace_symbol", query=query)
log.debug("Workspace symbol hook returned: %s", response)
niilohlin marked this conversation as resolved.
Show resolved Hide resolved
return flatten(response)

def hover(self, doc_uri, position):
return self._hook("pylsp_hover", doc_uri, position=position) or {"contents": ""}

Expand Down Expand Up @@ -767,6 +773,9 @@ def m_text_document__hover(self, textDocument=None, position=None, **_kwargs):
def m_text_document__document_symbol(self, textDocument=None, **_kwargs):
return self.document_symbols(textDocument["uri"])

def m_workspace__symbol(self, textDocument=None, **_kwargs):
return self.workspace_symbol(_kwargs["query"])

def m_text_document__formatting(self, textDocument=None, options=None, **_kwargs):
return self.format_document(textDocument["uri"], options)

Expand Down
40 changes: 40 additions & 0 deletions pylsp/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,46 @@ def root_path(self):
def root_uri(self):
return self._root_uri

def complete_search(self, query):
project = self.jedi_project()
return project.complete_search(query)

def jedi_project(self, use_document_path=False):
extra_paths = []
environment_path = None
env_vars = None

if self._config:
jedi_settings = self._config.plugin_settings(
"jedi", document_path=self.root_path
)
jedi.settings.auto_import_modules = jedi_settings.get(
"auto_import_modules", DEFAULT_AUTO_IMPORT_MODULES
)
environment_path = jedi_settings.get("environment")
# Jedi itself cannot deal with homedir-relative paths.
# On systems, where it is expected, expand the home directory.
if environment_path and os.name != "nt":
environment_path = os.path.expanduser(environment_path)

extra_paths = jedi_settings.get("extra_paths") or []
env_vars = jedi_settings.get("env_vars")

# Drop PYTHONPATH from env_vars before creating the environment because that makes
# Jedi throw an error.
if env_vars is None:
env_vars = os.environ.copy()
env_vars.pop("PYTHONPATH", None)

sys_path = extra_paths # self.sys_path(environment_path, env_vars=env_vars) + extra_paths
project_path = self.root_path

# Extend sys_path with document's path if requested
if use_document_path:
sys_path += [os.path.normpath(os.path.dirname(self.path))]

return jedi.Project(path=project_path, sys_path=sys_path)

def is_local(self):
return (self._root_uri_scheme in ["", "file"]) and os.path.exists(
self._root_path
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ jedi_highlight = "pylsp.plugins.highlight"
jedi_references = "pylsp.plugins.references"
jedi_rename = "pylsp.plugins.jedi_rename"
jedi_signature_help = "pylsp.plugins.signature"
jedi_symbols = "pylsp.plugins.symbols"
jedi_document_symbols = "pylsp.plugins.document_symbols"
jedi_workspace_symbol = "pylsp.plugins.workspace_symbol"
mccabe = "pylsp.plugins.mccabe_lint"
preload = "pylsp.plugins.preload_imports"
pycodestyle = "pylsp.plugins.pycodestyle_lint"
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import os
from io import StringIO
from test.test_utils import CALL_TIMEOUT_IN_SECONDS, ClientServerPair
from unittest.mock import MagicMock

import pytest
Expand All @@ -15,6 +14,7 @@
from pylsp.config.config import Config
from pylsp.python_lsp import PythonLSPServer
from pylsp.workspace import Document, Workspace
from test.test_utils import CALL_TIMEOUT_IN_SECONDS, ClientServerPair

DOC_URI = uris.from_fs_path(__file__)
DOC = """import sys
Expand Down
4 changes: 2 additions & 2 deletions test/plugins/test_autoimport.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Copyright 2022- Python Language Server Contributors.

from test.test_notebook_document import wait_for_condition
from test.test_utils import send_initialize_request, send_notebook_did_open
from typing import Any, Dict, List
from unittest.mock import Mock, patch

Expand All @@ -22,6 +20,8 @@
pylsp_completions as pylsp_autoimport_completions,
)
from pylsp.workspace import Workspace
from test.test_notebook_document import wait_for_condition
from test.test_utils import send_initialize_request, send_notebook_did_open

DOC_URI = uris.from_fs_path(__file__)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from pylsp import uris
from pylsp.lsp import SymbolKind
from pylsp.plugins.symbols import pylsp_document_symbols
from pylsp.plugins.document_symbols import pylsp_document_symbols
from pylsp.workspace import Document

PY2 = sys.version[0] == "2"
Expand Down Expand Up @@ -50,7 +50,7 @@ def sym(name):

def test_symbols(config, workspace):
doc = Document(DOC_URI, workspace, DOC)
config.update({"plugins": {"jedi_symbols": {"all_scopes": False}}})
config.update({"plugins": {"jedi_document_symbols": {"all_scopes": False}}})
symbols = pylsp_document_symbols(config, doc)

# All four symbols (import sys, a, B, main)
Expand Down
55 changes: 55 additions & 0 deletions test/plugins/test_workspace_symbols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

import os
import sys

import pytest

from pylsp import uris
from pylsp.plugins.workspace_symbol import pylsp_workspace_symbol

PY2 = sys.version[0] == "2"
LINUX = sys.platform.startswith("linux")
CI = os.environ.get("CI")
DOC_URI = uris.from_fs_path(__file__)


DOC1_NAME = "file1.py"
DOC2_NAME = "file2.py"

DOC1 = """class Test1():
pass
"""

DOC2 = """from file1 import Test1

try:
Test1()
except UnicodeError:
pass
"""


@pytest.fixture
def tmp_workspace(temp_workspace_factory):
return temp_workspace_factory(
{
DOC1_NAME: DOC1,
DOC2_NAME: DOC2,
}
)


def test_symbols_empty_query(tmp_workspace):
symbols = pylsp_workspace_symbol(tmp_workspace, "")

assert len(symbols) == 0


def test_symbols_nonempty_query(tmp_workspace):
symbols = pylsp_workspace_symbol(tmp_workspace, "Test")

assert len(symbols) == 1
assert symbols[0]["name"] == "Test1"
assert symbols[0]["kind"] == 5 # Class
4 changes: 2 additions & 2 deletions test/test_configuration.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Copyright 2021- Python Language Server Contributors.

from test.test_notebook_document import wait_for_condition
from test.test_utils import send_initialize_request
from unittest.mock import patch

import pytest

from pylsp import IS_WIN
from test.test_notebook_document import wait_for_condition
from test.test_utils import send_initialize_request

INITIALIZATION_OPTIONS = {
"pylsp": {
Expand Down
3 changes: 1 addition & 2 deletions test/test_document.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

from test.fixtures import DOC, DOC_URI

from pylsp.workspace import Document
from test.fixtures import DOC, DOC_URI


def test_document_props(doc):
Expand Down
3 changes: 2 additions & 1 deletion test/test_language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import os
import sys
import time
from test.test_utils import ClientServerPair, send_initialize_request

import pytest
from flaky import flaky
from pylsp_jsonrpc.exceptions import JsonRpcMethodNotFound

from test.test_utils import ClientServerPair, send_initialize_request

RUNNING_IN_CI = bool(os.environ.get("CI"))

CALL_TIMEOUT_IN_SECONDS = 10
Expand Down
10 changes: 5 additions & 5 deletions test/test_notebook_document.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Copyright 2021- Python Language Server Contributors.

import time
from test.test_utils import (
CALL_TIMEOUT_IN_SECONDS,
send_initialize_request,
send_notebook_did_open,
)
from unittest.mock import call, patch

import pytest

from pylsp import IS_WIN
from pylsp.lsp import NotebookCellKind
from pylsp.workspace import Notebook
from test.test_utils import (
CALL_TIMEOUT_IN_SECONDS,
send_initialize_request,
send_notebook_did_open,
)


def wait_for_condition(condition, timeout=CALL_TIMEOUT_IN_SECONDS):
Expand Down
3 changes: 1 addition & 2 deletions test/test_uris.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

from test import unix_only, windows_only

import pytest

from pylsp import uris
from test import unix_only, windows_only


@unix_only
Expand Down