lsp: Implement "go to definition"

This commit is contained in:
James Westman 2023-07-25 18:40:05 -05:00
parent e087aeb44f
commit 62f74178f7
7 changed files with 64 additions and 4 deletions

View file

@ -22,7 +22,7 @@ from collections import ChainMap, defaultdict
from functools import cached_property
from .errors import *
from .lsp_utils import DocumentSymbol, SemanticToken
from .lsp_utils import DocumentSymbol, SemanticToken, LocationLink
from .tokenizer import Range
TType = T.TypeVar("TType")
@ -185,9 +185,8 @@ class AstNode:
return getattr(self, name)
for child in self.children:
if child.group.start <= idx < child.group.end:
docs = child.get_docs(idx)
if docs is not None:
if idx in child.range:
if docs := child.get_docs(idx):
return docs
return None
@ -196,6 +195,13 @@ class AstNode:
for child in self.children:
yield from child.get_semantic_tokens()
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
for child in self.children:
if idx in child.range:
if ref := child.get_reference(idx):
return ref
return None
@property
def document_symbol(self) -> T.Optional[DocumentSymbol]:
return None

View file

@ -50,6 +50,7 @@ from ..lsp_utils import (
Completion,
CompletionItemKind,
DocumentSymbol,
LocationLink,
SemanticToken,
SemanticTokenType,
SymbolKind,

View file

@ -339,6 +339,16 @@ class IdentLiteral(AstNode):
token = self.group.tokens["value"]
yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)
def get_reference(self, _idx: int) -> T.Optional[LocationLink]:
ref = self.context[ScopeCtx].objects.get(self.ident)
if ref is None and self.root.is_legacy_template(self.ident):
ref = self.root.template
if ref:
return LocationLink(self.range, ref.range, ref.ranges["id"])
else:
return None
class Literal(AstNode):
grammar = AnyOf(

View file

@ -204,6 +204,7 @@ class LanguageServer:
"codeActionProvider": {},
"hoverProvider": True,
"documentSymbolProvider": True,
"definitionProvider": True,
},
"serverInfo": {
"name": "Blueprint",
@ -389,6 +390,21 @@ class LanguageServer:
self._send_response(id, [to_json(symbol) for symbol in symbols])
@command("textDocument/definition")
def definition(self, id, params):
open_file = self._open_files[params["textDocument"]["uri"]]
idx = utils.pos_to_idx(
params["position"]["line"], params["position"]["character"], open_file.text
)
definition = open_file.ast.get_reference(idx)
if definition is None:
self._send_response(id, None)
else:
self._send_response(
id,
definition.to_json(open_file.uri),
)
def _send_file_updates(self, open_file: OpenFile):
self._send_notification(
"textDocument/publishDiagnostics",

View file

@ -169,3 +169,18 @@ class DocumentSymbol:
selection_range: Range
detail: T.Optional[str] = None
children: T.List["DocumentSymbol"] = field(default_factory=list)
@dataclass
class LocationLink:
origin_selection_range: Range
target_range: Range
target_selection_range: Range
def to_json(self, target_uri: str):
return {
"originSelectionRange": self.origin_selection_range.to_json(),
"targetUri": target_uri,
"targetRange": self.target_range.to_json(),
"targetSelectionRange": self.target_selection_range.to_json(),
}

View file

@ -81,6 +81,8 @@ class ParseGroup:
self.keys[key] = val
self.tokens[key] = token
if token:
self.set_range(key, token.range)
def set_range(self, key: str, range: Range):
assert_true(key not in self.ranges)

View file

@ -24,6 +24,7 @@ from dataclasses import dataclass
from enum import Enum
from .errors import CompileError, CompilerBugError
from . import utils
class TokenType(Enum):
@ -127,3 +128,12 @@ class Range:
if b is None:
return a
return Range(min(a.start, b.start), max(a.end, b.end), a.original_text)
def __contains__(self, other: T.Union[int, "Range"]) -> bool:
if isinstance(other, int):
return self.start <= other <= self.end
else:
return self.start <= other.start and self.end >= other.end
def to_json(self):
return utils.idxs_to_range(self.start, self.end, self.original_text)