diff --git a/server/src/common/load_data.py b/server/src/common/load_data.py index bd8643f..67837ff 100644 --- a/server/src/common/load_data.py +++ b/server/src/common/load_data.py @@ -17,10 +17,15 @@ KIND_MAP = { class StandardCompletionItems: def __init__(self): + self.__json_data: dict = self.__load_json() self.__tcl_keyword_list = self.__load_tcl_keyword() self.__nx_procs = self.__load_nx_procs() self.__nx_variables = self.__load_nx_variables() + @property + def json_data(self): + return self.__json_data + @property def tcl_keyword_list(self): return self.__tcl_keyword_list @@ -33,11 +38,15 @@ class StandardCompletionItems: def nx_variables(self): return self.__nx_variables - def __load_tcl_keyword(self) -> list[lsp.CompletionItem]: + def __load_json(self) -> dict: with open( pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r" ) as f: data = json.load(f) + return data + + def __load_tcl_keyword(self) -> list[lsp.CompletionItem]: + data = self.json_data items = [] for i in data.get("tcl", []): @@ -50,10 +59,7 @@ class StandardCompletionItems: return items def __load_nx_variables(self): - with open( - pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r" - ) as f: - data = json.load(f) + data = self.json_data items = [] for var in data.get("mom_variables", []): label = var.get("label", "") @@ -85,11 +91,7 @@ class StandardCompletionItems: return items def __load_nx_procs(self) -> list[lsp.CompletionItem]: - with open( - pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r" - ) as f: - data = json.load(f) - + data = self.json_data items = [] for proc in data.get("MOM_procs", []): label = proc.get("label", "") diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py index c49b3ea..f835f95 100644 --- a/server/src/lsp_server.py +++ b/server/src/lsp_server.py @@ -13,6 +13,7 @@ import sys import sysconfig import traceback from typing import Any, Optional, Sequence +import re # ********************************************************** @@ -106,6 +107,72 @@ def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]: return lsp.CompletionList(is_incomplete=False, items=items) +@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_HOVER) +def hover(params: lsp.HoverParams) -> lsp.Hover: + pos = params.position + document_uri = params.text_document.uri + document = LSP_SERVER.workspace.get_text_document(document_uri) + col = params.position.character + + try: + line = document.lines[pos.line] + except IndexError: + return None + + for m in re.finditer(r"\b\w+\b", line): + if m.start() <= col <= m.end(): + token = m.group(0) + break + else: + return None + + command = token + data = standard_items.json_data + + all_items = data.get("MOM_procs", []) + data.get("mom_variables", []) + + match = next((item for item in all_items if item["label"] == command), None) + if not match: + return None + + if not match.get("kind") == "function": + return None + + label = match.get("label", "") + + parameters = match.get("parameters", []) + param_lines = ( + "\n".join(f"- `{p['name']}`: {p['desc']}" for p in parameters) or "_None_" + ) + + example_data = match.get("example", []) + example_md = "\n".join(f"{line}" for line in example_data) + + returns_data = match.get("returns", ["None"]) + returns_md = "\n".join(f"- {line}" for line in returns_data) + + doc_md = f"""\ +### 📘 {label} + +**Purpose** +{match.get("description", "No description available.")} + +**Format** +`{match.get("format", label)}` + +**Parameters** +{param_lines} + +**Return value** +{returns_md} + +**Example** +```tcl +{example_md}""" + + return lsp.Hover(lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=doc_md)) + + # ********************************************************** # Linting features end here # ********************************************************** diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark index 0097c91..e69de29 100644 --- a/server/src/parser/tcl.lark +++ b/server/src/parser/tcl.lark @@ -1,61 +0,0 @@ -// Lark grammar for Tcl scripts with proc definitions and optional args - -%import common.CNAME -> IDENTIFIER -%import common.INT -> INT -%import common.FLOAT -> FLOAT -%import common.ESCAPED_STRING -> STRING_LITERAL -%import common.WS_INLINE -> WS_INLINE -%import common.NEWLINE -> NEWLINE - -// Ignore only spaces, tabs; treat newlines as separators -%ignore WS_INLINE -// Tcl comments -%ignore COMMENT - -start: script -script: statement* - -statement: command (";" | NEWLINE)? - -command: set_command - | proc_command - | r_break - | r_continue - -proc_command: "proc" IDENTIFIER "{" argument_list "}" body_block - -argument_list: [argument (argument)*] - -argument: IDENTIFIER -> required_arg - | "{" IDENTIFIER (value)? "}" -> optional_arg - - - -value: STRING_LITERAL - | FLOAT - | INT - | IDENTIFIER - | list - -list: LBRACE [value (WS_INLINE value)*] RBRACE - -body_block: LBRACE body_content RBRACE -> body -body_content: ( /[^{}]+/ | body_block )* // allow nested braces - -set_command: "set" IDENTIFIER index? value -// Index: parentheses with comma-separated items -index: "(" index_item ("," index_item)* ")" -index_item: INT -> index_int - | STRING_VAR -> index_string - | "$" IDENTIFIER index? -> nested_index - - -r_break: "break" -r_continue: "continue" - -LBRACE: "{" -RBRACE: "}" - -STRING_VAR: /(?:[^"\\]|\\.)+/ -COMMENT: /#[^\r\n]*/ -