feat(xce-test-cli-ux): add command-name tab completion scoped to context
Registers libedit's GNU-readline-compatible rl_attempted_completion_function under g_interactive only. The candidate generator reads active_ctx->commands[] live on every call, so completion correctly tracks context switches rather than freezing at registration time. Single/multi-match UI (auto-complete vs. double-Tab candidate listing) is entirely libedit's stock behavior once given a correct context-scoped generator. Proven via a black-box pty test verified sound against a false-positive risk: xce_test has no fuzzy command-name matching, so an uncompleted partial token cannot coincidentally trigger the same effect a real completion would. Also fixes a harness bug (bug-1792): disabling kernel ECHO for output capture was silently suppressing libedit's own completion feedback too; added clitest_spawn_ex() with disable_echo as an explicit parameter, clitest_spawn() unchanged for existing callers. 1307/1307 controller suite passing (+1), re-verified 8/8 for flakiness. Task 2.3 of .kiro/specs/xce-test-cli-ux/tasks.md. Requirements: 7.1, 7.2, 7.3, 7.4 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
parent
07d238eada
commit
0ef232a013
4 changed files with 356 additions and 14 deletions
|
|
@ -860,6 +860,101 @@ command_t* find_command(char* name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* xce-test-cli-ux task 2.3, Requirement 7: command-name tab completion.
|
||||
*
|
||||
* libedit API verification (design.md's flagged P0 risk, research.md's
|
||||
* "libedit completion API may not perfectly mirror GNU readline" risk):
|
||||
* confirmed by reading the ACTUAL linked headers in this build environment
|
||||
* (this project's `src/flake.nix`-pinned libedit, pkg-config name
|
||||
* "libedit", providing `editline/readline.h` -- the same header already
|
||||
* included at the top of this file) plus a standalone pty-driven probe
|
||||
* program built and run against this exact library. Findings:
|
||||
*
|
||||
* - The GNU-readline-style completion API IS present verbatim:
|
||||
* `rl_completion_func_t* rl_attempted_completion_function` (signature
|
||||
* `char** (*)(const char*, int, int)`, exactly matching this task's
|
||||
* `xcetest_complete` shape from design.md) and
|
||||
* `char** rl_completion_matches(const char*, rl_compentry_func_t*)`
|
||||
* (matches on a `char* (*)(const char*, int)` generator, exactly
|
||||
* `xcetest_complete_generator`'s shape). No lower-level `<histedit.h>`
|
||||
* fallback was needed.
|
||||
* - `rl_attempted_completion_over` exists and suppresses libedit's
|
||||
* default filename-completion fallback -- used below so an empty
|
||||
* match list in the active context yields NO completion rather than
|
||||
* local filenames.
|
||||
* - Single/multiple-match UI behavior is entirely the library's: a sole
|
||||
* match is completed in full (plus a trailing space) with ZERO extra
|
||||
* code (7.2); multiple matches with no further common prefix ring the
|
||||
* bell on the first Tab and print the candidate list (input line left
|
||||
* untouched -- confirmed via a raw pty capture) on a SECOND,
|
||||
* consecutive Tap of Tab -- standard GNU-readline/bash double-Tab
|
||||
* convention (7.3), not something this code implements. This
|
||||
* implementation's own responsibility is narrowly the context-scoped
|
||||
* candidate lookup below.
|
||||
* - `rl_variable_bind("show-all-if-ambiguous", "on")` was probed and
|
||||
* returns failure (1) against this libedit build -- that variable is
|
||||
* not implemented here, so it is not used; the stock double-Tab
|
||||
* behavior above is what ships.
|
||||
*/
|
||||
|
||||
/* xcetest_complete_generator - the readline "compentry" generator: called
|
||||
* repeatedly with state==0 on the first call (resets the scan) and
|
||||
* state!=0 on each subsequent call, returning one more `strdup()`ed match
|
||||
* per call (readline/libedit frees each returned string) and NULL once
|
||||
* exhausted. Iterates active_ctx->commands[] -- the CURRENTLY active
|
||||
* context at the moment Tab is pressed, read fresh on every call rather
|
||||
* than captured at registration time -- so completion always tracks
|
||||
* `worker`/`scheduler`/`client`/`..` context switches for the life of the
|
||||
* session (Requirement 7.1/7.4: only the active context's commands are
|
||||
* ever offered). Plain prefix match against `text`, same convention as
|
||||
* find_command()'s exact-match lookup just above. */
|
||||
static char* xcetest_complete_generator(const char* text, int state)
|
||||
{
|
||||
static int idx;
|
||||
static size_t len;
|
||||
|
||||
if (state == 0) {
|
||||
idx = 0;
|
||||
len = strlen(text);
|
||||
}
|
||||
|
||||
while (active_ctx->commands[idx].name != NULL) {
|
||||
const char* name = active_ctx->commands[idx].name;
|
||||
|
||||
idx++;
|
||||
if (strncmp(name, text, len) == 0) {
|
||||
return strdup(name);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* xcetest_complete - the readline "attempted completion" entry point,
|
||||
* registered as rl_attempted_completion_function only when g_interactive
|
||||
* (main(), below). `start`/`end` are byte offsets of the word being
|
||||
* completed within rl_line_buffer; `start == 0` means the FIRST word on
|
||||
* the line, i.e. the command name itself. Per this task's explicit
|
||||
* boundary (command-NAME completion only -- no flag-value completion of
|
||||
* jobids/hostnames/resource names), every other word position (start != 0)
|
||||
* returns NULL and leaves rl_attempted_completion_over at its default so
|
||||
* libedit's normal fallback applies there, unchanged from today's
|
||||
* zero-customization behavior. For the command-name position, this always
|
||||
* sets rl_attempted_completion_over so a context with zero matching
|
||||
* commands offers NOTHING (never falls back to filename completion). */
|
||||
static char** xcetest_complete(const char* text, int start, int end)
|
||||
{
|
||||
(void)end;
|
||||
|
||||
if (start != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rl_attempted_completion_over = 1;
|
||||
|
||||
return rl_completion_matches(text, xcetest_complete_generator);
|
||||
}
|
||||
|
||||
/* Execute a command line. */
|
||||
int execute_line(char* line)
|
||||
{
|
||||
|
|
@ -4544,6 +4639,17 @@ int main(int argc, char* argv[])
|
|||
g_interactive = isatty(STDIN_FILENO);
|
||||
g_continue_on_error = g_interactive ? 1 : 0;
|
||||
|
||||
/* xce-test-cli-ux task 2.3, Requirement 7: register command-name tab
|
||||
* completion, but ONLY for a genuinely interactive session -- design.md's
|
||||
* explicit precondition on xcetest_complete(). A piped/redirected/
|
||||
* scripted stdin never calls readline()'s completion machinery anyway
|
||||
* (no terminal to press Tab on), but gating the registration itself
|
||||
* (rather than relying on that) keeps the precondition explicit and
|
||||
* matches this file's existing g_interactive-gated conventions. */
|
||||
if (g_interactive) {
|
||||
rl_attempted_completion_function = xcetest_complete;
|
||||
}
|
||||
|
||||
/* xce-test-rc-file task 2.2: auto-load $XDG_CONFIG_HOME/xce_test/rc (or
|
||||
* $HOME/.config/xce_test/rc) before the read loop starts, for both
|
||||
* interactive and scripted sessions alike -- unless --norc was given.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue