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 functools import cached_property
|
||||||
|
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .lsp_utils import DocumentSymbol, SemanticToken
|
from .lsp_utils import DocumentSymbol, SemanticToken, LocationLink
|
||||||
from .tokenizer import Range
|
from .tokenizer import Range
|
||||||
|
|
||||||
TType = T.TypeVar("TType")
|
TType = T.TypeVar("TType")
|
||||||
|
@ -185,9 +185,8 @@ class AstNode:
|
||||||
return getattr(self, name)
|
return getattr(self, name)
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
if child.group.start <= idx < child.group.end:
|
if idx in child.range:
|
||||||
docs = child.get_docs(idx)
|
if docs := child.get_docs(idx):
|
||||||
if docs is not None:
|
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -196,6 +195,13 @@ class AstNode:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child.get_semantic_tokens()
|
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
|
@property
|
||||||
def document_symbol(self) -> T.Optional[DocumentSymbol]:
|
def document_symbol(self) -> T.Optional[DocumentSymbol]:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -50,6 +50,7 @@ from ..lsp_utils import (
|
||||||
Completion,
|
Completion,
|
||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
DocumentSymbol,
|
DocumentSymbol,
|
||||||
|
LocationLink,
|
||||||
SemanticToken,
|
SemanticToken,
|
||||||
SemanticTokenType,
|
SemanticTokenType,
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
|
|
|
@ -339,6 +339,16 @@ class IdentLiteral(AstNode):
|
||||||
token = self.group.tokens["value"]
|
token = self.group.tokens["value"]
|
||||||
yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)
|
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):
|
class Literal(AstNode):
|
||||||
grammar = AnyOf(
|
grammar = AnyOf(
|
||||||
|
|
|
@ -204,6 +204,7 @@ class LanguageServer:
|
||||||
"codeActionProvider": {},
|
"codeActionProvider": {},
|
||||||
"hoverProvider": True,
|
"hoverProvider": True,
|
||||||
"documentSymbolProvider": True,
|
"documentSymbolProvider": True,
|
||||||
|
"definitionProvider": True,
|
||||||
},
|
},
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "Blueprint",
|
"name": "Blueprint",
|
||||||
|
@ -389,6 +390,21 @@ class LanguageServer:
|
||||||
|
|
||||||
self._send_response(id, [to_json(symbol) for symbol in symbols])
|
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):
|
def _send_file_updates(self, open_file: OpenFile):
|
||||||
self._send_notification(
|
self._send_notification(
|
||||||
"textDocument/publishDiagnostics",
|
"textDocument/publishDiagnostics",
|
||||||
|
|
|
@ -169,3 +169,18 @@ class DocumentSymbol:
|
||||||
selection_range: Range
|
selection_range: Range
|
||||||
detail: T.Optional[str] = None
|
detail: T.Optional[str] = None
|
||||||
children: T.List["DocumentSymbol"] = field(default_factory=list)
|
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.keys[key] = val
|
||||||
self.tokens[key] = token
|
self.tokens[key] = token
|
||||||
|
if token:
|
||||||
|
self.set_range(key, token.range)
|
||||||
|
|
||||||
def set_range(self, key: str, range: Range):
|
def set_range(self, key: str, range: Range):
|
||||||
assert_true(key not in self.ranges)
|
assert_true(key not in self.ranges)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from .errors import CompileError, CompilerBugError
|
from .errors import CompileError, CompilerBugError
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
class TokenType(Enum):
|
class TokenType(Enum):
|
||||||
|
@ -127,3 +128,12 @@ class Range:
|
||||||
if b is None:
|
if b is None:
|
||||||
return a
|
return a
|
||||||
return Range(min(a.start, b.start), max(a.end, b.end), a.original_text)
|
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