Files
cproof-context/playbooks/add-autocomplete.md
jabber.developer2 22977846a3 docs: split context into layered, agent-oriented files
Replace the single file-structure.md with a stratified layout designed
for AI/agent skill consumption: tables and concrete identifiers over
prose, files loaded on demand, content separated by churn rate.

Layers:
- architecture/  stable structural reference (overview, source-map,
                 test-map, data-flow)
- patterns/      memory, commands, autocomplete, events, xmpp,
                 encryption, ui, plugins
- testing/       unit-tests, stubs, functional-tests, bench
- build/         local, docker, ci
- playbooks/     add-command, add-test, add-autocomplete,
                 add-event-handler, add-encryption
- gotchas.md     append-only dated entries (seven seed entries)
- wip/           branch-specific notes; deleted on merge to master

Stable layers describe cproof on master only. In-flight feature
branches (currently feat/ai) get a single file under wip/.

INDEX.md is the entry map with churn labels; SKILL.md is the
always-loaded skill hint pointing to it.
2026-04-30 20:52:06 +03:00

120 lines
3.3 KiB
Markdown

# 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`).