X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=222b3698de6bcc60395ab74519b4c9048e2e5ed6;hb=29b1fccb6aaa8140fc2fdf82d750891444379ce1;hp=7a996dca08ca43e21b13428ebfdb3f4d0109cd42;hpb=129cf793c915ac00dac86c561c25099cd3cd4be0;p=tig.git diff --git a/tig.c b/tig.c index 7a996dc..222b369 100644 --- a/tig.c +++ b/tig.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,7 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma #define ABS(x) ((x) >= 0 ? (x) : -(x)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #define STRING_SIZE(x) (sizeof(x) - 1) @@ -102,25 +104,16 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma /* The format and size of the date column in the main view. */ #define DATE_FORMAT "%Y-%m-%d %H:%M" #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ") +#define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ") -#define AUTHOR_COLS 20 #define ID_COLS 8 -/* The default interval between line numbers. */ -#define NUMBER_INTERVAL 5 - -#define TAB_SIZE 8 - -#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3) +#define MIN_VIEW_HEIGHT 4 #define NULL_ID "0000000000000000000000000000000000000000" #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000) -#ifndef GIT_CONFIG -#define GIT_CONFIG "config" -#endif - /* Some ASCII-shorthands fitted into the ncurses namespace. */ #define KEY_TAB '\t' #define KEY_RETURN '\r' @@ -167,6 +160,14 @@ typedef enum input_status (*input_handler)(void *data, char *buf, int c); static char *prompt_input(const char *prompt, input_handler handler, void *data); static bool prompt_yesno(const char *prompt); +struct menu_item { + int hotkey; + const char *text; + void *data; +}; + +static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected); + /* * Allocation helpers ... Entering macro hell to never be seen again. */ @@ -336,12 +337,75 @@ suffixcmp(const char *str, int slen, const char *suffix) } -static const char * -mkdate(const time_t *time) +/* + * What value of "tz" was in effect back then at "time" in the + * local timezone? + */ +static int local_tzoffset(time_t time) +{ + time_t t, t_local; + struct tm tm; + int offset, eastwest; + + t = time; + localtime_r(&t, &tm); + t_local = mktime(&tm); + + if (t_local < t) { + eastwest = -1; + offset = t - t_local; + } else { + eastwest = 1; + offset = t_local - t; + } + offset /= 60; /* in minutes */ + offset = (offset % 60) + ((offset / 60) * 100); + return offset * eastwest; +} + +enum date { + DATE_NONE = 0, + DATE_DEFAULT, + DATE_RELATIVE, + DATE_SHORT +}; + +static char * +string_date(const time_t *time, enum date date) { static char buf[DATE_COLS + 1]; + static const struct enum_map reldate[] = { + { "second", 1, 60 * 2 }, + { "minute", 60, 60 * 60 * 2 }, + { "hour", 60 * 60, 60 * 60 * 24 * 2 }, + { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 }, + { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 }, + { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 }, + }; struct tm tm; + if (date == DATE_RELATIVE) { + struct timeval now; + time_t date = *time + local_tzoffset(*time); + time_t seconds; + int i; + + gettimeofday(&now, NULL); + seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date; + for (i = 0; i < ARRAY_SIZE(reldate); i++) { + if (seconds >= reldate[i].value) + continue; + + seconds /= reldate[i].namelen; + if (!string_format(buf, "%ld %s%s %s", + seconds, reldate[i].name, + seconds > 1 ? "s" : "", + now.tv_sec >= date ? "ago" : "ahead")) + break; + return buf; + } + } + gmtime_r(time, &tm); return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL; } @@ -433,9 +497,22 @@ init_io_rd(struct io *io, const char *argv[], const char *dir, } static bool -io_open(struct io *io, const char *name) +io_open(struct io *io, const char *fmt, ...) { + char name[SIZEOF_STR] = ""; + bool fits; + va_list args; + init_io(io, NULL, IO_FD); + + va_start(args, fmt); + fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name); + va_end(args); + + if (!fits) { + io->error = ENAMETOOLONG; + return FALSE; + } io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO; if (io->pipe == -1) io->error = errno; @@ -581,9 +658,9 @@ run_io_append(const char **argv, enum format_flags flags, int fd) } static bool -run_io_rd(struct io *io, const char **argv, enum format_flags flags) +run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags) { - return init_io_rd(io, argv, NULL, flags) && start_io(io); + return init_io_rd(io, argv, dir, flags) && start_io(io); } static bool @@ -720,7 +797,8 @@ run_io_buf(const char **argv, char buf[], size_t bufsize) { struct io io = {}; - return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize); + return run_io_rd(&io, argv, NULL, FORMAT_NONE) + && io_read_buf(&io, buf, bufsize); } static int @@ -831,8 +909,10 @@ run_io_load(const char **argv, const char *separators, REQ_(FIND_PREV, "Find previous search match"), \ \ REQ_GROUP("Option manipulation") \ + REQ_(OPTIONS, "Open option menu"), \ REQ_(TOGGLE_LINENO, "Toggle line numbers"), \ REQ_(TOGGLE_DATE, "Toggle date display"), \ + REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \ REQ_(TOGGLE_AUTHOR, "Toggle author display"), \ REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \ REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \ @@ -896,16 +976,17 @@ get_request(const char *name) */ /* Option and state variables. */ -static bool opt_date = TRUE; +static enum date opt_date = DATE_DEFAULT; static bool opt_author = TRUE; static bool opt_line_number = FALSE; static bool opt_line_graphics = TRUE; static bool opt_rev_graph = FALSE; static bool opt_show_refs = TRUE; -static int opt_num_interval = NUMBER_INTERVAL; +static int opt_num_interval = 5; static double opt_hscroll = 0.50; -static int opt_tab_size = TAB_SIZE; -static int opt_author_cols = AUTHOR_COLS-1; +static double opt_scale_split_view = 2.0 / 3.0; +static int opt_tab_size = 8; +static int opt_author_cols = 19; static char opt_path[SIZEOF_STR] = ""; static char opt_file[SIZEOF_STR] = ""; static char opt_ref[SIZEOF_REF] = ""; @@ -926,6 +1007,7 @@ static FILE *opt_tty = NULL; #define is_initial_commit() (!*opt_head_rev) #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev))) +#define mkdate(time) string_date(time, opt_date) /* @@ -987,6 +1069,8 @@ LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \ LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ +LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \ +LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \ LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0) enum line_type { @@ -1078,6 +1162,7 @@ struct line { unsigned int selected:1; unsigned int dirty:1; unsigned int cleareol:1; + unsigned int other:16; void *data; /* User data */ }; @@ -1146,6 +1231,7 @@ static const struct keybinding default_keybindings[] = { { 'z', REQ_STOP_LOADING }, { 'v', REQ_SHOW_VERSION }, { 'r', REQ_SCREEN_REDRAW }, + { 'o', REQ_OPTIONS }, { '.', REQ_TOGGLE_LINENO }, { 'D', REQ_TOGGLE_DATE }, { 'A', REQ_TOGGLE_AUTHOR }, @@ -1304,26 +1390,68 @@ get_key_name(int key_value) return seq ? seq : "(no key)"; } +static bool +append_key(char *buf, size_t *pos, const struct keybinding *keybinding) +{ + const char *sep = *pos > 0 ? ", " : ""; + const char *keyname = get_key_name(keybinding->alias); + + return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname); +} + +static bool +append_keymap_request_keys(char *buf, size_t *pos, enum request request, + enum keymap keymap, bool all) +{ + int i; + + for (i = 0; i < keybindings[keymap].size; i++) { + if (keybindings[keymap].data[i].request == request) { + if (!append_key(buf, pos, &keybindings[keymap].data[i])) + return FALSE; + if (!all) + break; + } + } + + return TRUE; +} + +#define get_key(keymap, request) get_keys(keymap, request, FALSE) + static const char * -get_key(enum request request) +get_keys(enum keymap keymap, enum request request, bool all) { static char buf[BUFSIZ]; size_t pos = 0; - char *sep = ""; int i; buf[pos] = 0; - for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) { - const struct keybinding *keybinding = &default_keybindings[i]; + if (!append_keymap_request_keys(buf, &pos, request, keymap, all)) + return "Too many keybindings!"; + if (pos > 0 && !all) + return buf; - if (keybinding->request != request) - continue; + if (keymap != KEYMAP_GENERIC) { + /* Only the generic keymap includes the default keybindings when + * listing all keys. */ + if (all) + return buf; - if (!string_format_from(buf, &pos, "%s%s", sep, - get_key_name(keybinding->alias))) + if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all)) return "Too many keybindings!"; - sep = ", "; + if (pos) + return buf; + } + + for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) { + if (default_keybindings[i].request == request) { + if (!append_key(buf, &pos, &default_keybindings[i])) + return "Too many keybindings!"; + if (!all) + return buf; + } } return buf; @@ -1482,7 +1610,7 @@ option_color_command(int argc, const char *argv[]) { struct line_info *info; - if (argc != 3 && argc != 4) { + if (argc < 3) { config_msg = "Wrong number of arguments given to color command"; return ERR; } @@ -1509,9 +1637,15 @@ option_color_command(int argc, const char *argv[]) return ERR; } - if (argc == 4 && !set_attribute(&info->attr, argv[3])) { - config_msg = "Unknown attribute"; - return ERR; + info->attr = 0; + while (argc-- > 3) { + int attr; + + if (!set_attribute(&attr, argv[argc])) { + config_msg = "Unknown attribute"; + return ERR; + } + info->attr |= attr; } return OK; @@ -1560,8 +1694,20 @@ option_set_command(int argc, const char *argv[]) if (!strcmp(argv[0], "show-author")) return parse_bool(&opt_author, argv[2]); - if (!strcmp(argv[0], "show-date")) - return parse_bool(&opt_date, argv[2]); + if (!strcmp(argv[0], "show-date")) { + bool show_date; + + if (!strcmp(argv[2], "relative")) { + opt_date = DATE_RELATIVE; + return OK; + } else if (!strcmp(argv[2], "short")) { + opt_date = DATE_SHORT; + return OK; + } else if (parse_bool(&show_date, argv[2])) { + opt_date = show_date ? DATE_DEFAULT : DATE_NONE; + } + return ERR; + } if (!strcmp(argv[0], "show-rev-graph")) return parse_bool(&opt_rev_graph, argv[2]); @@ -1584,6 +1730,9 @@ option_set_command(int argc, const char *argv[]) if (!strcmp(argv[0], "horizontal-scroll")) return parse_step(&opt_hscroll, argv[2]); + if (!strcmp(argv[0], "split-view-height")) + return parse_step(&opt_scale_split_view, argv[2]); + if (!strcmp(argv[0], "tab-size")) return parse_int(&opt_tab_size, argv[2], 1, 1024); @@ -1599,7 +1748,7 @@ static int option_bind_command(int argc, const char *argv[]) { enum request request; - int keymap; + int keymap = -1; int key; if (argc < 3) { @@ -1716,7 +1865,7 @@ load_option_file(const char *path) struct io io = {}; /* It's OK that the file doesn't exist. */ - if (!io_open(&io, path)) + if (!io_open(&io, "%s", path)) return; config_lineno = 0; @@ -1842,6 +1991,8 @@ struct view_ops { bool (*grep)(struct view *view, struct line *line); /* Select line */ void (*select)(struct view *view, struct line *line); + /* Prepare view for loading */ + bool (*prepare)(struct view *view); }; static struct view_ops blame_ops; @@ -2010,9 +2161,10 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo static bool draw_date(struct view *view, time_t *time) { - const char *date = mkdate(time); + const char *date = time ? mkdate(time) : ""; + int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS; - return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE); + return draw_field(view, LINE_DATE, date, cols, FALSE); } static bool @@ -2213,6 +2365,15 @@ update_view_title(struct view *view) wnoutrefresh(view->title); } +static int +apply_step(double step, int value) +{ + if (step >= 1) + return (int) step; + value *= step + 0.01; + return value ? value : 1; +} + static void resize_display(void) { @@ -2230,7 +2391,9 @@ resize_display(void) if (view != base) { /* Horizontal split. */ view->width = base->width; - view->height = SCALE_SPLIT_VIEW(base->height); + view->height = apply_step(opt_scale_split_view, base->height); + view->height = MAX(view->height, MIN_VIEW_HEIGHT); + view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT); base->height -= view->height; /* Make room for the title bar. */ @@ -2278,6 +2441,21 @@ redraw_display(bool clear) } } +static void +toggle_date_option(enum date *date) +{ + static const char *help[] = { + "no", + "default", + "relative", + "short" + }; + + opt_date = (opt_date + 1) % ARRAY_SIZE(help); + redraw_display(FALSE); + report("Displaying %s dates", help[opt_date]); +} + static void toggle_view_option(bool *option, const char *help) { @@ -2286,6 +2464,27 @@ toggle_view_option(bool *option, const char *help) report("%sabling %s", *option ? "En" : "Dis", help); } +static void +open_option_menu(void) +{ + const struct menu_item menu[] = { + { '.', "line numbers", &opt_line_number }, + { 'D', "date display", &opt_date }, + { 'A', "author display", &opt_author }, + { 'g', "revision graph display", &opt_rev_graph }, + { 'F', "reference display", &opt_show_refs }, + { 0 } + }; + int selected = 0; + + if (prompt_menu("Toggle option", menu, &selected)) { + if (menu[selected].data == &opt_date) + toggle_date_option(menu[selected].data); + else + toggle_view_option(menu[selected].data, menu[selected].text); + } +} + static void maximize_view(struct view *view) { @@ -2326,15 +2525,6 @@ goto_view_line(struct view *view, unsigned long offset, unsigned long lineno) return FALSE; } -static int -apply_step(double step, int value) -{ - if (step >= 1) - return (int) step; - value *= step + 0.01; - return value ? value : 1; -} - /* Scrolling backend */ static void do_scroll_view(struct view *view, int lines) @@ -2806,7 +2996,7 @@ prepare_update_file(struct view *view, const char *name) { if (view->pipe) end_update(view, TRUE); - return io_open(&view->io, name); + return io_open(&view->io, "%s", name); } static bool @@ -2815,16 +3005,13 @@ begin_update(struct view *view, bool refresh) if (view->pipe) end_update(view, TRUE); - if (refresh) { - if (!start_io(&view->io)) - return FALSE; - - } else { - if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id)) - opt_path[0] = 0; - - if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL)) + if (!refresh) { + if (view->ops->prepare) { + if (!view->ops->prepare(view)) + return FALSE; + } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) { return FALSE; + } /* Put the current ref_* value to the view title ref * member. This is needed by the blob view. Most other @@ -2833,6 +3020,9 @@ begin_update(struct view *view, bool refresh) string_copy_rev(view->ref, view->id); } + if (!start_io(&view->io)) + return FALSE; + setup_update(view, view->id); return TRUE; @@ -3011,6 +3201,11 @@ open_view(struct view *prev, enum request request, enum open_flags flags) display[current_view] = view; } + /* No parent signals that this is the first loaded view. */ + if (prev && view != prev) { + view->parent = prev; + } + /* Resize the view when switching between split- and full-screen, * or when switching between two different full-screen views. */ if (nviews != displayed_views() || @@ -3046,8 +3241,6 @@ open_view(struct view *prev, enum request request, enum open_flags flags) /* "Blur" the previous view. */ update_view_title(prev); } - - view->parent = prev; } if (view->pipe && view->lines == 0) { @@ -3171,7 +3364,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_BLAME: if (!opt_file[0]) { report("No file chosen, press %s to open tree view", - get_key(REQ_VIEW_TREE)); + get_key(view->keymap, REQ_VIEW_TREE)); break; } open_view(view, request, OPEN_DEFAULT); @@ -3180,7 +3373,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_BLOB: if (!ref_blob[0]) { report("No file chosen, press %s to open tree view", - get_key(REQ_VIEW_TREE)); + get_key(view->keymap, REQ_VIEW_TREE)); break; } open_view(view, request, OPEN_DEFAULT); @@ -3189,7 +3382,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_PAGER: if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) { report("No pager content, press %s to run command from prompt", - get_key(REQ_PROMPT)); + get_key(view->keymap, REQ_PROMPT)); break; } open_view(view, request, OPEN_DEFAULT); @@ -3198,7 +3391,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_STAGE: if (!VIEW(REQ_VIEW_STAGE)->lines) { report("No stage content, press %s to open the status view and choose file", - get_key(REQ_VIEW_STATUS)); + get_key(view->keymap, REQ_VIEW_STATUS)); break; } open_view(view, request, OPEN_DEFAULT); @@ -3276,12 +3469,16 @@ view_driver(struct view *view, enum request request) maximize_view(view); break; + case REQ_OPTIONS: + open_option_menu(); + break; + case REQ_TOGGLE_LINENO: toggle_view_option(&opt_line_number, "line numbers"); break; case REQ_TOGGLE_DATE: - toggle_view_option(&opt_date, "date display"); + toggle_date_option(&opt_date); break; case REQ_TOGGLE_AUTHOR: @@ -3351,7 +3548,8 @@ view_driver(struct view *view, enum request request) return FALSE; default: - report("Unknown key, press 'h' for help"); + report("Unknown key, press %s for help", + get_key(view->keymap, REQ_VIEW_HELP)); return TRUE; } @@ -3486,22 +3684,39 @@ parse_author_line(char *ident, const char **author, time_t *time) } } -static enum input_status -select_commit_parent_handler(void *data, char *buf, int c) +static bool +open_commit_parent_menu(char buf[SIZEOF_STR], int *parents) { - size_t parents = *(size_t *) data; - int parent = 0; + char rev[SIZEOF_REV]; + const char *revlist_argv[] = { + "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL + }; + struct menu_item *items; + char text[SIZEOF_STR]; + bool ok = TRUE; + int i; - if (!isdigit(c)) - return INPUT_SKIP; + items = calloc(*parents + 1, sizeof(*items)); + if (!items) + return FALSE; - if (*buf) - parent = atoi(buf) * 10; - parent += c - '0'; + for (i = 0; i < *parents; i++) { + string_copy_rev(rev, &buf[SIZEOF_REV * i]); + if (!run_io_buf(revlist_argv, text, sizeof(text)) || + !(items[i].text = strdup(text))) { + ok = FALSE; + break; + } + } - if (parent > parents) - return INPUT_SKIP; - return INPUT_OK; + if (ok) { + *parents = 0; + ok = prompt_menu("Select parent", items, parents); + } + for (i = 0; items[i].text; i++) + free((char *) items[i].text); + free(items); + return ok; } static bool @@ -3509,12 +3724,13 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) { char buf[SIZEOF_STR * 4]; const char *revlist_argv[] = { - "git", "rev-list", "-1", "--parents", id, "--", path, NULL + "git", "log", "--no-color", "-1", + "--pretty=format:%P", id, "--", path, NULL }; int parents; if (!run_io_buf(revlist_argv, buf, sizeof(buf)) || - (parents = (strlen(buf) / 40) - 1) < 0) { + (parents = strlen(buf) / 40) < 0) { report("Failed to get parent information"); return FALSE; @@ -3526,17 +3742,8 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) return FALSE; } - if (parents > 1) { - char prompt[SIZEOF_STR]; - char *result; - - if (!string_format(prompt, "Which parent? [1..%d] ", parents)) - return FALSE; - result = prompt_input(prompt, select_commit_parent_handler, &parents); - if (!result) - return FALSE; - parents = atoi(result); - } + if (parents > 1 && !open_commit_parent_menu(buf, &parents)) + return FALSE; string_copy_rev(rev, &buf[41 * parents]); return TRUE; @@ -3747,80 +3954,149 @@ static struct view_ops diff_ops = { * Help backend */ +static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)]; + +static char * +help_name(char buf[SIZEOF_STR], const char *name, size_t namelen) +{ + int bufpos; + + for (bufpos = 0; bufpos <= namelen; bufpos++) { + buf[bufpos] = tolower(name[bufpos]); + if (buf[bufpos] == '_') + buf[bufpos] = '-'; + } + + buf[bufpos] = 0; + return buf; +} + +#define help_keymap_name(buf, keymap) \ + help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen) + static bool -help_open(struct view *view) +help_open_keymap_title(struct view *view, enum keymap keymap) { char buf[SIZEOF_STR]; - size_t bufpos; - int i; + struct line *line; - if (view->lines > 0) - return TRUE; + line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings", + help_keymap_hidden[keymap] ? '+' : '-', + help_keymap_name(buf, keymap)); + if (line) + line->other = keymap; - add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT); + return help_keymap_hidden[keymap]; +} + +static void +help_open_keymap(struct view *view, enum keymap keymap) +{ + const char *group = NULL; + char buf[SIZEOF_STR]; + size_t bufpos; + bool add_title = TRUE; + int i; for (i = 0; i < ARRAY_SIZE(req_info); i++) { - const char *key; + const char *key = NULL; if (req_info[i].request == REQ_NONE) continue; if (!req_info[i].request) { - add_line_text(view, "", LINE_DEFAULT); - add_line_text(view, req_info[i].help, LINE_DEFAULT); + group = req_info[i].help; continue; } - key = get_key(req_info[i].request); - if (!*key) - key = "(no key defined)"; + key = get_keys(keymap, req_info[i].request, TRUE); + if (!key || !*key) + continue; + + if (add_title && help_open_keymap_title(view, keymap)) + return; + add_title = false; - for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) { - buf[bufpos] = tolower(req_info[i].name[bufpos]); - if (buf[bufpos] == '_') - buf[bufpos] = '-'; + if (group) { + add_line_text(view, group, LINE_HELP_GROUP); + group = NULL; } - add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", - key, buf, req_info[i].help); + add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key, + help_name(buf, req_info[i].name, req_info[i].namelen), + req_info[i].help); } - if (run_requests) { - add_line_text(view, "", LINE_DEFAULT); - add_line_text(view, "External commands:", LINE_DEFAULT); - } + group = "External commands:"; for (i = 0; i < run_requests; i++) { struct run_request *req = get_run_request(REQ_NONE + i + 1); const char *key; int argc; - if (!req) + if (!req || req->keymap != keymap) continue; key = get_key_name(req->key); if (!*key) key = "(no key defined)"; + if (add_title && help_open_keymap_title(view, keymap)) + return; + if (group) { + add_line_text(view, group, LINE_HELP_GROUP); + group = NULL; + } + for (bufpos = 0, argc = 0; req->argv[argc]; argc++) if (!string_format_from(buf, &bufpos, "%s%s", argc ? " " : "", req->argv[argc])) - return REQ_NONE; + return; - add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`", - keymap_table[req->keymap].name, key, buf); + add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf); } +} + +static bool +help_open(struct view *view) +{ + enum keymap keymap; + + reset_view(view); + add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT); + add_line_text(view, "", LINE_DEFAULT); + + for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++) + help_open_keymap(view, keymap); return TRUE; } +static enum request +help_request(struct view *view, enum request request, struct line *line) +{ + switch (request) { + case REQ_ENTER: + if (line->type == LINE_HELP_KEYMAP) { + help_keymap_hidden[line->other] = + !help_keymap_hidden[line->other]; + view->p_restore = TRUE; + open_view(view, REQ_VIEW_HELP, OPEN_REFRESH); + } + + return REQ_NONE; + default: + return pager_request(view, request, line); + } +} + static struct view_ops help_ops = { "line", NULL, help_open, NULL, pager_draw, - pager_request, + help_request, pager_grep, pager_select, }; @@ -3988,7 +4264,7 @@ tree_read_date(struct view *view, char *text, bool *read_date) return TRUE; } - if (!run_io_rd(&io, log_file, FORMAT_NONE)) { + if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) { report("Failed to load tree data"); return TRUE; } @@ -4011,8 +4287,6 @@ tree_read_date(struct view *view, char *text, bool *read_date) if (!pos) return TRUE; text = pos + 1; - if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix))) - text += strlen(opt_prefix); if (*opt_path && !strncmp(text, opt_path, strlen(opt_path))) text += strlen(opt_path); pos = strchr(text, '/'); @@ -4255,6 +4529,32 @@ tree_select(struct view *view, struct line *line) string_copy_rev(view->ref, entry->id); } +static bool +tree_prepare(struct view *view) +{ + if (view->lines == 0 && opt_prefix[0]) { + char *pos = opt_prefix; + + while (pos && *pos) { + char *end = strchr(pos, '/'); + + if (end) + *end = 0; + push_tree_stack_entry(pos, 0); + pos = end; + if (end) { + *end = '/'; + pos++; + } + } + + } else if (strcmp(view->vid, view->id)) { + opt_path[0] = 0; + } + + return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL); +} + static const char *tree_argv[SIZEOF_ARG] = { "git", "ls-tree", "%(commit)", "%(directory)", NULL }; @@ -4268,6 +4568,7 @@ static struct view_ops tree_ops = { tree_request, tree_grep, tree_select, + tree_prepare, }; static bool @@ -4346,8 +4647,16 @@ struct blame { static bool blame_open(struct view *view) { - if (*opt_ref || !io_open(&view->io, opt_file)) { - if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL)) + char path[SIZEOF_STR]; + + if (!view->parent && *opt_prefix) { + string_copy(path, opt_file); + if (!string_format(opt_file, "%s%s", opt_prefix, path)) + return FALSE; + } + + if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) { + if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL)) return FALSE; } @@ -4443,7 +4752,7 @@ blame_read_file(struct view *view, const char *line, bool *read_file) if (view->lines == 0 && !view->parent) die("No blame exist for %s", view->vid); - if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) { + if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) { report("Failed to load blame data"); return TRUE; } @@ -4859,13 +5168,14 @@ branch_open(struct view *view) "--simplify-by-decoration", "--all", NULL }; - if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) { + if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) { report("Failed to load branch data"); return TRUE; } setup_update(view, view->id); foreach_ref(branch_open_visitor, view); + view->p_restore = TRUE; return TRUE; } @@ -5127,8 +5437,7 @@ status_update_onbranch(void) if (!*opt_head) { struct io io = {}; - if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) && - io_open(&io, buf) && + if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) && io_read_buf(&io, buf, sizeof(buf))) { head = buf; if (!prefixcmp(head, "refs/heads/")) @@ -5437,7 +5746,7 @@ status_update_files(struct view *view, struct line *line) struct line *pos = view->line + view->lines; int files = 0; int file, done; - int cursor_y, cursor_x; + int cursor_y = -1, cursor_x = -1; if (!status_update_prepare(&io, line->type)) return FALSE; @@ -5505,9 +5814,8 @@ status_revert(struct status *status, enum line_type type, bool has_none) } else { report("Cannot revert changes to multiple files"); } - return FALSE; - } else { + } else if (prompt_yesno("Are you sure you want to revert changes?")) { char mode[10] = "100644"; const char *reset_argv[] = { "git", "update-index", "--cacheinfo", mode, @@ -5517,12 +5825,25 @@ status_revert(struct status *status, enum line_type type, bool has_none) "git", "checkout", "--", status->old.name, NULL }; - if (!prompt_yesno("Are you sure you want to overwrite any changes?")) - return FALSE; - string_format(mode, "%o", status->old.mode); - return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) && - run_io_fg(checkout_argv, opt_cdup); + if (status->status == 'U') { + string_format(mode, "%5o", status->old.mode); + + if (status->old.mode == 0 && status->new.mode == 0) { + reset_argv[2] = "--force-remove"; + reset_argv[3] = status->old.name; + reset_argv[4] = NULL; + } + + if (!run_io_fg(reset_argv, opt_cdup)) + return FALSE; + if (status->old.mode == 0 && status->new.mode == 0) + return TRUE; + } + + return run_io_fg(checkout_argv, opt_cdup); } + + return FALSE; } static enum request @@ -5624,10 +5945,10 @@ status_select(struct view *view, struct line *line) if (status && status->status == 'U') { text = "Press %s to resolve conflict in %s"; - key = get_key(REQ_STATUS_MERGE); + key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE); } else { - key = get_key(REQ_STATUS_UPDATE); + key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE); } string_format(view->ref, text, key, file); @@ -5825,7 +6146,7 @@ stage_request(struct view *view, enum request request, struct line *line) case REQ_STAGE_NEXT: if (stage_line_type == LINE_STAT_UNTRACKED) { report("File is untracked; press %s to add", - get_key(REQ_STATUS_UPDATE)); + get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE)); return REQ_NONE; } stage_next(view, line); @@ -6791,6 +7112,67 @@ read_prompt(const char *prompt) return prompt_input(prompt, read_prompt_handler, NULL); } +static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected) +{ + enum input_status status = INPUT_OK; + int size = 0; + + while (items[size].text) + size++; + + while (status == INPUT_OK) { + const struct menu_item *item = &items[*selected]; + int key; + int i; + + mvwprintw(status_win, 0, 0, "%s (%d of %d) ", + prompt, *selected + 1, size); + if (item->hotkey) + wprintw(status_win, "[%c] ", (char) item->hotkey); + wprintw(status_win, "%s", item->text); + wclrtoeol(status_win); + + key = get_input(COLS - 1); + switch (key) { + case KEY_RETURN: + case KEY_ENTER: + case '\n': + status = INPUT_STOP; + break; + + case KEY_LEFT: + case KEY_UP: + *selected = *selected - 1; + if (*selected < 0) + *selected = size - 1; + break; + + case KEY_RIGHT: + case KEY_DOWN: + *selected = (*selected + 1) % size; + break; + + case KEY_ESC: + status = INPUT_CANCEL; + break; + + default: + for (i = 0; items[i].text; i++) + if (items[i].hotkey == key) { + *selected = i; + status = INPUT_STOP; + break; + } + } + } + + /* Clear the status window */ + status_empty = FALSE; + report(""); + + return status != INPUT_CANCEL; +} + /* * Repository properties */ @@ -7096,7 +7478,7 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen static int load_git_config(void) { - const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL }; + const char *config_list_argv[] = { "git", "config", "--list", NULL }; return run_io_load(config_list_argv, "=", read_repo_config_option); } @@ -7264,8 +7646,8 @@ parse_options(int argc, const char *argv[]) die("command too long"); } - if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE)) - die("Failed to format arguments"); + if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE)) + die("Failed to format arguments"); return request; }