diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index 83201450..dce0df3a 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -15,6 +15,7 @@ #include "log.h" #include "config/preferences.h" #include "profanity.h" +#include "tools/autocomplete.h" #include "ui/ui.h" #include @@ -28,6 +29,7 @@ /* Global state */ static GHashTable* providers = NULL; static GHashTable* provider_keys = NULL; +static Autocomplete providers_ac = NULL; /* ======================================================================== * Curl helpers @@ -245,6 +247,9 @@ ai_client_init(void) providers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)ai_provider_unref); provider_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + /* Create autocomplete for provider names */ + providers_ac = autocomplete_new(); + /* Add default providers */ ai_add_provider("openai", DEFAULT_OPENAI_URL, NULL); ai_add_provider("perplexity", DEFAULT_PERPLEXITY_URL, NULL); @@ -266,6 +271,11 @@ ai_client_shutdown(void) providers = NULL; provider_keys = NULL; + if (providers_ac) { + autocomplete_free(providers_ac); + providers_ac = NULL; + } + curl_global_cleanup(); log_info("AI client shutdown"); } @@ -304,6 +314,9 @@ ai_add_provider(const gchar* name, const gchar* api_url, const gchar* org_id) AIProvider* provider = ai_provider_new(name, api_url, org_id); g_hash_table_insert(providers, g_strdup(name), provider); + /* Sync autocomplete */ + autocomplete_add(providers_ac, name); + log_info("Added provider: %s (URL: %s)", name, api_url); return provider; /* Caller gets non-owning pointer; hash table owns ref */ } @@ -313,6 +326,10 @@ ai_remove_provider(const gchar* name) { if (!name || !providers) return FALSE; + + /* Sync autocomplete before removing */ + autocomplete_remove(providers_ac, name); + return g_hash_table_remove(providers, name); } @@ -334,40 +351,23 @@ ai_list_providers(void) return result; } +/* ======================================================================== + * Provider autocomplete state + * ======================================================================== */ + +/* Stateful provider name finder with case-sensitive matching. + * Maintains position in sorted list for deterministic tab-completion cycling. + * The autocomplete is kept in sync via ai_add_provider/ai_remove_provider. */ char* ai_providers_find(const char* const search_str, gboolean previous, void* context) { - if (!providers || !search_str || strlen(search_str) == 0) { - return NULL; + /* Initialize autocomplete on first use */ + if (!providers_ac) { + providers_ac = autocomplete_new(); } - GHashTableIter iter; - gpointer key; - gpointer value; - - /* Collect all matching providers */ - GList* matches = NULL; - g_hash_table_iter_init(&iter, providers); - while (g_hash_table_iter_next(&iter, &key, &value)) { - if (g_str_has_prefix((gchar*)key, search_str)) { - matches = g_list_append(matches, g_strdup((gchar*)key)); - } - } - - if (matches == NULL) { - return NULL; - } - - /* Return first or last match based on previous flag */ - gchar* result; - if (previous) { - result = g_strdup((gchar*)matches->data); - } else { - result = g_strdup((gchar*)g_list_last(matches)->data); - } - - g_list_free_full(matches, g_free); - return result; + /* Use stateful autocomplete */ + return autocomplete_complete(providers_ac, search_str, FALSE, previous); } gchar*