# Playbook: add autocomplete to a command Pre-req: command already exists in `cmd_defs.c`. If not, see `playbooks/add-command.md`. ## Choose the flavour | Token set | Use | |---|---| | Static, known at startup. | `autocomplete_param_with_ac` + a static `Autocomplete`. | | Dynamic — depends on roster, accounts, DB query, etc. | `autocomplete_param_with_func` + a stateless callback. | ## Static token set `src/command/cmd_ac.c`: ```c // 1. Declare the AC at file scope: static Autocomplete foo_ac; // 2. Add it to the static array near the top of the file (the free-list // that cmd_ac_uninit walks at shutdown): static Autocomplete* all_acs[] = { // ... existing entries ... &foo_ac, }; // 3. Initialise in cmd_ac_init(): foo_ac = autocomplete_new(); autocomplete_add(foo_ac, "alpha"); autocomplete_add(foo_ac, "beta"); // 4. Implement the dispatcher: static char* _foo_autocomplete(ProfWin* window, const char* const input, gboolean previous) { return autocomplete_param_with_ac(input, "/foo", foo_ac, TRUE, previous); } // 5. Register: g_hash_table_insert(ac_funcs, "/foo", _foo_autocomplete); ``` ## Dynamic — function callback If suggestions come from runtime state (roster, accounts, providers list): ```c // In a domain module (e.g. src/foo/foo.c), implement a stateless callback: char* foo_suggestions(const char* const search_str, gboolean previous, void* context) { // Compute and return a freshly-allocated char* (caller frees), or NULL. // No module-level "_last_match" globals — keep state on the stack / // derive deterministically from search_str and previous. } ``` Wire into `cmd_ac.c`: ```c static char* _foo_autocomplete(ProfWin* window, const char* const input, gboolean previous) { return autocomplete_param_with_func(input, "/foo", foo_suggestions, previous, NULL); } g_hash_table_insert(ac_funcs, "/foo", _foo_autocomplete); ``` A clean canonical example is `roster_contact_autocomplete` in `src/xmpp/roster_list.c` — it delegates to `autocomplete_complete` against a roster-owned `Autocomplete` and keeps no callback-local state. **Do not copy older callbacks that keep state in a file-static `_last_match` variable** — those have known issues with shift-tab cycling and concurrent completers. ## Subcommand autocompletion If `/foo` has subcommands, branch inside `_foo_autocomplete`: ```c static char* _foo_autocomplete(ProfWin* window, const char* const input, gboolean previous) { char* result = NULL; // First the subcommand list itself: result = autocomplete_param_with_ac(input, "/foo", foo_subcommands_ac, TRUE, previous); if (result) return result; // Then per-subcommand argument completion: if (g_str_has_prefix(input, "/foo set ")) { result = autocomplete_param_with_func(input, "/foo set", foo_set_options, previous, NULL); if (result) return result; } return NULL; } ``` `/roster` (`src/command/cmd_ac.c`, search for `_roster_autocomplete`) is a current example of this layered subcommand layout. ## Unit-test the callback Stateless callbacks are easy to unit-test directly: ```c void test_foo_suggestions_returns_first_match(void** state) { char* r = foo_suggestions("al", FALSE, NULL); assert_string_equal(r, "alpha"); free(r); } ``` Add the test pair to `tests/unittests/` (see `playbooks/add-test.md`).