From 82e13cf7ce5f0f821b149888c8d1b818e8ceb111 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Tue, 8 Sep 2026 21:16:58 +0200 Subject: [PATCH] fix(completion): scan nested commands in unfinished braced args Make the completion parser inspect nested command segments when an unfinished braced argument is the active context, instead of treating the entire braced body as opaque. This resolves cases where inner commands (e.g. command substitutions) were ignored and improves suggestion accuracy. Tests were added to cover nested commands inside braced conditions and to ensure closed braced arguments do not change context. Also add Linux-specific launch and task configurations to IDE settings to simplify local extension development and debugging. - Recursively scan inner context when a braced body is unfinished - Add tests for nested commands and closed-brace behavior - Add Linux launch/task entries for easier development --- .vscode/launch.json | 26 +++++++++++++++++++ .vscode/tasks.json | 7 +++++ server/src/tools/tcl_command_completion.py | 8 +++++- .../python_tests/test_completion_context.py | 23 ++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fd0075e..03d17d7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,19 @@ ], "cwd": "${env:TEMP}/nx-post-support-vscode-debug", "outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"], + "linux": { + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "${workspaceFolder}", + "${workspaceFolder}/test/test.tcl" + ], + "cwd": "${workspaceFolder}", + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "resolveSourceMapLocations": [ + "${workspaceFolder}/dist/**/*.js", + "!**/node_modules/**" + ] + }, "sourceMaps": true, "resolveSourceMapLocations": [ "${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js", @@ -50,6 +63,19 @@ ], "cwd": "${env:TEMP}/nx-post-support-vscode-debug", "outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"], + "linux": { + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "${workspaceFolder}", + "${workspaceFolder}/test/test.tcl" + ], + "cwd": "${workspaceFolder}", + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "resolveSourceMapLocations": [ + "${workspaceFolder}/dist/**/*.js", + "!**/node_modules/**" + ] + }, "sourceMaps": true, "resolveSourceMapLocations": [ "${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9fc060b..fb7f391 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -15,6 +15,13 @@ "${workspaceFolder}", "${env:TEMP}\\nx-post-support-vscode-debug" ], + "linux": { + "command": "npm", + "args": ["run", "compile:debug"], + "options": { + "cwd": "${workspaceFolder}" + } + }, "problemMatcher": [], "presentation": { "reveal": "always", diff --git a/server/src/tools/tcl_command_completion.py b/server/src/tools/tcl_command_completion.py index 06f099e..b96caeb 100644 --- a/server/src/tools/tcl_command_completion.py +++ b/server/src/tools/tcl_command_completion.py @@ -1009,7 +1009,13 @@ def _current_command_segment(line_prefix: str) -> str: starts.append(brace_starts[-1] + 1) if bracket_starts: starts.append(bracket_starts[-1] + 1) - return line_prefix[max(starts) :] + start = max(starts) + if brace_starts and start == brace_starts[-1] + 1: + # An unfinished braced body or expression can itself contain commands + # (for example, ``if {[string compare ...``). Scan that inner context + # independently, while keeping completed braced arguments opaque. + return _current_command_segment(line_prefix[start:]) + return line_prefix[start:] def _tokenize_command_segment(segment: str) -> list[str]: diff --git a/server/tests/python_tests/test_completion_context.py b/server/tests/python_tests/test_completion_context.py index f0c7346..b6a8d30 100644 --- a/server/tests/python_tests/test_completion_context.py +++ b/server/tests/python_tests/test_completion_context.py @@ -204,6 +204,29 @@ def test_string_completion_handles_nested_commands_and_is_values(): } +def test_string_completion_inside_braced_conditions_and_bodies(): + for prefix in ("if {", "while {", "proc example {} { if {"): + subcommands = _argument_completion_labels(prefix + "[string ") + assert subcommands is not None + assert {"compare", "equal", "is"} <= subcommands + assert _argument_completion_labels(prefix + "[string compare -") == { + "-length", "-nocase" + } + assert _argument_completion_labels( + prefix + "[string compare -nocase " + ) == {"-length"} + + +def test_closed_braced_arguments_do_not_change_completion_context(): + assert _argument_completion_labels("puts {[string compare }") is None + assert _argument_completion_labels( + "if {[string equal a b]} {string compare " + ) == {"-length", "-nocase"} + assert _argument_completion_labels( + "if {[string equal a b] && [string is integer " + ) == {"-failindex", "-strict"} + + def test_dict_array_namespace_file_and_info_subcommands(): dict_items = _argument_completion_labels("dict ") assert dict_items is not None