From c00eb27d07cc218c1b889e1e32a5f5ac4e40c308 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Thu, 27 Nov 2008 17:29:03 +0100 Subject: [PATCH] IO API: refactor the run request command formatter Adds support for new %(file), %(directory) and %(ref) identifiers. Also adds infrastructure for working with argument arrays --- NEWS | 1 + manual.txt | 4 + tig.c | 219 ++++++++++++++++++++++++++++++++++++---------------- tigrc.5.txt | 4 + 4 files changed, 163 insertions(+), 65 deletions(-) diff --git a/NEWS b/NEWS index ece7619..3532b5d 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ Improvements: tracked remotes, remotes. - Add bash completion for blame. - Tree view: edit files of the current branch. + - Run requests: new identifiers %(directory), %(file), and %(ref) Bug fixes: diff --git a/manual.txt b/manual.txt index 447dec0..8ac7cd2 100644 --- a/manual.txt +++ b/manual.txt @@ -147,6 +147,10 @@ Browsing state variables %(head) The currently viewed 'head' ID. Defaults to HEAD %(commit) The currently selected commit ID. %(blob) The currently selected blob ID. +%(directory) The current directory path in the tree view; \ + empty for the root directory. +%(file) The currently selected file. +%(ref) The reference given to blame or HEAD if undefined. ------------------------------------------------------------------------------ [[title-window]] diff --git a/tig.c b/tig.c index 6486530..3fce6f0 100644 --- a/tig.c +++ b/tig.c @@ -166,6 +166,15 @@ struct ref { static struct ref **get_refs(const char *id); +enum format_flags { + FORMAT_ALL, /* Perform replacement in all arguments. */ + FORMAT_DASH, /* Perform replacement up until "--". */ + FORMAT_NONE /* No replacement should be performed. */ +}; + +static bool format_command(char dst[], const char *src[], enum format_flags flags); +static bool format_argv(const char *dst[], const char *src[], enum format_flags flags); + struct int_map { const char *name; int namelen; @@ -332,6 +341,24 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) return bufsize; } +static bool +argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) +{ + int valuelen; + + while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) { + bool advance = cmd[valuelen] != 0; + + cmd[valuelen] = 0; + argv[(*argc)++] = chomp_string(cmd); + cmd += valuelen + advance; + } + + if (*argc < SIZEOF_ARG) + argv[*argc] = NULL; + return *argc < SIZEOF_ARG; +} + /* * Executing external commands. @@ -1105,7 +1132,7 @@ get_key(enum request request) struct run_request { enum keymap keymap; int key; - char cmd[SIZEOF_STR]; + const char *argv[SIZEOF_ARG]; }; static struct run_request *run_request; @@ -1115,24 +1142,24 @@ static enum request add_run_request(enum keymap keymap, int key, int argc, const char **argv) { struct run_request *req; - char cmd[SIZEOF_STR]; - size_t bufpos; - for (bufpos = 0; argc > 0; argc--, argv++) - if (!string_format_from(cmd, &bufpos, "%s ", *argv)) - return REQ_NONE; + if (argc >= ARRAY_SIZE(req->argv) - 1) + return REQ_NONE; req = realloc(run_request, (run_requests + 1) * sizeof(*run_request)); if (!req) return REQ_NONE; run_request = req; - req = &run_request[run_requests++]; - string_copy(req->cmd, cmd); + req = &run_request[run_requests]; req->keymap = keymap; req->key = key; + req->argv[0] = NULL; - return REQ_NONE + run_requests; + if (!format_argv(req->argv, argv, FORMAT_NONE)) + return REQ_NONE; + + return REQ_NONE + ++run_requests; } static struct run_request * @@ -1146,20 +1173,23 @@ get_run_request(enum request request) static void add_builtin_run_requests(void) { + const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL }; + const char *gc[] = { "git", "gc", NULL }; struct { enum keymap keymap; int key; - const char *argv[1]; + int argc; + const char **argv; } reqs[] = { - { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } }, - { KEYMAP_GENERIC, 'G', { "git gc" } }, + { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick }, + { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc }, }; int i; for (i = 0; i < ARRAY_SIZE(reqs); i++) { enum request req; - req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv); + req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv); if (req != REQ_NONE) add_keybinding(reqs[i].keymap, req, reqs[i].key); } @@ -1391,21 +1421,11 @@ static int set_option(const char *opt, char *value) { const char *argv[SIZEOF_ARG]; - int valuelen; int argc = 0; - /* Tokenize */ - while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) { - argv[argc++] = value; - value += valuelen; - - /* Nothing more to tokenize or last available token. */ - if (!*value || argc >= ARRAY_SIZE(argv)) - break; - - *value++ = 0; - while (isspace(*value)) - value++; + if (!argv_from_string(argv, &argc, value)) { + config_msg = "Too many option arguments"; + return ERR; } if (!strcmp(opt, "color")) @@ -2341,6 +2361,103 @@ reset_view(struct view *view) view->vid[0] = 0; } +static void +free_argv(const char *argv[]) +{ + int argc; + + for (argc = 0; argv[argc]; argc++) + free((void *) argv[argc]); +} + +static bool +format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags) +{ + char buf[SIZEOF_STR]; + int argc; + bool noreplace = flags == FORMAT_NONE; + + free_argv(dst_argv); + + for (argc = 0; src_argv[argc]; argc++) { + const char *arg = src_argv[argc]; + size_t bufpos = 0; + + while (arg) { + char *next = strstr(arg, "%("); + int len = next - arg; + const char *value; + + if (!next || noreplace) { + if (flags == FORMAT_DASH && !strcmp(arg, "--")) + noreplace = TRUE; + len = strlen(arg); + value = ""; + + } else if (!prefixcmp(next, "%(directory)")) { + value = opt_path; + + } else if (!prefixcmp(next, "%(file)")) { + value = opt_file; + + } else if (!prefixcmp(next, "%(ref)")) { + value = *opt_ref ? opt_ref : "HEAD"; + + } else if (!prefixcmp(next, "%(head)")) { + value = ref_head; + + } else if (!prefixcmp(next, "%(commit)")) { + value = ref_commit; + + } else if (!prefixcmp(next, "%(blob)")) { + value = ref_blob; + + } else { + report("Unknown replacement: `%s`", next); + return FALSE; + } + + if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value)) + return FALSE; + + arg = next && !noreplace ? strchr(next, ')') + 1 : NULL; + } + + dst_argv[argc] = strdup(buf); + if (!dst_argv[argc]) + break; + } + + dst_argv[argc] = NULL; + + return src_argv[argc] == NULL; +} + +static bool +format_command(char dst[], const char *src_argv[], enum format_flags flags) +{ + const char *dst_argv[SIZEOF_ARG * 2] = { NULL }; + int bufsize = 0; + int argc; + + if (!format_argv(dst_argv, src_argv, flags)) { + free_argv(dst_argv); + return FALSE; + } + + for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) { + if (bufsize > 0) + dst[bufsize++] = ' '; + bufsize = sq_quote(dst, bufsize, dst_argv[argc]); + } + + if (bufsize < SIZEOF_STR) + dst[bufsize] = 0; + free_argv(dst_argv); + + return src_argv[argc] == NULL && bufsize < SIZEOF_STR; +} + static void end_update(struct view *view, bool force) { @@ -2746,49 +2863,14 @@ open_run_request(enum request request) { struct run_request *req = get_run_request(request); char buf[SIZEOF_STR * 2]; - size_t bufpos; - char *cmd; if (!req) { report("Unknown run request"); return; } - bufpos = 0; - cmd = req->cmd; - - while (cmd) { - char *next = strstr(cmd, "%("); - int len = next - cmd; - char *value; - - if (!next) { - len = strlen(cmd); - value = ""; - - } else if (!strncmp(next, "%(head)", 7)) { - value = ref_head; - - } else if (!strncmp(next, "%(commit)", 9)) { - value = ref_commit; - - } else if (!strncmp(next, "%(blob)", 7)) { - value = ref_blob; - - } else { - report("Unknown replacement in run request: `%s`", req->cmd); - return; - } - - if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value)) - return; - - if (next) - next = strchr(next, ')') + 1; - cmd = next; - } - - open_external_viewer(buf); + if (format_command(buf, req->argv, FORMAT_ALL)) + open_external_viewer(buf); } /* @@ -3293,6 +3375,9 @@ help_open(struct view *view) for (i = 0; i < run_requests; i++) { struct run_request *req = get_run_request(REQ_NONE + i + 1); const char *key; + char cmd[SIZEOF_STR]; + size_t bufpos; + int argc; if (!req) continue; @@ -3301,9 +3386,13 @@ help_open(struct view *view) if (!*key) key = "(no key defined)"; + for (bufpos = 0, argc = 0; req->argv[argc]; argc++) + if (!string_format_from(cmd, &bufpos, "%s%s", + argc ? " " : "", req->argv[argc])) + return REQ_NONE; + if (!string_format(buf, " %-10s %-14s `%s`", - keymap_table[req->keymap].name, - key, req->cmd)) + keymap_table[req->keymap].name, key, cmd)) continue; add_line_text(view, buf, LINE_DEFAULT); diff --git a/tigrc.5.txt b/tigrc.5.txt index 4665649..7c1daec 100644 --- a/tigrc.5.txt +++ b/tigrc.5.txt @@ -184,6 +184,10 @@ Browsing state variables %(head) The currently viewed 'head' ID. Defaults to HEAD %(commit) The currently selected commit ID. %(blob) The currently selected blob ID. +%(directory) The current directory path in the tree view; \ + empty for the root directory. +%(file) The currently selected file. +%(ref) The reference given to blame or HEAD if undefined. ------------------------------------------------------------------------------ As an example, the following external command will save the current commit as -- 2.32.0.93.g670b81a890