Working on completions

This commit is contained in:
2025-07-16 18:55:22 -04:00
parent dc1764e752
commit c15e3afa5e
3 changed files with 100 additions and 1 deletions

View File

@ -29,6 +29,26 @@ class FalyxCompleter(Completer):
yield from self._suggest_commands(tokens[0] if tokens else "") yield from self._suggest_commands(tokens[0] if tokens else "")
return return
# Identify command
command_key = tokens[0].upper()
command = self.falyx._name_map.get(command_key)
if not command or not command.arg_parser:
return
# If at end of token, e.g., "--t" vs "--tag ", add a stub so suggest_next sees it
parsed_args = tokens[1:] if cursor_at_end_of_token else tokens[1:-1]
stub = "" if cursor_at_end_of_token else tokens[-1]
try:
suggestions = command.arg_parser.suggest_next(
parsed_args + ([stub] if stub else [])
)
for suggestion in suggestions:
if suggestion.startswith(stub):
yield Completion(suggestion, start_position=-len(stub))
except Exception:
return
def _suggest_commands(self, prefix: str) -> Iterable[Completion]: def _suggest_commands(self, prefix: str) -> Iterable[Completion]:
prefix = prefix.upper() prefix = prefix.upper()
keys = [self.falyx.exit_command.key] keys = [self.falyx.exit_command.key]

View File

@ -507,7 +507,7 @@ class Falyx:
message=self.prompt, message=self.prompt,
multiline=False, multiline=False,
completer=self._get_completer(), completer=self._get_completer(),
reserve_space_for_menu=1, reserve_space_for_menu=5,
validator=CommandValidator(self, self._get_validator_error_message()), validator=CommandValidator(self, self._get_validator_error_message()),
bottom_toolbar=self._get_bottom_bar_render(), bottom_toolbar=self._get_bottom_bar_render(),
key_bindings=self.key_bindings, key_bindings=self.key_bindings,

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from difflib import get_close_matches
from typing import Any, Iterable from typing import Any, Iterable
from rich.console import Console from rich.console import Console
@ -914,6 +915,84 @@ class CommandArgumentParser:
kwargs_dict[arg.dest] = parsed[arg.dest] kwargs_dict[arg.dest] = parsed[arg.dest]
return tuple(args_list), kwargs_dict return tuple(args_list), kwargs_dict
def suggest_next(self, args: list[str]) -> list[str]:
"""
Suggest the next possible flags or values given partially typed arguments.
This does NOT raise errors. It is intended for completions, not validation.
Returns:
A list of possible completions based on the current input.
"""
consumed_positionals = []
positional_choices = [
str(choice)
for arg in self._positional.values()
for choice in arg.choices
if arg.choices
]
if not args:
# Nothing entered yet: suggest all top-level flags and positionals
if positional_choices:
return sorted(set(positional_choices))
return sorted(
set(
flag
for arg in self._arguments
for flag in arg.flags
if not arg.positional
)
)
last = args[-1]
suggestions: list[str] = []
# Case 1: Mid-flag (e.g., "--ver")
if last.startswith("-") and not last in self._flag_map:
possible_flags = [flag for flag in self._flag_map if flag.startswith(last)]
suggestions.extend(possible_flags)
# Case 2: Flag that expects a value (e.g., ["--tag"])
elif last in self._flag_map:
arg = self._flag_map[last]
if arg.choices:
suggestions.extend(arg.choices)
# Case 3: Just completed a flag, suggest next
elif len(args) >= 2 and args[-2] in self._flag_map:
# Just gave value for a flag, now suggest next possible
used_dests = {
self._flag_map[arg].dest for arg in args if arg in self._flag_map
}
remaining_args = [
a
for a in self._arguments
if not a.positional
and a.dest not in used_dests
and a.action != ArgumentAction.HELP
]
for arg in remaining_args:
suggestions.extend(arg.flags)
# Case 4: Positional values not yet filled
else:
consumed_positionals = [arg for arg in self._arguments if arg.positional][
: len(args)
]
remaining_positionals = [
arg
for arg in self._arguments
if arg.positional and arg not in consumed_positionals
]
if remaining_positionals:
arg = remaining_positionals[0]
if arg.choices:
suggestions.extend(arg.choices)
else:
suggestions.append(f"<{arg.dest}>") # generic placeholder
return sorted(set(suggestions))
def get_options_text(self, plain_text=False) -> str: def get_options_text(self, plain_text=False) -> str:
# Options # Options
# Add all keyword arguments to the options list # Add all keyword arguments to the options list