Add a new subcommand to the usage builtin:
usage complete script posReturns a list of completion candidates at position pos in script as a list of dicts.
Completions are offered:
- At top-level command positions
- Inside arguments marked with
type scriptin usage specs - Recursively through nested script contexts
The parser determines command positions following standard TCL parsing rules (after whitespace, newlines, semicolons, inside braces when parent arg is type script).
Returns a list of dicts, each representing a completion candidate:
{
{text <string> type <category> help <description>}
{text <string> type <category> help <description>}
...
}For argument placeholders (where user must provide a value):
{
{text {} type arg-placeholder name <arg-name> help <description>}
...
}| Type | Description | When Offered |
|---|---|---|
command |
Top-level command | At command position, empty or partial prefix |
subcommand |
Subcommand from usage spec | After parent command/subcommand |
flag |
Flag option | When flags are valid (always, except when flag expects value) |
value |
Argument/flag value from choices |
When completing arg/flag that has choices |
arg-placeholder |
Indicates expected argument | When positional arg expected |
-
Command Completion
- Source: Only commands with registered usage specs
- Matching: Case-sensitive prefix match
- Ordering: Alphabetical (since top-level commands aren't in spec order)
- At position 0 or empty prefix: return all registered commands
-
Subcommand Completion
- Only offer subcommands valid in current context (after parent has been matched)
- Ordering: Preserved from spec order
- Matching: Prefix match against subcommand name
-
Flag Completion
- Always offer flags when valid (even without
-prefix) - Exception: Don't offer flags when a flag is expecting its value
- Include both short and long forms as separate entries
- Always offer flags when valid (even without
-
Value Completion
- When argument or flag has
choicesdefined, offer those as completions - Inherit parent help text for value completions
- Only return choices, not other flags/args
- When argument or flag has
-
Argument Placeholders
- Offer required arg placeholders when that positional arg is expected
- Offer optional arg placeholders alongside flags
- Variadic args: offer placeholder only once (after first occurrence, stop suggesting)
-
Filtering by
hide- Exclude entries marked with
hidefrom completions
- Exclude entries marked with
-
Type Hints
- Arguments with
type file,type dir, etc. are not completed by Feather - Host should intercept these cases and provide filesystem-based completions
- Document this behavior for embedders
- Arguments with
usage for puts {arg <text>}
usage for proc {arg <name> arg <args> arg <body> {type script}}
usage complete {pu} 2
# => {{text puts type command help {}}}
usage complete {} 0
# => {{text proc type command help {}}
# {text puts type command help {}}
# ...all registered commands...}usage for git {
cmd clone {arg <repo>}
cmd commit {flag -m --message <msg>}
cmd checkout {arg <branch>}
}
usage complete {git co} 6
# => {{text commit type subcommand help {}}
# {text checkout type subcommand help {}}}
usage complete {git commit } 11
# => {{text -m type flag help {}}
# {text --message type flag help {}}
# {text {} type arg-placeholder name ... help {}}}usage for compile {
arg <source>
flag --verbose {help {Enable verbose output}}
flag -O --optimize ?level? {help {Optimization level}}
}
usage complete {compile file.c } 15
# => {{text --verbose type flag help {Enable verbose output}}
# {text -O type flag help {Optimization level}}
# {text --optimize type flag help {Optimization level}}}
usage complete {compile file.c -} 16
# => {{text --verbose type flag help {Enable verbose output}}
# {text -O type flag help {Optimization level}}
# {text --optimize type flag help {Optimization level}}}usage for compile {
flag --format <fmt> {
help {Output format}
choices {json yaml toml}
}
}
usage complete {compile --format } 18
# => {{text json type value help {Output format}}
# {text yaml type value help {Output format}}
# {text toml type value help {Output format}}}
usage complete {compile --format j} 19
# => {{text json type value help {Output format}}}usage for if {
arg <condition>
arg <body> {type script}
}
usage for puts {arg <text>}
usage complete {if {$x > 0} {pu}} 17
# => {{text puts type command help {}}}usage for while {
arg <condition>
arg <body> {type script}
}
usage complete {while {1} {\n if {$x} {\n se}} 30
# => {{text set type command help {}}}usage for cmd {
arg <input> {help {Input file}}
arg ?output? {help {Output file}}
flag --verbose
}
usage complete {cmd } 4
# => {{text {} type arg-placeholder name input help {Input file}}
# {text --verbose type flag help {}}}
usage complete {cmd file.txt } 13
# => {{text {} type arg-placeholder name output help {Output file}}
# {text --verbose type flag help {}}}usage for cmd {
arg <files>... {help {Input files}}
flag --verbose
}
usage complete {cmd file1.txt } 13
# => {{text --verbose type flag help {}}}
# Note: No more placeholder for files (already satisfied once)File: src/builtin_usage.c
Add internal functions to build completion entries:
// Create completion entry dict
static FeatherObj make_completion(
const FeatherHostOps *ops,
FeatherInterp interp,
const char *text,
const char *type,
FeatherObj help
);
// Create arg placeholder entry
static FeatherObj make_arg_placeholder(
const FeatherHostOps *ops,
FeatherInterp interp,
const char *name,
FeatherObj help
);These build dicts with the format: {text <str> type <type> help <help>} or {text {} type arg-placeholder name <name> help <help>}.
File: src/builtin_usage.c
Parse the script to determine what context the cursor is in:
typedef struct {
int at_command_position; // Cursor is where a command can start
FeatherObj partial_token; // The partial text being completed
size_t token_start; // Start position of current token
FeatherObj parent_command; // The command being invoked (if in args)
FeatherObj matched_subcommands; // List of matched subcommands (path)
int in_flag_value; // Cursor is completing a flag value
FeatherObj current_flag; // The flag expecting a value (if any)
} CompletionContext;
static CompletionContext analyze_completion_context(
const FeatherHostOps *ops,
FeatherInterp interp,
const char *script,
size_t script_len,
size_t pos
);This function:
- Tokenizes the script up to
posusing the parser - Determines if we're at a command position
- Tracks which command/subcommands have been matched
- Detects if we're completing a flag value
- Extracts the partial token being completed
File: src/builtin_usage.c
When at command position, complete from registered commands:
static FeatherObj complete_commands(
const FeatherHostOps *ops,
FeatherInterp interp,
FeatherObj prefix
);This function:
- Gets all registered usage specs from
::usage::specs - Filters by prefix match (case-sensitive)
- Returns list of
{text <cmd> type command help <...>}dicts - Sorted alphabetically
File: src/builtin_usage.c
When inside a command with subcommands, complete from spec:
static FeatherObj complete_subcommands(
const FeatherHostOps *ops,
FeatherInterp interp,
FeatherObj spec,
FeatherObj prefix
);This function:
- Walks the spec's
cmdentries - Filters by prefix match
- Respects
hideflag - Returns entries in spec order
- Returns
{text <subcmd> type subcommand help <...>}dicts
File: src/builtin_usage.c
Offer available flags from current spec:
static FeatherObj complete_flags(
const FeatherHostOps *ops,
FeatherInterp interp,
FeatherObj spec,
FeatherObj prefix,
FeatherObj already_provided // dict of flag_name -> 1 for flags already in args
);This function:
- Walks the spec's
flagentries - Skips flags already provided (unless they're variadic - future extension)
- Returns both short and long forms as separate entries
- Filters by prefix match
- Respects
hideflag - Returns
{text <flag> type flag help <...>}dicts
File: src/builtin_usage.c
When completing an argument or flag value with choices:
static FeatherObj complete_choices(
const FeatherHostOps *ops,
FeatherInterp interp,
FeatherObj entry, // arg or flag entry with choices
FeatherObj prefix
);This function:
- Extracts
choicesfrom entry - Filters by prefix match
- Inherits
helpfrom parent entry - Returns
{text <choice> type value help <...>}dicts
File: src/builtin_usage.c
Determine which positional args are expected:
static FeatherObj get_arg_placeholders(
const FeatherHostOps *ops,
FeatherInterp interp,
FeatherObj spec,
size_t args_provided // number of positional args provided so far
);This function:
- Walks
argentries in spec - Determines which args still need values:
- If required and not yet provided: include
- If optional and next in sequence: include
- If variadic and not yet provided: include once
- Returns
{text {} type arg-placeholder name <name> help <...>}dicts
File: src/builtin_usage.c
When cursor is inside a script-type argument, recurse:
static int is_inside_script_arg(
const FeatherHostOps *ops,
FeatherInterp interp,
CompletionContext *ctx,
FeatherObj spec,
size_t *script_start_pos // OUT: where the script arg starts
);This function:
- Checks if current argument position corresponds to a
type scriptarg - If yes, calculates the position within that script argument
- Recursively calls completion logic with the adjusted position
File: src/builtin_usage.c
Tie everything together:
static FeatherObj usage_complete_impl(
const FeatherHostOps *ops,
FeatherInterp interp,
const char *script,
size_t script_len,
size_t pos
);Algorithm:
- Parse script and analyze context (Step 2)
- If at command position:
- If no parent command: complete top-level commands (Step 3)
- If parent has subcommands: complete subcommands (Step 4)
- If in flag value: complete choices for that flag (Step 6)
- If in argument position:
- If arg has choices: complete choices (Step 6)
- Otherwise: combine flags (Step 5) + arg placeholders (Step 7)
- If inside script-type argument: recurse (Step 8)
- Filter all results by prefix match
- Return combined list
File: src/builtin_usage.c
Add usage complete to the subcommand dispatcher:
// In feather_builtin_usage():
if (feather_obj_eq_literal(ops, interp, subcmd, "complete")) {
// usage complete script pos
if (argc != 2) {
*err = ops->string.intern(interp,
S("usage: usage complete script pos"));
return FEATHER_ERR;
}
// Get script string
size_t script_len = 0;
const char *script = ops->string.get_bytes(interp, argv[0], &script_len);
// Get position
int64_t pos_i = 0;
if (!ops->integer.get(interp, argv[1], &pos_i) || pos_i < 0) {
*err = ops->string.intern(interp,
S("usage complete: pos must be a non-negative integer"));
return FEATHER_ERR;
}
size_t pos = (size_t)pos_i;
if (pos > script_len) {
pos = script_len; // Clamp to end
}
*result = usage_complete_impl(ops, interp, script, script_len, pos);
return FEATHER_OK;
}File: docs/builtin-usage.md
Add section documenting usage complete:
- Syntax and return format
- All completion types
- Examples for each scenario
- Notes for embedders (handling placeholders, type hints)
File: harness/testdata/usage-complete.html (new file)
Create comprehensive test suite covering:
- Command completion at various positions
- Subcommand completion with nesting
- Flag completion (with and without prefix)
- Value completion from choices
- Argument placeholders
- Script-type recursion
- Variadic argument handling
- Hide flag filtering
- Edge cases (empty script, position at end, position 0)
Test format:
<test id="complete-command-prefix">
<script>
usage for puts {arg <text>}
usage for proc {arg <name>}
usage complete {pu} 2
</script>
<expected>{{text puts type command help {}}}</expected>
</test>File: src/feather.h
Add declaration for programmatic completion (for embedders):
/**
* Get completion candidates at a position in a script.
*
* Returns a list of dicts with completion information.
* See docs/builtin-usage.md for dict format.
*
* @param ops Host operations
* @param interp Interpreter
* @param script Script text
* @param script_len Length of script
* @param pos Cursor position (byte offset)
* @return List of completion candidate dicts
*/
FeatherObj feather_usage_complete(
const FeatherHostOps *ops,
FeatherInterp interp,
const char *script,
size_t script_len,
size_t pos
);Build and test with real usage:
mise buildto compilemise testto run test suite- Create test scripts with various usage specs
- Manually verify completion behavior
- Test in the raylib game example (add console with completion UI)
- Iterate on edge cases and UX
Create commit with:
- Full implementation
- Documentation updates
- Test suite
- Commit message documenting design decisions and learnings