From 5d23df8da76939d31a68f464f0ccc935b0ecd921 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Thu, 24 Sep 2026 07:53:15 +0200 Subject: [PATCH] feat(server/lsp_server): include .def file changes and offer quoted def/variable completions --- server/src/lsp_server.py | 67 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py index 9b7f440..a321e08 100644 --- a/server/src/lsp_server.py +++ b/server/src/lsp_server.py @@ -219,21 +219,29 @@ def did_rename_files(params: lsp.RenameFilesParams) -> None: def did_change_watched_files(params: lsp.DidChangeWatchedFilesParams) -> None: """Keep indexes for closed Tcl files synchronized with disk changes.""" for change in params.changes: - if pathlib.Path(uris.to_fs_path(change.uri)).suffix.lower() == ".psc": + suffix = pathlib.Path(uris.to_fs_path(change.uri)).suffix.lower() + if suffix == ".psc": _refresh_psc_index() continue + if suffix == ".def": + LSP_SERVER.refresh_def_symbols(_workspace_roots(), report=log_warning) + continue if change.type == lsp.FileChangeType.Deleted: LSP_SERVER.remove_file_state(change.uri) else: _index_tcl_file_from_disk(change.uri) -def _refresh_psc_index(): +def _workspace_roots() -> list[pathlib.Path]: folders = LSP_SERVER.workspace.folders roots = [pathlib.Path(uris.to_fs_path(uri)) for uri in folders] if not roots and LSP_SERVER.workspace.root_path: roots = [pathlib.Path(LSP_SERVER.workspace.root_path)] - LSP_SERVER.refresh_psc_scripts(roots, report=log_warning) + return roots + + +def _refresh_psc_index(): + LSP_SERVER.refresh_psc_scripts(_workspace_roots(), report=log_warning) @LSP_SERVER.feature( @@ -393,6 +401,30 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList: items = [*ranked_completion_items(static_candidates, CompletionContext.GENERAL), *items] return lsp.CompletionList(is_incomplete=False, items=items) + def_symbol_items = { + DynamicCompletionKind.BLOCK_TEMPLATE: LSP_SERVER.block_template_items, + DynamicCompletionKind.ADDRESS: LSP_SERVER.address_items, + DynamicCompletionKind.VALUE: list, + }.get(argument_completion.dynamic_kind) + if def_symbol_items is not None: + # MOM_do_template, MOM_force, ... take a .def name, a fixed value (e.g. + # Always|Once|Off) or a variable holding one. + def_symbols = [*static_candidates, *((0, item) for item in def_symbol_items())] + variable_candidates = list(candidates) + variable_candidates.extend((300, item) for item in standard_items.nx_variables) + variables = [] + for item in ranked_completion_items(variable_candidates, CompletionContext.VARIABLE): + # Without a typed "$" the variable must be substituted. + item.insert_text = f"${item.label}" + item.filter_text = item.label + item.text_edit = None + variables.append(item) + quoted = [ + _quoted_item(item, source_lines, position, argument_completion.active_prefix) + for item in ranked_completion_items(def_symbols, CompletionContext.GENERAL) + ] + return lsp.CompletionList(is_incomplete=False, items=[*quoted, *variables]) + if argument_completion.dynamic_kind == DynamicCompletionKind.PROCEDURE: procedure_kinds = { lsp.CompletionItemKind.Constructor, @@ -436,6 +468,35 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList: return lsp.CompletionList(is_incomplete=False, items=items) +def _quoted_item( + item: lsp.CompletionItem, + source_lines, + position: lsp.Position, + active_prefix: str, +) -> lsp.CompletionItem: + """Insert ``item`` as a quoted word, replacing quotes the user already typed.""" + line = source_lines[position.line] if position.line < len(source_lines) else "" + utf16 = line.encode("utf-16-le") + start = position.character - len(active_prefix.encode("utf-16-le")) // 2 + end = position.character + opened = start > 0 and utf16[(start - 1) * 2 : start * 2].decode("utf-16-le") == '"' + if opened: + start -= 1 + # Also replace a closing quote the editor inserted automatically. + if utf16[end * 2 : (end + 1) * 2].decode("utf-16-le") == '"': + end += 1 + item.text_edit = lsp.TextEdit( + range=lsp.Range( + start=lsp.Position(line=position.line, character=start), + end=lsp.Position(line=position.line, character=end), + ), + new_text=f'"{item.label}"', + ) + item.insert_text = None + item.filter_text = f'"{item.label}' if opened else item.label + return item + + @LSP_SERVER.feature( lsp.TEXT_DOCUMENT_SIGNATURE_HELP, lsp.SignatureHelpOptions(