feat: add path completion, LCP-based suggestions, and validator tests

- Refactored `FalyxCompleter` to support longest common prefix (LCP) completions by default.
- Added `_ensure_quote` helper to auto-quote completions containing spaces/tabs.
- Integrated `_yield_lcp_completions` for consistent completion insertion logic.
- Added `_suggest_paths()` helper to dynamically suggest filesystem paths for arguments of type `Path`.
- Integrated path completion into `suggest_next()` for both positional and flagged arguments.
- Updated `argument_examples.py` to include a `--path` argument (`Path | None`), demonstrating file path completion.
- Enabled `CompleteStyle.COLUMN` for tab-completion menu formatting in interactive sessions.
- Improved bottom bar docstring formatting with fenced code block examples.
- Added safeguard to `word_validator` to reject `"N"` since it’s reserved for `yes_no_validator`.
- Improved help panel rendering for commands (using `Padding` + `Panel`).
- Added full test coverage for:
  - `FalyxCompleter` and LCP behavior (`tests/test_completer/`)
  - All validators (`tests/test_validators/`)
- Bumped version to 0.1.80.
This commit is contained in:
2025-08-03 18:10:32 -04:00
parent 8e306b9eaf
commit a25888f316
18 changed files with 594 additions and 34 deletions

View File

@@ -17,6 +17,7 @@ Integrated with the `Falyx.prompt_session` to enhance the interactive experience
from __future__ import annotations
import os
import shlex
from typing import TYPE_CHECKING, Iterable
@@ -69,7 +70,9 @@ class FalyxCompleter(Completer):
if not tokens or (len(tokens) == 1 and not cursor_at_end_of_token):
# Suggest command keys and aliases
yield from self._suggest_commands(tokens[0] if tokens else "")
stub = tokens[0] if tokens else ""
suggestions = [c.text for c in self._suggest_commands(stub)]
yield from self._yield_lcp_completions(suggestions, stub)
return
# Identify command
@@ -83,21 +86,10 @@ class FalyxCompleter(Completer):
stub = "" if cursor_at_end_of_token else tokens[-1]
try:
if not command.arg_parser:
return
suggestions = command.arg_parser.suggest_next(
parsed_args + ([stub] if stub else []), cursor_at_end_of_token
)
for suggestion in suggestions:
if suggestion.startswith(stub):
if len(suggestion.split()) > 1:
yield Completion(
f'"{suggestion}"',
start_position=-len(stub),
display=suggestion,
)
else:
yield Completion(suggestion, start_position=-len(stub))
yield from self._yield_lcp_completions(suggestions, stub)
except Exception:
return
@@ -126,3 +118,42 @@ class FalyxCompleter(Completer):
for key in keys:
if key.upper().startswith(prefix):
yield Completion(key, start_position=-len(prefix))
def _ensure_quote(self, text: str) -> str:
"""
Ensure the text is properly quoted for shell commands.
Args:
text (str): The input text to quote.
Returns:
str: The quoted text, suitable for shell command usage.
"""
if " " in text or "\t" in text:
return f'"{text}"'
return text
def _yield_lcp_completions(self, suggestions, stub):
matches = [s for s in suggestions if s.startswith(stub)]
if not matches:
return
lcp = os.path.commonprefix(matches)
if len(matches) == 1:
yield Completion(
self._ensure_quote(matches[0]),
start_position=-len(stub),
display=matches[0],
)
elif len(lcp) > len(stub) and not lcp.startswith("-"):
yield Completion(lcp, start_position=-len(stub), display=lcp)
for match in matches:
yield Completion(
self._ensure_quote(match), start_position=-len(stub), display=match
)
else:
for match in matches:
yield Completion(
self._ensure_quote(match), start_position=-len(stub), display=match
)