mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
lsp: Implement "go to definition"
This commit is contained in:
parent
e087aeb44f
commit
62f74178f7
7 changed files with 64 additions and 4 deletions
|
@ -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
|
||||
|
|
|
@ -50,6 +50,7 @@ from ..lsp_utils import (
|
|||
Completion,
|
||||
CompletionItemKind,
|
||||
DocumentSymbol,
|
||||
LocationLink,
|
||||
SemanticToken,
|
||||
SemanticTokenType,
|
||||
SymbolKind,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue