diff --git a/blueprintcompiler/lsp.py b/blueprintcompiler/lsp.py index 6e43e3b..5dc7a04 100644 --- a/blueprintcompiler/lsp.py +++ b/blueprintcompiler/lsp.py @@ -166,6 +166,7 @@ class LanguageServer: "completionProvider": {}, "codeActionProvider": {}, "hoverProvider": True, + "declarationProvider": True, }, "serverInfo": { "name": "Blueprint", @@ -209,6 +210,57 @@ class LanguageServer: else: self._send_response(id, None) + @command("textDocument/declaration") + def gotoDeclaration(self, id, params): + """Triggered when `Ctrl+LClick` over anything that references a widget + object by ID (e.g. the param in signal handler, the value token of a + property with type Widget, the id of the source widget in a property + binding, ...)""" + open_file = self._open_files[params["textDocument"]["uri"]] + span_start = utils.pos_to_idx(params["position"]["line"], params["position"]["character"], open_file.text) + obj_id = str(tokenizer._tokenize(open_file.text[span_start:])) + + full_range = { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 0 }, + } + id_range = None # { + # "start": { "line": 0, "character": 0 }, + # "end": { "line": 0, "character": 0 }, + # } + for obj in open_file.ast.objects_by_id(): + if str(obj.tokens["id"]) == obj_id: + tokens = list(obj.get_semantic_tokens()) + + # Full range starts at the position of the first token of the widget + position = utils.idx_to_pos(tokens[0].start, open_file.text) + full_range["start"]["line"] = position[0] + full_range["start"]["character"] = position[1] + + # Goes through all the tokens (starting from the 2nd) of the widget object until ID token + for token in tokens[1:]: + if str(token) == obj_id: + position = utils.idx_to_pos(token.start, open_file.text) + id_range = { + "start": { "line": position[0], "character": position[1] }, + "end": { "line": position[0], "character": position[1] + len(obj_id) }, + } + full_range["end"] = id_range["end"] + break + break + + if id_range: + # result: LocationLink array + self._send_response(id, [{ + "targetUri": params["textDocument"]["uri"], + # contains from widget class to the closing curly bracket `}` + "targetRange": full_range, + # only contains widget's id + "targetSelectionRange": id_range + }]) + else: + self._send_response(id, None) + @command("textDocument/completion") def completion(self, id, params): open_file = self._open_files[params["textDocument"]["uri"]]