Skip to content

Commit

Permalink
Support Jedi 0.17, remove support for older Jedi
Browse files Browse the repository at this point in the history
  • Loading branch information
pappasam committed Apr 20, 2020
1 parent c52532c commit fcd95fc
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 178 deletions.
85 changes: 37 additions & 48 deletions jedi_language_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
HOVER,
REFERENCES,
RENAME,
WORKSPACE_SYMBOL,
)
from pygls.server import LanguageServer
from pygls.types import (
Expand All @@ -31,26 +30,25 @@
TextDocumentPositionParams,
TextEdit,
WorkspaceEdit,
WorkspaceSymbolParams,
)

from .server_utils import (
get_jedi_document_names,
get_jedi_script,
get_jedi_workspace_names,
get_location_from_definition,
get_symbol_information_from_definition,
get_location_from_name,
get_symbol_information_from_name,
)
from .type_map import get_lsp_completion_type

SERVER = LanguageServer()


@SERVER.feature(COMPLETION, triggerCharacters=["."])
def lsp_completion(server: LanguageServer, params: CompletionParams = None):
def lsp_completion(server: LanguageServer, params: CompletionParams):
"""Returns completion items."""
script = get_jedi_script(server, params)
jedi_completions = script.completions()
script = get_jedi_script(server, params.textDocument.uri)
jedi_completions = script.complete(
line=params.position.line + 1, column=params.position.character,
)
return CompletionList(
is_incomplete=False,
items=[
Expand All @@ -71,27 +69,28 @@ def lsp_definition(
server: LanguageServer, params: TextDocumentPositionParams
) -> List[Location]:
"""Support Goto Definition"""
script = get_jedi_script(server, params)
definitions = script.goto_assignments(
follow_imports=True, follow_builtin_imports=True
script = get_jedi_script(server, params.textDocument.uri)
names = script.goto(
line=params.position.line + 1,
column=params.position.character,
follow_imports=True,
follow_builtin_imports=True,
)
return [
get_location_from_definition(definition) for definition in definitions
]
return [get_location_from_name(name) for name in names]


@SERVER.feature(HOVER)
def lsp_hover(
server: LanguageServer, params: TextDocumentPositionParams
) -> Hover:
"""Support the hover feature"""
script = get_jedi_script(server, params)
definitions = script.goto_definitions()
script = get_jedi_script(server, params.textDocument.uri)
names = script.infer(
line=params.position.line + 1, column=params.position.character,
)
return Hover(
contents=(
definitions[0].docstring()
if definitions
else "No docstring definition found."
names[0].docstring() if names else "No docstring definition found."
)
)

Expand All @@ -101,29 +100,29 @@ def lsp_references(
server: LanguageServer, params: TextDocumentPositionParams
) -> List[Location]:
"""Obtain all references to document"""
script = get_jedi_script(server, params)
script = get_jedi_script(server, params.textDocument.uri)
try:
definitions = script.usages()
names = script.get_references(
line=params.position.line + 1, column=params.position.character,
)
except Exception: # pylint: disable=broad-except
return []
return [
get_location_from_definition(definition) for definition in definitions
]
return [get_location_from_name(name) for name in names]


@SERVER.feature(RENAME)
def lsp_rename(
server: LanguageServer, params: RenameParams
) -> Optional[WorkspaceEdit]:
"""Rename a symbol across a workspace"""
script = get_jedi_script(server, params)
script = get_jedi_script(server, params.textDocument.uri)
try:
definitions = script.usages()
names = script.get_references(
line=params.position.line + 1, column=params.position.character,
)
except Exception: # pylint: disable=broad-except
return None
locations = [
get_location_from_definition(definition) for definition in definitions
]
locations = [get_location_from_name(name) for name in names]
if not locations:
return None
changes = {} # type: Dict[str, List[TextEdit]]
Expand All @@ -141,21 +140,11 @@ def lsp_document_symbol(
server: LanguageServer, params: DocumentSymbolParams
) -> List[SymbolInformation]:
"""Document Python document symbols"""
jedi_names = get_jedi_document_names(server, params)
return [
get_symbol_information_from_definition(definition)
for definition in jedi_names
]


@SERVER.feature(WORKSPACE_SYMBOL)
def lsp_workspace_symbol(
server: LanguageServer,
params: WorkspaceSymbolParams, # pylint: disable=unused-argument
) -> List[SymbolInformation]:
"""Document Python workspace symbols"""
jedi_names = get_jedi_workspace_names(server)
return [
get_symbol_information_from_definition(definition)
for definition in jedi_names
]
script = get_jedi_script(server, params.textDocument.uri)
try:
names = script.get_names(
line=params.position.line + 1, column=params.position.character,
)
except Exception: # pylint: disable=broad-except
return []
return [get_symbol_information_from_name(name) for name in names]
149 changes: 28 additions & 121 deletions jedi_language_server/server_utils.py
Original file line number Diff line number Diff line change
@@ -1,132 +1,44 @@
"""Utility functions used by the language server"""

import itertools
import os
import subprocess
from typing import List, Optional, Union
from typing import Optional

import jedi.api
from jedi import Script
from jedi.api.classes import Definition
from jedi import Project, Script
from jedi.api.classes import Name
from jedi.api.environment import get_cached_default_environment
from pygls.server import LanguageServer, Workspace
from pygls.types import (
DocumentSymbolParams,
Location,
Position,
Range,
RenameParams,
SymbolInformation,
TextDocumentItem,
TextDocumentPositionParams,
)
from pygls.server import LanguageServer
from pygls.types import Location, Position, Range, SymbolInformation
from pygls.uris import from_fs_path

from .type_map import get_lsp_symbol_type


def get_jedi_script(
server: LanguageServer,
params: Union[TextDocumentPositionParams, RenameParams],
) -> Script:
def get_jedi_script(server: LanguageServer, doc_uri: str) -> Script:
"""Simplifies getting jedi Script
:param doc_uri: the uri for the LSP text document. Obtained through
params.textDocument.uri
NOTE:
* jedi is 1-indexed for lines and 0-indexed for columns
* LSP is 0-indexed for lines and 0-indexed for columns
* Therefore, add 1 to LSP's request for the line
"""
workspace = server.workspace
text_doc = workspace.get_document(params.textDocument.uri)
return Script(
source=text_doc.source,
path=text_doc.path,
line=params.position.line + 1,
column=params.position.character,
environment=get_cached_default_environment(),
text_doc = workspace.get_document(doc_uri)
project = Project(
path=workspace.root_path,
smart_sys_path=True,
load_unsafe_extensions=False,
)


def get_jedi_document_names(
server: LanguageServer, params: DocumentSymbolParams,
) -> List[Definition]:
"""Get a list of document names from Jedi"""
workspace = server.workspace
text_doc = workspace.get_document(params.textDocument.uri)
return jedi.api.names(
source=text_doc.source,
return Script(
code=text_doc.source,
path=text_doc.path,
all_scopes=True,
definitions=True,
references=False,
project=project,
environment=get_cached_default_environment(),
)


class WorkspaceDocuments:
"""A class to manage one-time gathering of workspace documents"""

# pylint: disable=too-few-public-methods

def __init__(self) -> None:
self.gathered_names = False

def gather_workspace_names(self, workspace: Workspace) -> None:
"""Collect the workspace names"""
if not self.gathered_names:
git_path = os.path.join(workspace.root_path, ".git")
git_command = "git --git-dir=" + git_path + " ls-files --full-name"
try:
git_output = subprocess.check_output(git_command, shell=True)
except subprocess.CalledProcessError:
self.gathered_names = True
return
project_paths = git_output.decode("utf-8").splitlines()
for doc_path in project_paths:
if os.path.splitext(doc_path)[1] == ".py":
full_doc_path = os.path.join(workspace.root_path, doc_path)
doc_uri = from_fs_path(full_doc_path)
with open(full_doc_path) as infile:
text = infile.read()
workspace.put_document(
TextDocumentItem(
uri=doc_uri,
language_id="python",
version=0,
text=text,
)
)
self.gathered_names = True


_WORKSPACE_DOCUMENTS = WorkspaceDocuments()


def get_jedi_workspace_names(server: LanguageServer) -> List[Definition]:
"""Get a list of workspace names from Jedi"""
workspace = server.workspace
_WORKSPACE_DOCUMENTS.gather_workspace_names(workspace)
definitions_by_document = (
jedi.api.names(
source=text_doc.source,
path=text_doc.path,
all_scopes=True,
definitions=True,
references=False,
environment=get_cached_default_environment(),
)
for text_doc in workspace.documents.values()
if text_doc and os.path.splitext(text_doc.path)[1] == ".py"
)
return list(
itertools.chain.from_iterable(
definitions if definitions else []
for definitions in definitions_by_document
)
)


def get_location_from_definition(definition: Definition) -> Location:
def get_location_from_name(name: Name) -> Location:
"""Get LSP location from Jedi definition
NOTE:
Expand All @@ -135,38 +47,33 @@ def get_location_from_definition(definition: Definition) -> Location:
* Therefore, subtract 1 from Jedi's definition line
"""
return Location(
uri=from_fs_path(definition.module_path),
uri=from_fs_path(name.module_path),
range=Range(
start=Position(
line=definition.line - 1, character=definition.column
),
start=Position(line=name.line - 1, character=name.column),
end=Position(
line=definition.line - 1,
character=definition.column + len(definition.name),
line=name.line - 1, character=name.column + len(name.name),
),
),
)


def get_symbol_information_from_definition(
definition: Definition,
) -> SymbolInformation:
def get_symbol_information_from_name(name: Name,) -> SymbolInformation:
"""Get LSP SymbolInformation from Jedi definition"""
return SymbolInformation(
name=definition.name,
kind=get_lsp_symbol_type(definition.type),
location=get_location_from_definition(definition),
container_name=get_jedi_parent_name(definition),
name=name.name,
kind=get_lsp_symbol_type(name.type),
location=get_location_from_name(name),
container_name=get_jedi_parent_name(name),
)


def get_jedi_parent_name(definition: Definition) -> Optional[str]:
def get_jedi_parent_name(name: Name) -> Optional[str]:
"""Retrieve the parent name from Jedi
Error prone Jedi calls are wrapped in try/except to avoid
"""
try:
parent = definition.parent()
parent = name.parent()
except Exception: # pylint: disable=broad-except
return None
return parent.name if parent and parent.parent() else None
14 changes: 7 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fcd95fc

Please sign in to comment.