refactor: make completer routing-aware for namespaces

- route completions through resolve_completion_route instead of one-level command lookup
- add CompletionRoute to model partial completion state
- suggest namespace entries and namespace-level help/TLDR flags while routing
- delegate leaf argv completion to CommandArgumentParser after command resolution
- restore LCP completion behavior with deduping and flag-safe handling
- add namespace completion name iteration and TLDR example support to Falyx
- update completer and completion route documentation
This commit is contained in:
2026-04-12 14:04:06 -04:00
parent 8ece2a5de6
commit dcec792d32
10 changed files with 782 additions and 311 deletions

View File

@@ -1,3 +1,5 @@
import re
import pytest
from prompt_toolkit.completion import Completion
from prompt_toolkit.document import Document
@@ -7,99 +9,241 @@ from falyx.completer import FalyxCompleter
from falyx.parser import CommandArgumentParser
def completion_texts(completions) -> list[str]:
return [c.text for c in completions]
@pytest.fixture
def falyx():
flx = Falyx()
parser = CommandArgumentParser(
run_parser = CommandArgumentParser(
command_key="R",
command_description="Run Command",
)
parser.add_argument(
"--tag",
)
parser.add_argument(
"--name",
)
run_parser.add_argument("--tag")
run_parser.add_argument("--name")
flx.add_command(
"R",
"Run Command",
lambda x: None,
lambda: None,
aliases=["RUN"],
arg_parser=parser,
arg_parser=run_parser,
)
ops = Falyx(program="ops")
deploy_parser = CommandArgumentParser(
command_key="D",
command_description="Deploy Command",
)
deploy_parser.add_argument("--target")
deploy_parser.add_argument("--region")
ops.add_command(
"D",
"Deploy Command",
lambda: None,
aliases=["DEPLOY"],
arg_parser=deploy_parser,
)
flx.add_submenu(
"OPS",
"Operations",
ops,
aliases=["OPERATIONS"],
)
return flx
def test_suggest_commands(falyx):
def test_suggest_namespace_entries_root(falyx):
completer = FalyxCompleter(falyx)
completions = list(completer._suggest_commands("R"))
assert any(c.text == "R" for c in completions)
assert any(c.text == "RUN" for c in completions)
completions = completer._suggest_namespace_entries(falyx, "R")
assert "R" in completions
assert "RUN" in completions
completions = completer._suggest_namespace_entries(falyx, "r")
assert "r" in completions
assert "run" in completions
def test_suggest_commands_empty(falyx):
def test_suggest_namespace_entries_submenu(falyx):
completer = FalyxCompleter(falyx)
completions = list(completer._suggest_commands(""))
assert any(c.text == "X" for c in completions)
assert any(c.text == "H" for c in completions)
ops = falyx.namespaces["OPS"].namespace
completions = completer._suggest_namespace_entries(ops, "D")
assert "D" in completions
assert "DEPLOY" in completions
def test_suggest_commands_no_match(falyx):
def test_get_completions_no_input_shows_root_entries(falyx):
completer = FalyxCompleter(falyx)
completions = list(completer._suggest_commands("Z"))
assert not completions
results = list(completer.get_completions(Document(""), None))
texts = completion_texts(results)
def test_get_completions_no_input(falyx):
completer = FalyxCompleter(falyx)
doc = Document("")
results = list(completer.get_completions(doc, None))
assert any(isinstance(c, Completion) for c in results)
assert any(c.text == "X" for c in results)
assert "R" in texts
assert "OPS" in texts
assert "X" in texts
def test_get_completions_no_match(falyx):
def test_get_completions_partial_root_entry(falyx):
completer = FalyxCompleter(falyx)
doc = Document("Z")
completions = list(completer.get_completions(doc, None))
assert not completions
doc = Document("Z Z")
completions = list(completer.get_completions(doc, None))
assert not completions
results = list(completer.get_completions(Document("OP"), None))
texts = completion_texts(results)
assert "OPS" in texts
assert "OPERATIONS" in texts
def test_get_completions_partial_command(falyx):
def test_get_completions_no_match_returns_empty(falyx):
completer = FalyxCompleter(falyx)
doc = Document("R")
results = list(completer.get_completions(doc, None))
assert any(c.text in ("R", "RUN") for c in results)
assert list(completer.get_completions(Document("Z"), None)) == []
assert list(completer.get_completions(Document("OPS Z"), None)) == []
def test_get_completions_with_flag(falyx):
def test_get_completions_namespace_boundary_suggests_help_flags(falyx):
completer = FalyxCompleter(falyx)
doc = Document("R ")
results = list(completer.get_completions(doc, None))
assert "--tag" in [c.text for c in results]
results = list(completer.get_completions(Document("OPS -"), None))
texts = completion_texts(results)
assert "-h" in texts
assert "--help" in texts
assert "-T" in texts
assert "--tldr" in texts
def test_get_completions_partial_flag(falyx):
def test_get_completions_preview_prefix_is_preserved(falyx):
completer = FalyxCompleter(falyx)
doc = Document("R --t")
results = list(completer.get_completions(doc, None))
assert all(c.start_position <= 0 for c in results)
assert any(c.text.startswith("--t") or c.display == "--tag" for c in results)
results = list(completer.get_completions(Document("?R"), None))
texts = completion_texts(results)
assert any(text.startswith("?R") for text in texts)
def test_get_completions_preview_prefix_for_namespace_entries(falyx):
completer = FalyxCompleter(falyx)
results = list(completer.get_completions(Document("?OP"), None))
texts = completion_texts(results)
assert "?OPS" in texts or "?OPERATIONS" in texts
def test_get_completions_leaf_command_delegates_flags_to_root_command_parser(
falyx, monkeypatch
):
completer = FalyxCompleter(falyx)
seen = {}
def fake_suggest_next(args, cursor_at_end_of_token):
seen["args"] = list(args)
seen["cursor_at_end_of_token"] = cursor_at_end_of_token
return ["--tag"]
monkeypatch.setattr(
falyx.commands["R"].arg_parser,
"suggest_next",
fake_suggest_next,
)
results = list(completer.get_completions(Document("R --t"), None))
texts = completion_texts(results)
assert seen["args"] == ["--t"]
assert seen["cursor_at_end_of_token"] is False
assert "--tag" in texts
def test_get_completions_leaf_command_delegates_flags_to_submenu_command_parser(
falyx, monkeypatch
):
completer = FalyxCompleter(falyx)
ops = falyx.namespaces["OPS"].namespace
deploy = ops.commands["D"]
seen = {}
def fake_suggest_next(args, cursor_at_end_of_token):
seen["args"] = list(args)
seen["cursor_at_end_of_token"] = cursor_at_end_of_token
return ["--target"]
monkeypatch.setattr(
deploy.arg_parser,
"suggest_next",
fake_suggest_next,
)
results = list(completer.get_completions(Document("OPS D --t"), None))
texts = completion_texts(results)
assert seen["args"] == ["--t"]
assert seen["cursor_at_end_of_token"] is False
assert "--target" in texts
def test_get_completions_leaf_command_receives_empty_stub_after_space(falyx, monkeypatch):
completer = FalyxCompleter(falyx)
seen = {}
def fake_suggest_next(args, cursor_at_end_of_token):
seen["args"] = list(args)
seen["cursor_at_end_of_token"] = cursor_at_end_of_token
return ["--tag", "--name"]
monkeypatch.setattr(
falyx.commands["R"].arg_parser,
"suggest_next",
fake_suggest_next,
)
results = list(completer.get_completions(Document("R "), None))
texts = completion_texts(results)
assert seen["args"] == []
assert seen["cursor_at_end_of_token"] is True
assert "--tag" in texts
assert "--name" in texts
def test_get_completions_bad_input(falyx):
completer = FalyxCompleter(falyx)
doc = Document('R "unclosed quote')
results = list(completer.get_completions(doc, None))
results = list(completer.get_completions(Document('R "unclosed quote'), None))
assert results == []
def test_get_completions_exception_handling(falyx):
def test_get_completions_exception_handling(falyx, monkeypatch):
completer = FalyxCompleter(falyx)
falyx.commands["R"].arg_parser.suggest_next = lambda *args: 1 / 0
doc = Document("R --tag")
results = list(completer.get_completions(doc, None))
def boom(*args, **kwargs):
raise ZeroDivisionError("boom")
monkeypatch.setattr(falyx.commands["R"].arg_parser, "suggest_next", boom)
results = list(completer.get_completions(Document("R --tag"), None))
assert results == []
def test_ensure_quote_wraps_whitespace(falyx):
completer = FalyxCompleter(falyx)
assert completer._ensure_quote("hello world") == '"hello world"'
assert completer._ensure_quote("hello") == "hello"