X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/93a97d864dc8d4fa2ae51a4cb2d742e4ae9d2320..73fb51d547de503a6c58f20507ee9017bf29ce97:/tig.c diff --git a/tig.c b/tig.c index 708213e..5eded68 100644 --- a/tig.c +++ b/tig.c @@ -12,7 +12,7 @@ */ #ifndef VERSION -#define VERSION "tig-0.3" +#define VERSION "tig-0.4.git" #endif #ifndef DEBUG @@ -74,13 +74,13 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, "git ls-remote . 2>/dev/null" #define TIG_DIFF_CMD \ - "git show --patch-with-stat --find-copies-harder -B -C %s" + "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" #define TIG_LOG_CMD \ - "git log --cc --stat -n100 %s" + "git log --cc --stat -n100 %s 2>/dev/null" #define TIG_MAIN_CMD \ - "git log --topo-order --stat --pretty=raw %s" + "git log --topo-order --pretty=raw %s 2>/dev/null" /* XXX: Needs to be defined to the empty string. */ #define TIG_HELP_CMD "" @@ -284,7 +284,7 @@ sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src) REQ_(SHOW_VERSION, "Show version information"), \ REQ_(STOP_LOADING, "Stop all loading views"), \ REQ_(TOGGLE_LINENO, "Toggle line numbers"), \ - REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), + REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization") /* User action requests. */ @@ -294,7 +294,8 @@ enum request { /* Offset all requests to avoid conflicts with ncurses getch values. */ REQ_OFFSET = KEY_MAX + 1, - REQ_INFO + REQ_INFO, + REQ_UNKNOWN, #undef REQ_GROUP #undef REQ_ @@ -302,17 +303,34 @@ enum request { struct request_info { enum request request; + char *name; + int namelen; char *help; }; static struct request_info req_info[] = { -#define REQ_GROUP(help) { 0, (help) }, -#define REQ_(req, help) { REQ_##req, (help) } +#define REQ_GROUP(help) { 0, NULL, 0, (help) }, +#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) } REQ_INFO #undef REQ_GROUP #undef REQ_ }; +static enum request +get_request(const char *name) +{ + int namelen = strlen(name); + int i; + + for (i = 0; i < ARRAY_SIZE(req_info); i++) + if (req_info[i].namelen == namelen && + !string_enum_compare(req_info[i].name, name, namelen)) + return req_info[i].request; + + return REQ_UNKNOWN; +} + + /* * Options */ @@ -619,6 +637,7 @@ struct line { struct keybinding { int alias; enum request request; + struct keybinding *next; }; static struct keybinding default_keybindings[] = { @@ -660,7 +679,7 @@ static struct keybinding default_keybindings[] = { { 'v', REQ_SHOW_VERSION }, { 'r', REQ_SCREEN_REDRAW }, { 'n', REQ_TOGGLE_LINENO }, - { 'g', REQ_TOGGLE_REV_GRAPH}, + { 'g', REQ_TOGGLE_REV_GRAPH }, { ':', REQ_PROMPT }, /* wgetch() with nodelay() enabled returns ERR when there's no input. */ @@ -670,11 +689,62 @@ static struct keybinding default_keybindings[] = { { KEY_RESIZE, REQ_SCREEN_RESIZE }, }; +#define KEYMAP_INFO \ + KEYMAP_(GENERIC), \ + KEYMAP_(MAIN), \ + KEYMAP_(DIFF), \ + KEYMAP_(LOG), \ + KEYMAP_(PAGER), \ + KEYMAP_(HELP) \ + +enum keymap { +#define KEYMAP_(name) KEYMAP_##name + KEYMAP_INFO +#undef KEYMAP_ +}; + +static struct int_map keymap_table[] = { +#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name } + KEYMAP_INFO +#undef KEYMAP_ +}; + +#define set_keymap(map, name) \ + set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name)) + +static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)]; + +static void +add_keybinding(enum keymap keymap, enum request request, int key) +{ + struct keybinding *keybinding; + + keybinding = calloc(1, sizeof(*keybinding)); + if (!keybinding) + die("Failed to allocate keybinding"); + + keybinding->alias = key; + keybinding->request = request; + keybinding->next = keybindings[keymap]; + keybindings[keymap] = keybinding; +} + +/* Looks for a key binding first in the given map, then in the generic map, and + * lastly in the default keybindings. */ static enum request -get_keybinding(int key) +get_keybinding(enum keymap keymap, int key) { + struct keybinding *kbd; int i; + for (kbd = keybindings[keymap]; kbd; kbd = kbd->next) + if (kbd->alias == key) + return kbd->request; + + for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next) + if (kbd->alias == key) + return kbd->request; + for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) if (default_keybindings[i].alias == key) return default_keybindings[i].request; @@ -700,6 +770,7 @@ static struct key key_table[] = { { "Down", KEY_DOWN }, { "Insert", KEY_IC }, { "Delete", KEY_DC }, + { "Hash", '#' }, { "Home", KEY_HOME }, { "End", KEY_END }, { "PageUp", KEY_PPAGE }, @@ -718,6 +789,21 @@ static struct key key_table[] = { { "F12", KEY_F(12) }, }; +static int +get_key_value(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(key_table); i++) + if (!strcasecmp(key_table[i].name, name)) + return key_table[i].value; + + if (strlen(name) == 1 && isprint(*name)) + return (int) *name; + + return ERR; +} + static char * get_key(enum request request) { @@ -815,12 +901,8 @@ option_color_command(int argc, char *argv[]) return ERR; } - if (set_color(&info->fg, argv[1]) == ERR) { - config_msg = "Unknown color"; - return ERR; - } - - if (set_color(&info->bg, argv[2]) == ERR) { + if (set_color(&info->fg, argv[1]) == ERR || + set_color(&info->bg, argv[2]) == ERR) { config_msg = "Unknown color"; return ERR; } @@ -864,14 +946,64 @@ option_set_command(int argc, char *argv[]) return OK; } - if (!strcmp(argv[0], "encoding")) { - string_copy(opt_encoding, argv[2]); - return OK; + if (!strcmp(argv[0], "commit-encoding")) { + char *arg = argv[2]; + int delimiter = *arg; + int i; + + switch (delimiter) { + case '"': + case '\'': + for (arg++, i = 0; arg[i]; i++) + if (arg[i] == delimiter) { + arg[i] = 0; + break; + } + default: + string_copy(opt_encoding, arg); + return OK; + } } + config_msg = "Unknown variable name"; return ERR; } +/* Wants: mode request key */ +static int +option_bind_command(int argc, char *argv[]) +{ + enum request request; + int keymap; + int key; + + if (argc != 3) { + config_msg = "Wrong number of arguments given to bind command"; + return ERR; + } + + if (set_keymap(&keymap, argv[0]) == ERR) { + config_msg = "Unknown key map"; + return ERR; + } + + key = get_key_value(argv[1]); + if (key == ERR) { + config_msg = "Unknown key"; + return ERR; + } + + request = get_request(argv[2]); + if (request == REQ_UNKNOWN) { + config_msg = "Unknown request name"; + return ERR; + } + + add_keybinding(keymap, request, key); + + return OK; +} + static int set_option(char *opt, char *value) { @@ -898,33 +1030,45 @@ set_option(char *opt, char *value) if (!strcmp(opt, "set")) return option_set_command(argc, argv); + if (!strcmp(opt, "bind")) + return option_bind_command(argc, argv); + + config_msg = "Unknown option command"; return ERR; } static int read_option(char *opt, int optlen, char *value, int valuelen) { + int status = OK; + config_lineno++; config_msg = "Internal error"; - optlen = strcspn(opt, "#;"); - if (optlen == 0) { - /* The whole line is a commend or empty. */ + /* Check for comment markers, since read_properties() will + * only ensure opt and value are split at first " \t". */ + optlen = strcspn(opt, "#"); + if (optlen == 0) return OK; - } else if (opt[optlen] != 0) { - /* Part of the option name is a comment, so the value part - * should be ignored. */ - valuelen = 0; - opt[optlen] = value[valuelen] = 0; - } else { - /* Else look for comment endings in the value. */ - valuelen = strcspn(value, "#;"); - value[valuelen] = 0; + if (opt[optlen] != 0) { + config_msg = "No option value"; + status = ERR; + + } else { + /* Look for comment endings in the value. */ + int len = strcspn(value, "#"); + + if (len < valuelen) { + valuelen = len; + value[valuelen] = 0; + } + + status = set_option(opt, value); } - if (set_option(opt, value) == ERR) { - fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n", + if (status == ERR) { + fprintf(stderr, "Error on line %d, near '%.*s': %s\n", config_lineno, optlen, opt, config_msg); config_errors = TRUE; } @@ -987,6 +1131,8 @@ struct view { struct view_ops *ops; /* View operations */ + enum keymap keymap; /* What keymap does this view have */ + char cmd[SIZEOF_CMD]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */ @@ -1028,11 +1174,11 @@ struct view_ops { static struct view_ops pager_ops; static struct view_ops main_ops; -#define VIEW_STR(name, cmd, env, ref, ops) \ - { name, cmd, #env, ref, ops } +#define VIEW_STR(name, cmd, env, ref, ops, map) \ + { name, cmd, #env, ref, ops, map} #define VIEW_(id, name, ops, ref) \ - VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops) + VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id) static struct view views[] = { @@ -2140,10 +2286,22 @@ main_read(struct view *view, char *line) break; if (end) { + char *email = end + 1; + for (; end > ident && isspace(end[-1]); end--) ; + + if (end == ident && *email) { + ident = email; + end = strchr(ident, '>'); + for (; end > ident && isspace(end[-1]); end--) ; + } *end = 0; } + /* End is NULL or ident meaning there's no author. */ + if (end <= ident) + ident = "Unknown"; + string_copy(commit->author, ident); /* Parse epoch and timezone */ @@ -2437,6 +2595,8 @@ init_display(void) /* Leave stdin and stdout alone when acting as a pager. */ FILE *io = fopen("/dev/tty", "r+"); + if (!io) + die("Failed to open /dev/tty"); cursed = !!newterm(NULL, io, io); } @@ -2461,6 +2621,68 @@ init_display(void) wbkgdset(status_win, get_line_attr(LINE_STATUS)); } +static int +read_prompt(void) +{ + enum { READING, STOP, CANCEL } status = READING; + char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")]; + int pos = 0; + + while (status == READING) { + struct view *view; + int i, key; + + foreach_view (view, i) + update_view(view); + + report(":%.*s", pos, buf); + /* Refresh, accept single keystroke of input */ + key = wgetch(status_win); + switch (key) { + case KEY_RETURN: + case KEY_ENTER: + case '\n': + status = pos ? STOP : CANCEL; + break; + + case KEY_BACKSPACE: + if (pos > 0) + pos--; + else + status = CANCEL; + break; + + case KEY_ESC: + status = CANCEL; + break; + + case ERR: + break; + + default: + if (pos >= sizeof(buf)) { + report("Input string too long"); + return ERR; + } + + if (isprint(key)) + buf[pos++] = (char) key; + } + } + + if (status == CANCEL) { + /* Clear the status window */ + report(""); + return ERR; + } + + buf[pos++] = 0; + if (!string_format(opt_cmd, "git %s", buf)) + return ERR; + opt_request = REQ_VIEW_PAGER; + + return OK; +} /* * Repository references @@ -2703,29 +2925,15 @@ main(int argc, char *argv[]) /* Refresh, accept single keystroke of input */ key = wgetch(status_win); - request = get_keybinding(key); + + request = get_keybinding(display[current_view]->keymap, key); /* Some low-level request handling. This keeps access to * status_win restricted. */ switch (request) { case REQ_PROMPT: - report(":"); - /* Temporarily switch to line-oriented and echoed - * input. */ - nocbreak(); - echo(); - - if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) { - memcpy(opt_cmd, "git ", 4); - opt_request = REQ_VIEW_PAGER; - } else { - report("Prompt interrupted by loading view, " - "press 'z' to stop loading views"); + if (read_prompt() == ERR) request = REQ_SCREEN_UPDATE; - } - - noecho(); - cbreak(); break; case REQ_SCREEN_RESIZE: