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 "") | ||||
|             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]: | ||||
|         prefix = prefix.upper() | ||||
|         keys = [self.falyx.exit_command.key] | ||||
|   | ||||
| @@ -507,7 +507,7 @@ class Falyx: | ||||
|                 message=self.prompt, | ||||
|                 multiline=False, | ||||
|                 completer=self._get_completer(), | ||||
|                 reserve_space_for_menu=1, | ||||
|                 reserve_space_for_menu=5, | ||||
|                 validator=CommandValidator(self, self._get_validator_error_message()), | ||||
|                 bottom_toolbar=self._get_bottom_bar_render(), | ||||
|                 key_bindings=self.key_bindings, | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from __future__ import annotations | ||||
|  | ||||
| from collections import defaultdict | ||||
| from copy import deepcopy | ||||
| from difflib import get_close_matches | ||||
| from typing import Any, Iterable | ||||
|  | ||||
| from rich.console import Console | ||||
| @@ -914,6 +915,84 @@ class CommandArgumentParser: | ||||
|                 kwargs_dict[arg.dest] = parsed[arg.dest] | ||||
|         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: | ||||
|         # Options | ||||
|         # Add all keyword arguments to the options list | ||||
|   | ||||
		Reference in New Issue
	
	Block a user