- 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
250 lines
6.3 KiB
Python
250 lines
6.3 KiB
Python
import re
|
|
|
|
import pytest
|
|
from prompt_toolkit.completion import Completion
|
|
from prompt_toolkit.document import Document
|
|
|
|
from falyx import Falyx
|
|
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()
|
|
|
|
run_parser = CommandArgumentParser(
|
|
command_key="R",
|
|
command_description="Run Command",
|
|
)
|
|
run_parser.add_argument("--tag")
|
|
run_parser.add_argument("--name")
|
|
|
|
flx.add_command(
|
|
"R",
|
|
"Run Command",
|
|
lambda: None,
|
|
aliases=["RUN"],
|
|
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_namespace_entries_root(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
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_namespace_entries_submenu(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
ops = falyx.namespaces["OPS"].namespace
|
|
|
|
completions = completer._suggest_namespace_entries(ops, "D")
|
|
|
|
assert "D" in completions
|
|
assert "DEPLOY" in completions
|
|
|
|
|
|
def test_get_completions_no_input_shows_root_entries(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
results = list(completer.get_completions(Document(""), None))
|
|
texts = completion_texts(results)
|
|
|
|
assert any(isinstance(c, Completion) for c in results)
|
|
assert "R" in texts
|
|
assert "OPS" in texts
|
|
assert "X" in texts
|
|
|
|
|
|
def test_get_completions_partial_root_entry(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
results = list(completer.get_completions(Document("OP"), None))
|
|
texts = completion_texts(results)
|
|
|
|
assert "OPS" in texts
|
|
assert "OPERATIONS" in texts
|
|
|
|
|
|
def test_get_completions_no_match_returns_empty(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
assert list(completer.get_completions(Document("Z"), None)) == []
|
|
assert list(completer.get_completions(Document("OPS Z"), None)) == []
|
|
|
|
|
|
def test_get_completions_namespace_boundary_suggests_help_flags(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
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_preview_prefix_is_preserved(falyx):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
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)
|
|
|
|
results = list(completer.get_completions(Document('R "unclosed quote'), None))
|
|
|
|
assert results == []
|
|
|
|
|
|
def test_get_completions_exception_handling(falyx, monkeypatch):
|
|
completer = FalyxCompleter(falyx)
|
|
|
|
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"
|