summaryrefslogtreecommitdiff
path: root/examples/dte/completion.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
commitdcacc00e3750300617ba6e16eb346713f91a783a (patch)
tree38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/dte/completion.c
parent58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff)
downloadcrep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz
Remove testing data
Diffstat (limited to 'examples/dte/completion.c')
-rw-r--r--examples/dte/completion.c879
1 files changed, 0 insertions, 879 deletions
diff --git a/examples/dte/completion.c b/examples/dte/completion.c
deleted file mode 100644
index 89e8c8c..0000000
--- a/examples/dte/completion.c
+++ /dev/null
@@ -1,879 +0,0 @@
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "completion.h"
-#include "bind.h"
-#include "command/alias.h"
-#include "command/args.h"
-#include "command/parse.h"
-#include "command/run.h"
-#include "command/serialize.h"
-#include "commands.h"
-#include "compiler.h"
-#include "config.h"
-#include "filetype.h"
-#include "options.h"
-#include "show.h"
-#include "syntax/color.h"
-#include "tag.h"
-#include "terminal/cursor.h"
-#include "terminal/key.h"
-#include "terminal/style.h"
-#include "util/arith.h"
-#include "util/array.h"
-#include "util/ascii.h"
-#include "util/bsearch.h"
-#include "util/debug.h"
-#include "util/intmap.h"
-#include "util/log.h"
-#include "util/numtostr.h"
-#include "util/path.h"
-#include "util/str-util.h"
-#include "util/string-view.h"
-#include "util/string.h"
-#include "util/xdirent.h"
-#include "util/xmalloc.h"
-#include "vars.h"
-
-extern char **environ;
-
-typedef enum {
- COLLECT_ALL, // (directories and files)
- COLLECT_EXECUTABLES, // (directories and executable files)
- COLLECT_DIRS_ONLY,
-} FileCollectionType;
-
-static bool is_executable(int dir_fd, const char *filename)
-{
- return faccessat(dir_fd, filename, X_OK, 0) == 0;
-}
-
-static bool do_collect_files (
- PointerArray *array,
- const char *dirname,
- const char *dirprefix,
- const char *fileprefix,
- FileCollectionType type
-) {
- DIR *const dir = xopendir(dirname);
- if (!dir) {
- return false;
- }
-
- const int dir_fd = dirfd(dir);
- if (unlikely(dir_fd < 0)) {
- LOG_ERRNO("dirfd");
- xclosedir(dir);
- return false;
- }
-
- size_t dlen = strlen(dirprefix);
- size_t flen = strlen(fileprefix);
- const struct dirent *de;
-
- while ((de = xreaddir(dir))) {
- const char *name = de->d_name;
- if (streq(name, ".") || streq(name, "..") || unlikely(streq(name, ""))) {
- continue;
- }
-
- // TODO: add a global option to allow dotfiles to be included
- // even when there's no prefix
- if (flen ? strncmp(name, fileprefix, flen) : name[0] == '.') {
- continue;
- }
-
- struct stat st;
- if (fstatat(dir_fd, name, &st, AT_SYMLINK_NOFOLLOW)) {
- continue;
- }
-
- bool is_dir = S_ISDIR(st.st_mode);
- if (S_ISLNK(st.st_mode)) {
- if (!fstatat(dir_fd, name, &st, 0)) {
- is_dir = S_ISDIR(st.st_mode);
- }
- }
-
- if (!is_dir) {
- switch (type) {
- case COLLECT_DIRS_ONLY:
- continue;
- case COLLECT_ALL:
- break;
- case COLLECT_EXECUTABLES:
- if (!is_executable(dir_fd, name)) {
- continue;
- }
- if (!dlen) {
- dirprefix = "./";
- dlen = 2;
- }
- break;
- default:
- BUG("unhandled FileCollectionType value");
- }
- }
-
- ptr_array_append(array, path_joinx(dirprefix, name, is_dir));
- }
-
- xclosedir(dir);
- return true;
-}
-
-static void collect_files(EditorState *e, CompletionState *cs, FileCollectionType type)
-{
- StringView esc = cs->escaped;
- if (strview_has_prefix(&esc, "~/")) {
- CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false);
- char *str = parse_command_arg(&runner, esc.data, esc.length, false);
- const char *slash = strrchr(str, '/');
- BUG_ON(!slash);
- cs->tilde_expanded = true;
- char *dir = path_dirname(cs->parsed);
- char *dirprefix = path_dirname(str);
- do_collect_files(&cs->completions, dir, dirprefix, slash + 1, type);
- free(dirprefix);
- free(dir);
- free(str);
- } else {
- const char *slash = strrchr(cs->parsed, '/');
- if (!slash) {
- do_collect_files(&cs->completions, ".", "", cs->parsed, type);
- } else {
- char *dir = path_dirname(cs->parsed);
- do_collect_files(&cs->completions, dir, dir, slash + 1, type);
- free(dir);
- }
- }
-
- if (cs->completions.count == 1) {
- // Add space if completed string is not a directory
- const char *s = cs->completions.ptrs[0];
- size_t len = strlen(s);
- if (len > 0) {
- cs->add_space_after_single_match = s[len - 1] != '/';
- }
- }
-}
-
-void collect_normal_aliases(EditorState *e, PointerArray *a, const char *prefix)
-{
- collect_hashmap_keys(&e->aliases, a, prefix);
-}
-
-static void collect_bound_keys(const IntMap *bindings, PointerArray *a, const char *prefix)
-{
- char keystr[KEYCODE_STR_MAX];
- for (IntMapIter it = intmap_iter(bindings); intmap_next(&it); ) {
- size_t keylen = keycode_to_string(it.entry->key, keystr);
- if (str_has_prefix(keystr, prefix)) {
- ptr_array_append(a, xmemdup(keystr, keylen + 1));
- }
- }
-}
-
-void collect_bound_normal_keys(EditorState *e, PointerArray *a, const char *prefix)
-{
- collect_bound_keys(&e->modes[INPUT_NORMAL].key_bindings, a, prefix);
-}
-
-void collect_hl_colors(EditorState *e, PointerArray *a, const char *prefix)
-{
- collect_builtin_colors(a, prefix);
- collect_hashmap_keys(&e->colors.other, a, prefix);
-}
-
-void collect_compilers(EditorState *e, PointerArray *a, const char *prefix)
-{
- collect_hashmap_keys(&e->compilers, a, prefix);
-}
-
-void collect_env(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix)
-{
- if (strchr(prefix, '=')) {
- return;
- }
-
- for (size_t i = 0; environ[i]; i++) {
- const char *var = environ[i];
- if (str_has_prefix(var, prefix)) {
- const char *delim = strchr(var, '=');
- if (likely(delim && delim != var)) {
- ptr_array_append(a, xstrcut(var, delim - var));
- }
- }
- }
-}
-
-static void complete_alias(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_normal_aliases(e, &cs->completions, cs->parsed);
- } else if (a->nr_args == 1 && cs->parsed[0] == '\0') {
- const char *cmd = find_alias(&e->aliases, a->args[0]);
- if (cmd) {
- ptr_array_append(&cs->completions, xstrdup(cmd));
- }
- }
-}
-
-static void complete_bind(EditorState *e, const CommandArgs *a)
-{
- static const char flags[] = {
- [INPUT_NORMAL] = 'n',
- [INPUT_COMMAND] = 'c',
- [INPUT_SEARCH] = 's',
- };
-
- static_assert(ARRAYLEN(flags) == ARRAYLEN(e->modes));
- InputMode mode = INPUT_NORMAL;
- for (size_t i = 0, count = 0; i < ARRAYLEN(flags); i++) {
- if (cmdargs_has_flag(a, flags[i])) {
- if (++count >= 2) {
- return; // Don't complete bindings for multiple modes
- }
- mode = i;
- }
- }
-
- const IntMap *key_bindings = &e->modes[mode].key_bindings;
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_bound_keys(key_bindings, &cs->completions, cs->parsed);
- return;
- }
-
- if (a->nr_args != 1 || cs->parsed[0] != '\0') {
- return;
- }
-
- KeyCode key;
- if (!parse_key_string(&key, a->args[0])) {
- return;
- }
- const CachedCommand *cmd = lookup_binding(key_bindings, key);
- if (!cmd) {
- return;
- }
-
- ptr_array_append(&cs->completions, xstrdup(cmd->cmd_str));
-}
-
-static void complete_cd(EditorState *e, const CommandArgs* UNUSED_ARG(a))
-{
- CompletionState *cs = &e->cmdline.completion;
- collect_files(e, cs, COLLECT_DIRS_ONLY);
- if (str_has_prefix("-", cs->parsed)) {
- if (likely(xgetenv("OLDPWD"))) {
- ptr_array_append(&cs->completions, xstrdup("-"));
- }
- }
-}
-
-static void complete_exec(EditorState *e, const CommandArgs *a)
-{
- // TODO: add completion for [-ioe] option arguments
- CompletionState *cs = &e->cmdline.completion;
- collect_files(e, cs, a->nr_args == 0 ? COLLECT_EXECUTABLES : COLLECT_ALL);
-}
-
-static void complete_compile(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- size_t n = a->nr_args;
- if (n == 0) {
- collect_compilers(e, &cs->completions, cs->parsed);
- } else {
- collect_files(e, cs, n == 1 ? COLLECT_EXECUTABLES : COLLECT_ALL);
- }
-}
-
-static void complete_cursor(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- size_t n = a->nr_args;
- if (n == 0) {
- collect_cursor_modes(&cs->completions, cs->parsed);
- } else if (n == 1) {
- collect_cursor_types(&cs->completions, cs->parsed);
- } else if (n == 2) {
- collect_cursor_colors(&cs->completions, cs->parsed);
- // Add an example #rrggbb color, to make things more discoverable
- static const char rgb_example[] = "#22AABB";
- if (str_has_prefix(rgb_example, cs->parsed)) {
- ptr_array_append(&cs->completions, xstrdup(rgb_example));
- }
- }
-}
-
-static void complete_errorfmt(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_compilers(e, &cs->completions, cs->parsed);
- } else if (a->nr_args >= 2 && !cmdargs_has_flag(a, 'i')) {
- collect_errorfmt_capture_names(&cs->completions, cs->parsed);
- }
-}
-
-static void complete_ft(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_ft(&e->filetypes, &cs->completions, cs->parsed);
- }
-}
-
-static void complete_hi(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_hl_colors(e, &cs->completions, cs->parsed);
- } else {
- collect_colors_and_attributes(&cs->completions, cs->parsed);
- }
-}
-
-static void complete_include(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- if (cmdargs_has_flag(a, 'b')) {
- collect_builtin_includes(&cs->completions, cs->parsed);
- } else {
- collect_files(e, cs, COLLECT_ALL);
- }
- }
-}
-
-static void complete_macro(EditorState *e, const CommandArgs *a)
-{
- static const char verbs[][8] = {
- "cancel",
- "play",
- "record",
- "stop",
- "toggle",
- };
-
- if (a->nr_args != 0) {
- return;
- }
-
- CompletionState *cs = &e->cmdline.completion;
- COLLECT_STRINGS(verbs, &cs->completions, cs->parsed);
-}
-
-static void complete_move_tab(EditorState *e, const CommandArgs *a)
-{
- if (a->nr_args != 0) {
- return;
- }
-
- static const char words[][8] = {"left", "right"};
- CompletionState *cs = &e->cmdline.completion;
- COLLECT_STRINGS(words, &cs->completions, cs->parsed);
-}
-
-static void complete_open(EditorState *e, const CommandArgs *a)
-{
- if (!cmdargs_has_flag(a, 't')) {
- collect_files(e, &e->cmdline.completion, COLLECT_ALL);
- }
-}
-
-static void complete_option(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- if (!cmdargs_has_flag(a, 'r')) {
- collect_ft(&e->filetypes, &cs->completions, cs->parsed);
- }
- } else if (a->nr_args & 1) {
- collect_auto_options(&cs->completions, cs->parsed);
- } else {
- collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed);
- }
-}
-
-static void complete_save(EditorState *e, const CommandArgs* UNUSED_ARG(a))
-{
- collect_files(e, &e->cmdline.completion, COLLECT_ALL);
-}
-
-static void complete_quit(EditorState *e, const CommandArgs* UNUSED_ARG(a))
-{
- CompletionState *cs = &e->cmdline.completion;
- if (str_has_prefix("0", cs->parsed)) {
- ptr_array_append(&cs->completions, xstrdup("0"));
- }
- if (str_has_prefix("1", cs->parsed)) {
- ptr_array_append(&cs->completions, xstrdup("1"));
- }
-}
-
-static void complete_redo(EditorState *e, const CommandArgs* UNUSED_ARG(a))
-{
- const Change *change = e->buffer->cur_change;
- CompletionState *cs = &e->cmdline.completion;
- for (unsigned long i = 1, n = change->nr_prev; i <= n; i++) {
- ptr_array_append(&cs->completions, xstrdup(ulong_to_str(i)));
- }
-}
-
-static void complete_set(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if ((a->nr_args + 1) & 1) {
- bool local = cmdargs_has_flag(a, 'l');
- bool global = cmdargs_has_flag(a, 'g');
- collect_options(&cs->completions, cs->parsed, local, global);
- } else {
- collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed);
- }
-}
-
-static void complete_setenv(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_env(e, &cs->completions, cs->parsed);
- } else if (a->nr_args == 1 && cs->parsed[0] == '\0') {
- BUG_ON(!a->args[0]);
- const char *value = getenv(a->args[0]);
- if (value) {
- ptr_array_append(&cs->completions, xstrdup(value));
- }
- }
-}
-
-static void complete_show(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- collect_show_subcommands(&cs->completions, cs->parsed);
- } else if (a->nr_args == 1) {
- BUG_ON(!a->args[0]);
- collect_show_subcommand_args(e, &cs->completions, a->args[0], cs->parsed);
- }
-}
-
-static void complete_tag(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0 && !cmdargs_has_flag(a, 'r')) {
- BUG_ON(!cs->parsed);
- StringView prefix = strview_from_cstring(cs->parsed);
- collect_tags(&e->tagfile, &cs->completions, &prefix);
- }
-}
-
-static void complete_toggle(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (a->nr_args == 0) {
- bool global = cmdargs_has_flag(a, 'g');
- collect_toggleable_options(&cs->completions, cs->parsed, global);
- }
-}
-
-static void complete_wsplit(EditorState *e, const CommandArgs *a)
-{
- CompletionState *cs = &e->cmdline.completion;
- if (!cmdargs_has_flag(a, 't') && !cmdargs_has_flag(a, 'n')) {
- collect_files(e, cs, COLLECT_ALL);
- }
-}
-
-typedef struct {
- char cmd_name[12];
- void (*complete)(EditorState *e, const CommandArgs *a);
-} CompletionHandler;
-
-static const CompletionHandler completion_handlers[] = {
- {"alias", complete_alias},
- {"bind", complete_bind},
- {"cd", complete_cd},
- {"compile", complete_compile},
- {"cursor", complete_cursor},
- {"errorfmt", complete_errorfmt},
- {"exec", complete_exec},
- {"ft", complete_ft},
- {"hi", complete_hi},
- {"include", complete_include},
- {"macro", complete_macro},
- {"move-tab", complete_move_tab},
- {"open", complete_open},
- {"option", complete_option},
- {"quit", complete_quit},
- {"redo", complete_redo},
- {"save", complete_save},
- {"set", complete_set},
- {"setenv", complete_setenv},
- {"show", complete_show},
- {"tag", complete_tag},
- {"toggle", complete_toggle},
- {"wsplit", complete_wsplit},
-};
-
-UNITTEST {
- CHECK_BSEARCH_ARRAY(completion_handlers, cmd_name, strcmp);
- // Ensure handlers are kept in sync with renamed/removed commands
- for (size_t i = 0; i < ARRAYLEN(completion_handlers); i++) {
- const char *name = completion_handlers[i].cmd_name;
- if (!find_normal_command(name)) {
- BUG("completion handler for non-existent command: \"%s\"", name);
- }
- }
-}
-
-static bool can_collect_flags (
- char **args,
- size_t argc,
- size_t nr_flag_args,
- bool allow_flags_after_nonflags
-) {
- if (allow_flags_after_nonflags) {
- for (size_t i = 0; i < argc; i++) {
- if (streq(args[i], "--")) {
- return false;
- }
- }
- return true;
- }
-
- for (size_t i = 0, nonflag = 0; i < argc; i++) {
- if (args[i][0] != '-') {
- if (++nonflag > nr_flag_args) {
- return false;
- }
- continue;
- }
- if (streq(args[i], "--")) {
- return false;
- }
- }
-
- return true;
-}
-
-static bool collect_command_flags (
- PointerArray *array,
- char **args,
- size_t argc,
- const Command *cmd,
- const CommandArgs *a,
- const char *prefix
-) {
- BUG_ON(prefix[0] != '-');
- const char *flags = cmd->flags;
- bool flags_after_nonflags = (flags[0] != '-');
-
- if (!can_collect_flags(args, argc, a->nr_flag_args, flags_after_nonflags)) {
- return false;
- }
-
- flags += flags_after_nonflags ? 0 : 1;
- if (ascii_isalnum(prefix[1]) && prefix[2] == '\0') {
- if (strchr(flags, prefix[1])) {
- ptr_array_append(array, xmemdup(prefix, 3));
- }
- return true;
- }
-
- if (prefix[1] != '\0') {
- return true;
- }
-
- char buf[3] = "-";
- for (size_t i = 0; flags[i]; i++) {
- if (!ascii_isalnum(flags[i]) || cmdargs_has_flag(a, flags[i])) {
- continue;
- }
- buf[1] = flags[i];
- ptr_array_append(array, xmemdup(buf, 3));
- }
-
- return true;
-}
-
-static void collect_completions(EditorState *e, char **args, size_t argc)
-{
- CompletionState *cs = &e->cmdline.completion;
- PointerArray *arr = &cs->completions;
- const char *prefix = cs->parsed;
- if (!argc) {
- collect_normal_commands(arr, prefix);
- collect_normal_aliases(e, arr, prefix);
- return;
- }
-
- for (size_t i = 0; i < argc; i++) {
- if (!args[i]) {
- // Embedded NULLs indicate there are multiple commands.
- // Just return early here and avoid handling this case.
- return;
- }
- }
-
- const Command *cmd = find_normal_command(args[0]);
- if (!cmd) {
- return;
- }
-
- char **args_copy = copy_string_array(args + 1, argc - 1);
- CommandArgs a = cmdargs_new(args_copy);
- ArgParseError err = do_parse_args(cmd, &a);
- bool dash = (prefix[0] == '-');
- if (
- (err != ARGERR_NONE && err != ARGERR_TOO_FEW_ARGUMENTS)
- || (a.nr_args >= cmd->max_args && cmd->max_args != 0xFF && !dash)
- ) {
- goto out;
- }
-
- if (dash && collect_command_flags(arr, args + 1, argc - 1, cmd, &a, prefix)) {
- goto out;
- }
-
- if (cmd->max_args == 0) {
- goto out;
- }
-
- const CompletionHandler *h = BSEARCH(args[0], completion_handlers, vstrcmp);
- if (h) {
- h->complete(e, &a);
- } else if (streq(args[0], "repeat")) {
- if (a.nr_args == 1) {
- collect_normal_commands(arr, prefix);
- } else if (a.nr_args >= 2) {
- collect_completions(e, args + 2, argc - 2);
- }
- }
-
-out:
- free_string_array(args_copy);
-}
-
-static bool is_var(const char *str, size_t len)
-{
- if (len == 0 || str[0] != '$') {
- return false;
- }
- if (len == 1) {
- return true;
- }
- if (!is_alpha_or_underscore(str[1])) {
- return false;
- }
- for (size_t i = 2; i < len; i++) {
- if (!is_alnum_or_underscore(str[i])) {
- return false;
- }
- }
- return true;
-}
-
-UNITTEST {
- BUG_ON(!is_var(STRN("$VAR")));
- BUG_ON(!is_var(STRN("$xy_190")));
- BUG_ON(!is_var(STRN("$__x_y_z")));
- BUG_ON(!is_var(STRN("$x")));
- BUG_ON(!is_var(STRN("$A")));
- BUG_ON(!is_var(STRN("$_0")));
- BUG_ON(!is_var(STRN("$")));
- BUG_ON(is_var(STRN("")));
- BUG_ON(is_var(STRN("A")));
- BUG_ON(is_var(STRN("$.a")));
- BUG_ON(is_var(STRN("$xyz!")));
- BUG_ON(is_var(STRN("$1")));
- BUG_ON(is_var(STRN("$09")));
- BUG_ON(is_var(STRN("$1a")));
-}
-
-static int strptrcmp(const void *v1, const void *v2)
-{
- const char *const *s1 = v1;
- const char *const *s2 = v2;
- return strcmp(*s1, *s2);
-}
-
-static void init_completion(EditorState *e, const CommandLine *cmdline)
-{
- CompletionState *cs = &e->cmdline.completion;
- const CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false);
- BUG_ON(cs->orig);
- BUG_ON(runner.userdata != e);
- BUG_ON(!runner.lookup_alias);
-
- const size_t cmdline_pos = cmdline->pos;
- char *const cmd = string_clone_cstring(&cmdline->buf);
- PointerArray array = PTR_ARRAY_INIT;
- ssize_t semicolon = -1;
- ssize_t completion_pos = -1;
-
- for (size_t pos = 0; true; ) {
- while (ascii_isspace(cmd[pos])) {
- pos++;
- }
-
- if (pos >= cmdline_pos) {
- completion_pos = cmdline_pos;
- break;
- }
-
- if (!cmd[pos]) {
- break;
- }
-
- if (cmd[pos] == ';') {
- semicolon = array.count;
- ptr_array_append(&array, NULL);
- pos++;
- continue;
- }
-
- CommandParseError err;
- size_t end = find_end(cmd, pos, &err);
- if (err != CMDERR_NONE || end >= cmdline_pos) {
- completion_pos = pos;
- break;
- }
-
- if (semicolon + 1 == array.count) {
- char *name = xstrslice(cmd, pos, end);
- const char *value = runner.lookup_alias(name, runner.userdata);
- if (value) {
- size_t save = array.count;
- if (parse_commands(&runner, &array, value) != CMDERR_NONE) {
- for (size_t i = save, n = array.count; i < n; i++) {
- free(array.ptrs[i]);
- array.ptrs[i] = NULL;
- }
- array.count = save;
- ptr_array_append(&array, parse_command_arg(&runner, name, end - pos, true));
- } else {
- // Remove NULL
- array.count--;
- }
- } else {
- ptr_array_append(&array, parse_command_arg(&runner, name, end - pos, true));
- }
- free(name);
- } else {
- ptr_array_append(&array, parse_command_arg(&runner, cmd + pos, end - pos, true));
- }
- pos = end;
- }
-
- const char *str = cmd + completion_pos;
- size_t len = cmdline_pos - completion_pos;
- if (is_var(str, len)) {
- char *name = xstrslice(str, 1, len);
- completion_pos++;
- collect_env(e, &cs->completions, name);
- collect_normal_vars(&cs->completions, name);
- free(name);
- } else {
- cs->escaped = string_view(str, len);
- cs->parsed = parse_command_arg(&runner, str, len, true);
- cs->add_space_after_single_match = true;
- size_t count = array.count;
- char **args = count ? (char**)array.ptrs + 1 + semicolon : NULL;
- size_t argc = count ? array.count - semicolon - 1 : 0;
- collect_completions(e, args, argc);
- }
-
- ptr_array_free(&array);
- ptr_array_sort(&cs->completions, strptrcmp);
- cs->orig = cmd; // (takes ownership)
- cs->tail = strview_from_cstring(cmd + cmdline_pos);
- cs->head_len = completion_pos;
-}
-
-static void do_complete_command(CommandLine *cmdline)
-{
- const CompletionState *cs = &cmdline->completion;
- const PointerArray *arr = &cs->completions;
- const StringView middle = strview_from_cstring(arr->ptrs[cs->idx]);
- const StringView tail = cs->tail;
- const size_t head_length = cs->head_len;
-
- String buf = string_new(head_length + tail.length + middle.length + 16);
- string_append_buf(&buf, cs->orig, head_length);
- string_append_escaped_arg_sv(&buf, middle, !cs->tilde_expanded);
-
- bool single_completion = (arr->count == 1);
- if (single_completion && cs->add_space_after_single_match) {
- string_append_byte(&buf, ' ');
- }
-
- size_t pos = buf.len;
- string_append_strview(&buf, &tail);
- cmdline_set_text(cmdline, string_borrow_cstring(&buf));
- cmdline->pos = pos;
- string_free(&buf);
-
- if (single_completion) {
- reset_completion(cmdline);
- }
-}
-
-void complete_command_next(EditorState *e)
-{
- CompletionState *cs = &e->cmdline.completion;
- const bool init = !cs->orig;
- if (init) {
- init_completion(e, &e->cmdline);
- }
- size_t count = cs->completions.count;
- if (!count) {
- return;
- }
- if (!init) {
- cs->idx = size_increment_wrapped(cs->idx, count);
- }
- do_complete_command(&e->cmdline);
-}
-
-void complete_command_prev(EditorState *e)
-{
- CompletionState *cs = &e->cmdline.completion;
- const bool init = !cs->orig;
- if (init) {
- init_completion(e, &e->cmdline);
- }
- size_t count = cs->completions.count;
- if (!count) {
- return;
- }
- if (!init) {
- cs->idx = size_decrement_wrapped(cs->idx, count);
- }
- do_complete_command(&e->cmdline);
-}
-
-void reset_completion(CommandLine *cmdline)
-{
- CompletionState *cs = &cmdline->completion;
- free(cs->parsed);
- free(cs->orig);
- ptr_array_free(&cs->completions);
- *cs = (CompletionState){.orig = NULL};
-}
-
-void collect_hashmap_keys(const HashMap *map, PointerArray *a, const char *prefix)
-{
- for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); ) {
- const char *name = it.entry->key;
- if (str_has_prefix(name, prefix)) {
- ptr_array_append(a, xstrdup(name));
- }
- }
-}