feat(tcl): add dynamic argument completion and snippets
The changes add dynamic, semantic argument completion for Tcl commands and snippet support. - Introduces DynamicCompletionKind, TclArgumentCompletion, and dynamic rules for Tcl to provide variable, procedure, namespace, and path suggestions. - Adds snippet-backed commands and arguments for Tcl blocks and paths. - Refactors tcl_argument_completion and updates the LSP to use dynamic path, variable, and namespace completions with snippet kinds.
This commit is contained in:
+105
-6
@@ -77,7 +77,9 @@ from tools.signature_help import build_signature_help
|
||||
from tools.tcl_command_completion import (
|
||||
TCL_COMMAND_ITEMS,
|
||||
TCL_COMMAND_NAMES,
|
||||
tcl_argument_completions,
|
||||
DynamicCompletionKind,
|
||||
path_completion_items,
|
||||
tcl_argument_completion,
|
||||
)
|
||||
|
||||
WORKSPACE_SETTINGS = {}
|
||||
@@ -94,9 +96,16 @@ BUILTIN_PROC_NAMES = {
|
||||
for item in standard_items.tcl_keyword_list + standard_items.nx_procs
|
||||
} | set(TCL_COMMAND_NAMES)
|
||||
BUILTIN_VARIABLE_NAMES = {item.label for item in standard_items.nx_variables}
|
||||
_TCL_COMMAND_ITEMS_BY_LABEL = {
|
||||
item.label: item for item in TCL_COMMAND_ITEMS
|
||||
}
|
||||
_TCL_KEYWORD_ITEMS = [
|
||||
_TCL_COMMAND_ITEMS_BY_LABEL.get(item.label, item)
|
||||
for item in standard_items.tcl_keyword_list
|
||||
]
|
||||
_STATIC_TCL_LABELS = {item.label for item in standard_items.tcl_keyword_list}
|
||||
STATIC_COMPLETION_ITEMS = tuple(
|
||||
standard_items.tcl_keyword_list
|
||||
_TCL_KEYWORD_ITEMS
|
||||
+ [item for item in TCL_COMMAND_ITEMS if item.label not in _STATIC_TCL_LABELS]
|
||||
+ standard_items.nx_procs
|
||||
+ standard_items.nx_variables
|
||||
@@ -272,11 +281,37 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
|
||||
# Variable completion wins inside command arguments. Otherwise prefer the
|
||||
# narrow command grammar when the cursor is at a known subcommand/option.
|
||||
argument_completion = None
|
||||
if context != CompletionContext.VARIABLE:
|
||||
argument_items = tcl_argument_completions(source_lines, position)
|
||||
if argument_items is not None:
|
||||
argument_completion = tcl_argument_completion(source_lines, position)
|
||||
if (
|
||||
argument_completion is not None
|
||||
and argument_completion.dynamic_kind is None
|
||||
):
|
||||
items = ranked_completion_items(
|
||||
((0, item) for item in argument_items),
|
||||
((0, item) for item in argument_completion.items),
|
||||
CompletionContext.GENERAL,
|
||||
)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
if (
|
||||
argument_completion is not None
|
||||
and argument_completion.dynamic_kind == DynamicCompletionKind.PATH
|
||||
):
|
||||
dynamic_items: tuple[lsp.CompletionItem, ...] = ()
|
||||
if doc.uri.startswith("file:"):
|
||||
document_path = pathlib.Path(uris.to_fs_path(doc.uri))
|
||||
dynamic_items = path_completion_items(
|
||||
document_path.parent,
|
||||
argument_completion,
|
||||
position,
|
||||
)
|
||||
path_candidates = [
|
||||
(0, item) for item in argument_completion.items
|
||||
]
|
||||
path_candidates.extend((10, item) for item in dynamic_items)
|
||||
items = ranked_completion_items(
|
||||
path_candidates,
|
||||
CompletionContext.GENERAL,
|
||||
)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
@@ -284,7 +319,8 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
# Space and dash are registered only to open command-aware suggestions.
|
||||
# Do not display the broad fallback list when such a trigger has no match.
|
||||
if (
|
||||
params.context is not None
|
||||
argument_completion is None
|
||||
and params.context is not None
|
||||
and params.context.trigger_kind
|
||||
== lsp.CompletionTriggerKind.TriggerCharacter
|
||||
and params.context.trigger_character in {" ", "-"}
|
||||
@@ -337,6 +373,69 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
|
||||
priority = 100 if LSP_SERVER.paths_equal(item_path, filepath) else 200
|
||||
candidates.extend((priority, item) for item in items_by_file[item_path])
|
||||
|
||||
if argument_completion is not None:
|
||||
static_candidates = [(0, item) for item in argument_completion.items]
|
||||
if argument_completion.dynamic_kind == DynamicCompletionKind.VARIABLE:
|
||||
variable_candidates = [*static_candidates, *candidates]
|
||||
variable_candidates.extend(
|
||||
(300, item) for item in standard_items.nx_variables
|
||||
)
|
||||
items = ranked_completion_items(
|
||||
variable_candidates,
|
||||
CompletionContext.VARIABLE,
|
||||
)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
if argument_completion.dynamic_kind == DynamicCompletionKind.PROCEDURE:
|
||||
procedure_kinds = {
|
||||
lsp.CompletionItemKind.Constructor,
|
||||
lsp.CompletionItemKind.Function,
|
||||
lsp.CompletionItemKind.Method,
|
||||
}
|
||||
procedure_candidates = [
|
||||
(priority, item)
|
||||
for priority, item in candidates
|
||||
if item.kind in procedure_kinds
|
||||
]
|
||||
procedure_candidates.extend(
|
||||
(300, item) for item in standard_items.nx_procs
|
||||
)
|
||||
items = ranked_completion_items(
|
||||
[*static_candidates, *procedure_candidates],
|
||||
CompletionContext.GENERAL,
|
||||
)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
if argument_completion.dynamic_kind == DynamicCompletionKind.NAMESPACE:
|
||||
namespace_candidates = list(static_candidates)
|
||||
prefix_is_absolute = argument_completion.active_prefix.startswith("::")
|
||||
for index in LSP_SERVER.navigation_snapshot().values():
|
||||
priority = (
|
||||
100
|
||||
if LSP_SERVER.paths_equal(index.path, filepath)
|
||||
else 200
|
||||
)
|
||||
for occurrence in index.occurrences:
|
||||
if occurrence.identity.kind != "namespace":
|
||||
continue
|
||||
name = occurrence.identity.name
|
||||
label = name if prefix_is_absolute else name.removeprefix("::")
|
||||
namespace_candidates.append(
|
||||
(
|
||||
priority,
|
||||
lsp.CompletionItem(
|
||||
label=label,
|
||||
kind=lsp.CompletionItemKind.Module,
|
||||
detail="Tcl namespace",
|
||||
),
|
||||
)
|
||||
)
|
||||
items = ranked_completion_items(
|
||||
namespace_candidates,
|
||||
CompletionContext.GENERAL,
|
||||
)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
candidates.extend((300, item) for item in STATIC_COMPLETION_ITEMS)
|
||||
items = ranked_completion_items(candidates, context)
|
||||
return lsp.CompletionList(is_incomplete=False, items=items)
|
||||
|
||||
Reference in New Issue
Block a user