refactor(ai): flatten autocomplete into sequential prefix-matching chain
Some checks failed
CI Code / Check spelling (pull_request) Successful in 1m5s
CI Code / Check coding style (pull_request) Successful in 2m7s
CI Code / Linux (arch) (pull_request) Failing after 3m9s
CI Code / Code Coverage (pull_request) Successful in 2m54s
CI Code / Linux (debian) (pull_request) Successful in 4m28s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m41s

Replace the deeply nested conditional tree in _ai_autocomplete() with a
flat sequential chain of autocomplete_param_with_*() calls. Each call
tries a specific command prefix (e.g., "/ai set provider", "/ai start")
and returns immediately on match — a common, reliable pattern for
command-line tab completion.

The previous design nested provider-name autocomplete inside
g_strv_length() and g_strcmp0() checks, meaning /ai<tab> and /ai <tab>
would never reach the subcommand matcher. The new flat structure ensures
every prefix is tried in order, with the most specific prefixes first
and the generic /ai subcommand fallback last.

This pattern — sequential prefix matching with early return — is the
standard approach for autocomplete dispatch: each handler owns its
prefix, no shared state or argument parsing is needed, and adding a
new subcommand is a single append to the chain.
This commit is contained in:
2026-04-30 23:10:14 +00:00
parent dc75f16221
commit 002a6ed15b

View File

@@ -4445,84 +4445,61 @@ static char*
_ai_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
gboolean parsed = FALSE;
auto_gcharv gchar** args = parse_args(input, 0, 3, &parsed);
if (parsed) {
int num_args = g_strv_length(args);
/* Top-level /ai <subcommand> autocomplete (e.g., /ai s<tab> -> /ai set) */
if (num_args >= 1) {
if (g_strcmp0(args[0], "set") == 0) {
if (num_args >= 2) {
if (g_strcmp0(args[1], "provider") == 0) {
// /ai set provider <name> <url> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai set provider", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else if (g_strcmp0(args[1], "token") == 0) {
// /ai set token <provider> <token> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai set token", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else if (g_strcmp0(args[1], "org") == 0) {
// /ai set org <provider> <org_id> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai set org", ai_providers_find, previous, NULL);
if (result) {
return result;
}
}
}
// /ai set <subcommand> - autocomplete subcommands
result = autocomplete_param_with_ac(input, "/ai set", ai_set_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
} else if (g_strcmp0(args[0], "remove") == 0) {
if (num_args >= 2 && g_strcmp0(args[1], "provider") == 0) {
// /ai remove provider <name> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai remove provider", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else {
result = autocomplete_param_with_ac(input, "/ai remove", ai_remove_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
}
} else if (g_strcmp0(args[0], "start") == 0) {
// /ai start [<provider>/<model>] - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai start", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else if (g_strcmp0(args[0], "clear") == 0) {
// /ai clear [<provider>] - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai clear", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else if (g_strcmp0(args[0], "correct") == 0) {
// /ai correct <provider> <text>
result = autocomplete_param_with_func(input, "/ai correct", ai_providers_find, previous, NULL);
if (result) {
return result;
}
} else if (g_strcmp0(args[0], "providers") == 0) {
// /ai providers - no subcommands
return NULL;
}
}
result = autocomplete_param_with_ac(input, "/ai", ai_subcommands_ac, FALSE, previous);
if (result) {
return result;
}
/* Top-level /ai <subcommand> autocomplete (e.g., /ai s<tab> -> /ai set) */
result = autocomplete_param_with_func(input, "/ai set provider", ai_providers_find, previous, NULL);
if (result) {
return result;
}
// /ai set token <provider> <token> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai set token", ai_providers_find, previous, NULL);
if (result) {
return result;
}
// /ai set org <provider> <org_id> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai set org", ai_providers_find, previous, NULL);
if (result) {
return result;
}
// /ai set <subcommand> - autocomplete subcommands
result = autocomplete_param_with_ac(input, "/ai set", ai_set_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
// /ai remove provider <name> - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai remove provider", ai_providers_find, previous, NULL);
if (result) {
return result;
}
result = autocomplete_param_with_ac(input, "/ai remove", ai_remove_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
// /ai start [<provider>/<model>] - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai start", ai_providers_find, previous, NULL);
if (result) {
return result;
}
// /ai clear [<provider>] - autocomplete provider names
result = autocomplete_param_with_func(input, "/ai clear", ai_providers_find, previous, NULL);
if (result) {
return result;
}
// /ai correct <provider> <text>
result = autocomplete_param_with_func(input, "/ai correct", ai_providers_find, previous, NULL);
if (result) {
return result;
}
result = autocomplete_param_with_ac(input, "/ai", ai_subcommands_ac, FALSE, previous);
return result;
}