Compare commits
3
Commits
2026.9.600
...
2026.9.700
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1eccf90f4 | ||
|
|
da9091fa38 | ||
|
|
439c700226 |
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "nx-post-support",
|
"name": "nx-post-support",
|
||||||
"displayName": "NX Postprocessor Support",
|
"displayName": "NX Postprocessor Support",
|
||||||
"description": "VS Code extension for NX CAM postprocessor development with language support and remote Tcl debugging for CDL, TCL, and DEF files",
|
"description": "VS Code extension for NX CAM postprocessor development with language support and remote Tcl debugging for CDL, TCL, and DEF files",
|
||||||
"version": "2026.9.501",
|
"version": "2026.9.600",
|
||||||
"publisher": "Christoph",
|
"publisher": "Christoph",
|
||||||
"icon": "images/nx-1.png",
|
"icon": "images/nx-1.png",
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ from tools.tcl_command_completion import (
|
|||||||
TCL_COMMAND_ITEMS,
|
TCL_COMMAND_ITEMS,
|
||||||
TCL_COMMAND_NAMES,
|
TCL_COMMAND_NAMES,
|
||||||
DynamicCompletionKind,
|
DynamicCompletionKind,
|
||||||
|
line_prefix_at_position,
|
||||||
path_completion_items,
|
path_completion_items,
|
||||||
tcl_argument_completion,
|
tcl_argument_completion,
|
||||||
)
|
)
|
||||||
@@ -279,6 +280,66 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams):
|
|||||||
lsp.CompletionOptions(trigger_characters=["$", " ", "-", "(", ","]),
|
lsp.CompletionOptions(trigger_characters=["$", " ", "-", "(", ","]),
|
||||||
)
|
)
|
||||||
def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
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)
|
doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||||
position = params.position
|
position = params.position
|
||||||
source_lines = LSP_SERVER.get_lines(doc)
|
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)
|
return lsp.CompletionList(is_incomplete=False, items=array_items)
|
||||||
context = completion_context(source_lines, position)
|
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
|
# Variable completion wins inside command arguments. Otherwise prefer the
|
||||||
# narrow command grammar when the cursor is at a known subcommand/option.
|
# narrow command grammar when the cursor is at a known subcommand/option.
|
||||||
argument_completion = None
|
argument_completion = None
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ class NxFormatter(BaseFormatter):
|
|||||||
while leaving all other formatting behavior unchanged.
|
while leaving all other formatting behavior unchanged.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def format_comment(self, comment) -> List[str]: # type: ignore[override]
|
||||||
|
# "#Comment" -> "# Comment". Leave "#", "##..." separators, "#!" and
|
||||||
|
# comments that already start with whitespace untouched.
|
||||||
|
value = comment.value
|
||||||
|
if value and not value[0].isspace() and value[0] not in "#!":
|
||||||
|
value = " " + value
|
||||||
|
return [f"#{value}"]
|
||||||
|
|
||||||
def format_braced_expression(self, expr) -> List[str]: # type: ignore[override]
|
def format_braced_expression(self, expr) -> List[str]: # type: ignore[override]
|
||||||
# This method mirrors BaseFormatter.format_braced_expression but inserts
|
# This method mirrors BaseFormatter.format_braced_expression but inserts
|
||||||
# a line continuation (" \") between continuation lines similar to
|
# a line continuation (" \") between continuation lines similar to
|
||||||
|
|||||||
@@ -198,3 +198,63 @@ def test_typed_quotes_are_replaced_not_doubled(tmp_path, monkeypatch):
|
|||||||
)
|
)
|
||||||
).items
|
).items
|
||||||
assert _edit(items, "SPOS") == ('"SPOS"', (22, 26))
|
assert _edit(items, "SPOS") == ('"SPOS"', (22, 26))
|
||||||
|
|
||||||
|
|
||||||
|
def test_block_list_prefix_offers_keyword_and_stays_incomplete(tmp_path, monkeypatch):
|
||||||
|
server, _ = _project(tmp_path, monkeypatch)
|
||||||
|
uri = (tmp_path / "caller.tcl").as_uri()
|
||||||
|
source = "MOM_do_template BLOCK_L"
|
||||||
|
server.workspace.put_text_document(
|
||||||
|
lsp.TextDocumentItem(uri=uri, language_id="tcl", version=next(_versions), text=source)
|
||||||
|
)
|
||||||
|
result = lsp_server.on_completion(
|
||||||
|
lsp.CompletionParams(
|
||||||
|
text_document=lsp.TextDocumentIdentifier(uri=uri),
|
||||||
|
position=lsp.Position(line=0, character=len(source)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert result.is_incomplete
|
||||||
|
keyword = result.items[0]
|
||||||
|
assert keyword.label == "BLOCK_LIST"
|
||||||
|
assert keyword.command.command == "editor.action.triggerSuggest"
|
||||||
|
|
||||||
|
|
||||||
|
def test_block_list_shows_all_templates_quoted(tmp_path, monkeypatch):
|
||||||
|
server, _ = _project(tmp_path, monkeypatch)
|
||||||
|
items = _complete(server, tmp_path, "set a 1\n BLOCK_LIST")
|
||||||
|
assert [item.label for item in items] == ["external_subprogram", "steady_rest"]
|
||||||
|
assert _edit(items, "steady_rest") == ('"steady_rest"', (4, 14))
|
||||||
|
assert all(item.filter_text.startswith("BLOCK_LIST") for item in items)
|
||||||
|
|
||||||
|
|
||||||
|
def test_block_list_ignores_variables_and_other_words(tmp_path, monkeypatch):
|
||||||
|
server, _ = _project(tmp_path, monkeypatch)
|
||||||
|
for source in ("set x $BLOCK_LIST", "set x MY_BLOCK_LIST", "set x steady"):
|
||||||
|
labels = [item.label for item in _complete(server, tmp_path, source)]
|
||||||
|
assert "BLOCK_LIST" not in labels
|
||||||
|
|
||||||
|
|
||||||
|
def test_addr_list_shows_all_addresses_quoted(tmp_path, monkeypatch):
|
||||||
|
server, _ = _project(tmp_path, monkeypatch)
|
||||||
|
items = _complete(server, tmp_path, "MOM_force Once ADDR_LIST")
|
||||||
|
assert [item.label for item in items] == ["SPOS", "X"]
|
||||||
|
assert _edit(items, "SPOS") == ('"SPOS"', (15, 24))
|
||||||
|
assert all(item.filter_text.startswith("ADDR_LIST") for item in items)
|
||||||
|
|
||||||
|
|
||||||
|
def test_addr_list_prefix_offers_keyword(tmp_path, monkeypatch):
|
||||||
|
server, _ = _project(tmp_path, monkeypatch)
|
||||||
|
uri = (tmp_path / "caller.tcl").as_uri()
|
||||||
|
source = "ADDR"
|
||||||
|
server.workspace.put_text_document(
|
||||||
|
lsp.TextDocumentItem(uri=uri, language_id="tcl", version=next(_versions), text=source)
|
||||||
|
)
|
||||||
|
result = lsp_server.on_completion(
|
||||||
|
lsp.CompletionParams(
|
||||||
|
text_document=lsp.TextDocumentIdentifier(uri=uri),
|
||||||
|
position=lsp.Position(line=0, character=len(source)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert result.is_incomplete
|
||||||
|
assert result.items[0].label == "ADDR_LIST"
|
||||||
|
assert "BLOCK_LIST" not in {item.label for item in result.items}
|
||||||
|
|||||||
+26
-1
@@ -1,4 +1,4 @@
|
|||||||
"""Formatting of uplevel bodies."""
|
"""Formatting with NxFormatter."""
|
||||||
|
|
||||||
from tclint.format import FormatterOpts
|
from tclint.format import FormatterOpts
|
||||||
from tools.formatter import NxFormatter
|
from tools.formatter import NxFormatter
|
||||||
@@ -31,3 +31,28 @@ def test_uplevel_without_level_and_with_variable_level():
|
|||||||
assert _format(source) == (
|
assert _format(source) == (
|
||||||
"uplevel {\n\tset x 1\n}\nuplevel $lvl {\n\tset y 2\n}\nuplevel set z 3\n"
|
"uplevel {\n\tset x 1\n}\nuplevel $lvl {\n\tset y 2\n}\nuplevel set z 3\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_gets_space_after_hash():
|
||||||
|
source = (
|
||||||
|
"#Comment\n"
|
||||||
|
"# already spaced\n"
|
||||||
|
"#\tTabbed\n"
|
||||||
|
"#\n"
|
||||||
|
"##########\n"
|
||||||
|
"set x 1 ;#inline\n"
|
||||||
|
"proc a {} {\n"
|
||||||
|
"\t#nested\n"
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
assert _format(source) == (
|
||||||
|
"# Comment\n"
|
||||||
|
"# already spaced\n"
|
||||||
|
"#\tTabbed\n"
|
||||||
|
"#\n"
|
||||||
|
"##########\n"
|
||||||
|
"set x 1 ;# inline\n"
|
||||||
|
"proc a {} {\n"
|
||||||
|
"\t# nested\n"
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user