feat: complete BLOCK_LIST/ADDR_LIST keywords and space comments
Add keyword-driven completions for BLOCK_LIST and ADDR_LIST in the LSP server. - While typing a prefix of these keywords the server returns the keyword(s) as incomplete snippet suggestions that trigger a re-request. - Once the keyword is typed the server replaces it with a full list of the corresponding loaded block templates or addresses (quoted), and sets each item's filter_text to include the keyword so further typing narrows results. - Integrate this flow into on_completion and factor the symbol-list logic into a helper that returns either incomplete keyword suggestions or the completed symbol list. Also implement NxFormatter.format_comment to ensure a single space after a leading '#' for comments that don't already start with whitespace, while leaving sequences of '#' (separators), shebangs, and already-spaced comments unchanged. This affects both standalone and inline comments. Add/rename tests to cover the new completions and comment-spacing behavior.
This commit is contained in:
@@ -85,6 +85,7 @@ from tools.tcl_command_completion import (
|
||||
TCL_COMMAND_ITEMS,
|
||||
TCL_COMMAND_NAMES,
|
||||
DynamicCompletionKind,
|
||||
line_prefix_at_position,
|
||||
path_completion_items,
|
||||
tcl_argument_completion,
|
||||
)
|
||||
@@ -279,6 +280,66 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams):
|
||||
lsp.CompletionOptions(trigger_characters=["$", " ", "-", "(", ","]),
|
||||
)
|
||||
def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
result = _on_completion(params)
|
||||
doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
symbol_list = _symbol_list_completion(LSP_SERVER.get_lines(doc), params.position)
|
||||
if symbol_list is not None and symbol_list.is_incomplete:
|
||||
# Re-request while typing so the full list appears at BLOCK_LIST/ADDR_LIST.
|
||||
result.items = [*symbol_list.items, *result.items]
|
||||
result.is_incomplete = True
|
||||
return result
|
||||
|
||||
|
||||
# Keywords that expand to all loaded .def names: keyword -> (items, description).
|
||||
SYMBOL_LIST_KEYWORDS = {
|
||||
"BLOCK_LIST": (lambda: LSP_SERVER.block_template_items(), "block templates"),
|
||||
"ADDR_LIST": (lambda: LSP_SERVER.address_items(), "addresses"),
|
||||
}
|
||||
_WORD_BEFORE_CURSOR_RE = re.compile(r"(?<![$\w])[A-Za-z_]+$")
|
||||
|
||||
|
||||
def _symbol_list_completion(source_lines, position: lsp.Position) -> lsp.CompletionList | None:
|
||||
"""Complete BLOCK_LIST/ADDR_LIST to all loaded block templates/addresses.
|
||||
|
||||
While the typed word is a prefix of a keyword, an incomplete list with the
|
||||
keyword itself is returned. Once the keyword is typed, the complete list of
|
||||
quoted names replaces it.
|
||||
"""
|
||||
line_prefix = line_prefix_at_position(source_lines, position)
|
||||
match = _WORD_BEFORE_CURSOR_RE.search(line_prefix or "")
|
||||
if match is None:
|
||||
return None
|
||||
word = match.group()
|
||||
for keyword, (symbol_items, _) in SYMBOL_LIST_KEYWORDS.items():
|
||||
if not word.startswith(keyword):
|
||||
continue
|
||||
items = []
|
||||
ranked = ranked_completion_items(((0, item) for item in symbol_items()), CompletionContext.GENERAL)
|
||||
for item in ranked:
|
||||
item = _quoted_item(item, source_lines, position, word)
|
||||
# Keep every name visible for the typed keyword; text after it narrows.
|
||||
item.filter_text = f"{keyword}{item.label}"
|
||||
items.append(item)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
keywords = [
|
||||
lsp.CompletionItem(
|
||||
label=keyword,
|
||||
kind=lsp.CompletionItemKind.Snippet,
|
||||
detail=f"Show all loaded {description}",
|
||||
insert_text=keyword,
|
||||
sort_text=f"000:{keyword.casefold()}",
|
||||
command=lsp.Command(title=f"Show {description}", command="editor.action.triggerSuggest"),
|
||||
)
|
||||
for keyword, (_, description) in SYMBOL_LIST_KEYWORDS.items()
|
||||
if keyword.startswith(word)
|
||||
]
|
||||
if keywords:
|
||||
return lsp.CompletionList(is_incomplete=True, items=keywords)
|
||||
return None
|
||||
|
||||
|
||||
def _on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
position = params.position
|
||||
source_lines = LSP_SERVER.get_lines(doc)
|
||||
@@ -302,6 +363,10 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
return lsp.CompletionList(is_incomplete=False, items=array_items)
|
||||
context = completion_context(source_lines, position)
|
||||
|
||||
symbol_list = _symbol_list_completion(source_lines, position)
|
||||
if symbol_list is not None and symbol_list.is_incomplete is False:
|
||||
return symbol_list
|
||||
|
||||
# Variable completion wins inside command arguments. Otherwise prefer the
|
||||
# narrow command grammar when the cursor is at a known subcommand/option.
|
||||
argument_completion = None
|
||||
|
||||
Reference in New Issue
Block a user