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.
120 lines
3.3 KiB
Markdown
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`).
|