Working on completions
This commit is contained in:
@ -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]
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user