X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=801fccd4914e0338a8e23e576d3dded09f8ee8a2;hb=61817e8ac2440b361b8e4192c10a72528d2e4ed0;hp=dd155e35e23a6b11fd3aedc9e5883085372ebdb7;hpb=47f8cdf12413e6c4a6d3a7082b2ea282381c068d;p=tig.git diff --git a/tig.c b/tig.c index dd155e3..801fccd 100644 --- a/tig.c +++ b/tig.c @@ -49,15 +49,11 @@ /* ncurses(3): Must be defined to have extended wide-character functions. */ #define _XOPEN_SOURCE_EXTENDED -#ifdef HAVE_NCURSESW_NCURSES_H +#ifdef HAVE_NCURSESW_H #include #else -#ifdef HAVE_NCURSES_NCURSES_H -#include -#else #include #endif -#endif #if __GNUC__ >= 3 #define __NORETURN __attribute__((__noreturn__)) @@ -114,6 +110,7 @@ static void report(const char *msg, ...); #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000) /* Some ASCII-shorthands fitted into the ncurses namespace. */ +#define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */ #define KEY_TAB '\t' #define KEY_RETURN '\r' #define KEY_ESC 27 @@ -209,7 +206,7 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen) #define string_add(dst, from, src) \ string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src)) -static void +static size_t string_expand(char *dst, size_t dstlen, const char *src, int tabsize) { size_t size, pos; @@ -228,6 +225,7 @@ string_expand(char *dst, size_t dstlen, const char *src, int tabsize) } dst[size] = 0; + return pos; } static char * @@ -683,15 +681,23 @@ argv_free(const char *argv[]) argv[0] = NULL; } +static size_t +argv_size(const char **argv) +{ + int argc = 0; + + while (argv && argv[argc]) + argc++; + + return argc; +} + DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG) static bool argv_append(const char ***argv, const char *arg) { - int argc = 0; - - while (*argv && (*argv)[argc]) - argc++; + size_t argc = argv_size(*argv); if (!argv_realloc(argv, argc, 2)) return FALSE; @@ -1034,9 +1040,11 @@ io_run_buf(const char **argv, char buf[], size_t bufsize) return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize); } +typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data); + static int io_load(struct io *io, const char *separators, - int (*read_property)(char *, size_t, char *, size_t)) + io_read_fn read_property, void *data) { char *name; int state = OK; @@ -1059,7 +1067,7 @@ io_load(struct io *io, const char *separators, valuelen = 0; } - state = read_property(name, namelen, value, valuelen); + state = read_property(name, namelen, value, valuelen, data); } if (state != ERR && io_error(io)) @@ -1071,13 +1079,13 @@ io_load(struct io *io, const char *separators, static int io_run_load(const char **argv, const char *separators, - int (*read_property)(char *, size_t, char *, size_t)) + io_read_fn read_property, void *data) { struct io io; if (!io_run(&io, IO_RD, NULL, argv)) return ERR; - return io_load(&io, separators, read_property); + return io_load(&io, separators, read_property, data); } @@ -1126,6 +1134,7 @@ io_run_load(const char **argv, const char *separators, REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \ \ REQ_GROUP("Scrolling") \ + REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \ REQ_(SCROLL_LEFT, "Scroll two columns left"), \ REQ_(SCROLL_RIGHT, "Scroll two columns right"), \ REQ_(SCROLL_LINE_UP, "Scroll one line up"), \ @@ -1143,7 +1152,6 @@ io_run_load(const char **argv, const char *separators, 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)"), \ @@ -1213,6 +1221,7 @@ 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 bool opt_untracked_dirs_content = TRUE; static int opt_num_interval = 5; static double opt_hscroll = 0.50; static double opt_scale_split_view = 2.0 / 3.0; @@ -1233,9 +1242,9 @@ static char opt_git_dir[SIZEOF_STR] = ""; static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */ static char opt_editor[SIZEOF_STR] = ""; static FILE *opt_tty = NULL; -static const char **opt_diff_args = NULL; -static const char **opt_rev_args = NULL; -static const char **opt_file_args = NULL; +static const char **opt_diff_argv = NULL; +static const char **opt_rev_argv = NULL; +static const char **opt_file_argv = NULL; #define is_initial_commit() (!get_ref_head()) #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id))) @@ -1428,7 +1437,9 @@ static struct keybinding default_keybindings[] = { { KEY_TAB, REQ_VIEW_NEXT }, { KEY_RETURN, REQ_ENTER }, { KEY_UP, REQ_PREVIOUS }, + { KEY_CTL('P'), REQ_PREVIOUS }, { KEY_DOWN, REQ_NEXT }, + { KEY_CTL('N'), REQ_NEXT }, { 'R', REQ_REFRESH }, { KEY_F(5), REQ_REFRESH }, { 'O', REQ_MAXIMIZE }, @@ -1439,16 +1450,21 @@ static struct keybinding default_keybindings[] = { { KEY_HOME, REQ_MOVE_FIRST_LINE }, { KEY_END, REQ_MOVE_LAST_LINE }, { KEY_NPAGE, REQ_MOVE_PAGE_DOWN }, + { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN }, { ' ', REQ_MOVE_PAGE_DOWN }, { KEY_PPAGE, REQ_MOVE_PAGE_UP }, + { KEY_CTL('U'), REQ_MOVE_PAGE_UP }, { 'b', REQ_MOVE_PAGE_UP }, { '-', REQ_MOVE_PAGE_UP }, /* Scrolling */ + { '|', REQ_SCROLL_FIRST_COL }, { KEY_LEFT, REQ_SCROLL_LEFT }, { KEY_RIGHT, REQ_SCROLL_RIGHT }, { KEY_IC, REQ_SCROLL_LINE_UP }, + { KEY_CTL('Y'), REQ_SCROLL_LINE_UP }, { KEY_DC, REQ_SCROLL_LINE_DOWN }, + { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN }, { 'w', REQ_SCROLL_PAGE_UP }, { 's', REQ_SCROLL_PAGE_DOWN }, @@ -1463,6 +1479,7 @@ static struct keybinding default_keybindings[] = { { 'z', REQ_STOP_LOADING }, { 'v', REQ_SHOW_VERSION }, { 'r', REQ_SCREEN_REDRAW }, + { KEY_CTL('L'), REQ_SCREEN_REDRAW }, { 'o', REQ_OPTIONS }, { '.', REQ_TOGGLE_LINENO }, { 'D', REQ_TOGGLE_DATE }, @@ -1611,16 +1628,17 @@ get_key_value(const char *name) if (!strcasecmp(key_table[i].name, name)) return key_table[i].value; + if (strlen(name) == 2 && name[0] == '^' && isprint(*name)) + return (int)name[1] & 0x1f; if (strlen(name) == 1 && isprint(*name)) return (int) *name; - return ERR; } static const char * get_key_name(int key_value) { - static char key_char[] = "'X'"; + static char key_char[] = "'X'\0"; const char *seq = NULL; int key; @@ -1628,10 +1646,17 @@ get_key_name(int key_value) if (key_table[key].value == key_value) seq = key_table[key].name; - if (seq == NULL && - key_value < 127 && - isprint(key_value)) { - key_char[1] = (char) key_value; + if (seq == NULL && key_value < 0x7f) { + char *s = key_char + 1; + + if (key_value >= 0x20) { + *s++ = key_value; + } else { + *s++ = '^'; + *s++ = 0x40 | (key_value & 0x1f); + } + *s++ = '\''; + *s++ = '\0'; seq = key_char; } @@ -1773,9 +1798,36 @@ add_builtin_run_requests(void) * User config file handling. */ -static int config_lineno; -static bool config_errors; -static const char *config_msg; +#define OPT_ERR_INFO \ + OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \ + OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \ + OPT_ERR_(NO_OPTION_VALUE, "No option value"), \ + OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \ + OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \ + OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \ + OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \ + OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \ + OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \ + OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \ + OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \ + OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \ + OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \ + OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \ + OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \ + OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"), + +enum option_code { +#define OPT_ERR_(name, msg) OPT_ERR_ ## name + OPT_ERR_INFO +#undef OPT_ERR_ + OPT_OK +}; + +static const char *option_errors[] = { +#define OPT_ERR_(name, msg) msg + OPT_ERR_INFO +#undef OPT_ERR_ +}; static const struct enum_map color_map[] = { #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name) @@ -1803,39 +1855,37 @@ static const struct enum_map attr_map[] = { #define set_attribute(attr, name) map_enum(attr, attr_map, name) -static int parse_step(double *opt, const char *arg) +static enum option_code +parse_step(double *opt, const char *arg) { *opt = atoi(arg); if (!strchr(arg, '%')) - return OK; + return OPT_OK; /* "Shift down" so 100% and 1 does not conflict. */ *opt = (*opt - 1) / 100; if (*opt >= 1.0) { *opt = 0.99; - config_msg = "Step value larger than 100%"; - return ERR; + return OPT_ERR_INVALID_STEP_VALUE; } if (*opt < 0.0) { *opt = 1; - config_msg = "Invalid step value"; - return ERR; + return OPT_ERR_INVALID_STEP_VALUE; } - return OK; + return OPT_OK; } -static int +static enum option_code parse_int(int *opt, const char *arg, int min, int max) { int value = atoi(arg); if (min <= value && value <= max) { *opt = value; - return OK; + return OPT_OK; } - config_msg = "Integer value out of bound"; - return ERR; + return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND; } static bool @@ -1849,15 +1899,13 @@ set_color(int *color, const char *name) } /* Wants: object fgcolor bgcolor [attribute] */ -static int +static enum option_code option_color_command(int argc, const char *argv[]) { struct line_info *info; - if (argc < 3) { - config_msg = "Wrong number of arguments given to color command"; - return ERR; - } + if (argc < 3) + return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS; info = get_line_info(argv[0]); if (!info) { @@ -1869,16 +1917,14 @@ option_color_command(int argc, const char *argv[]) int index; if (!map_enum(&index, obsolete, argv[0])) { - config_msg = "Unknown color name"; - return ERR; + return OPT_ERR_UNKNOWN_COLOR_NAME; } info = &line_info[index]; } if (!set_color(&info->fg, argv[1]) || !set_color(&info->bg, argv[2])) { - config_msg = "Unknown color"; - return ERR; + return OPT_ERR_UNKNOWN_COLOR; } info->attr = 0; @@ -1886,43 +1932,42 @@ option_color_command(int argc, const char *argv[]) int attr; if (!set_attribute(&attr, argv[argc])) { - config_msg = "Unknown attribute"; - return ERR; + return OPT_ERR_UNKNOWN_ATTRIBUTE; } info->attr |= attr; } - return OK; + return OPT_OK; } -static int parse_bool(bool *opt, const char *arg) +static enum option_code +parse_bool(bool *opt, const char *arg) { *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes")) ? TRUE : FALSE; - return OK; + return OPT_OK; } -static int parse_enum_do(unsigned int *opt, const char *arg, - const struct enum_map *map, size_t map_size) +static enum option_code +parse_enum_do(unsigned int *opt, const char *arg, + const struct enum_map *map, size_t map_size) { bool is_true; assert(map_size > 1); if (map_enum_do(map, map_size, (int *) opt, arg)) - return OK; - - if (parse_bool(&is_true, arg) != OK) - return ERR; + return OPT_OK; + parse_bool(&is_true, arg); *opt = is_true ? map[1].value : map[0].value; - return OK; + return OPT_OK; } #define parse_enum(opt, arg, map) \ parse_enum_do(opt, arg, map, ARRAY_SIZE(map)) -static int +static enum option_code parse_string(char *opt, const char *arg, size_t optsize) { int arglen = strlen(arg); @@ -1930,30 +1975,24 @@ parse_string(char *opt, const char *arg, size_t optsize) switch (arg[0]) { case '\"': case '\'': - if (arglen == 1 || arg[arglen - 1] != arg[0]) { - config_msg = "Unmatched quotation"; - return ERR; - } + if (arglen == 1 || arg[arglen - 1] != arg[0]) + return OPT_ERR_UNMATCHED_QUOTATION; arg += 1; arglen -= 2; default: string_ncopy_do(opt, optsize, arg, arglen); - return OK; + return OPT_OK; } } /* Wants: name = value */ -static int +static enum option_code option_set_command(int argc, const char *argv[]) { - if (argc != 3) { - config_msg = "Wrong number of arguments given to set command"; - return ERR; - } + if (argc != 3) + return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS; - if (strcmp(argv[1], "=")) { - config_msg = "No value assigned"; - return ERR; - } + if (strcmp(argv[1], "=")) + return OPT_ERR_NO_VALUE_ASSIGNED; if (!strcmp(argv[0], "show-author")) return parse_enum(&opt_author, argv[2], author_map); @@ -1991,33 +2030,29 @@ option_set_command(int argc, const char *argv[]) if (!strcmp(argv[0], "commit-encoding")) return parse_string(opt_encoding, argv[2], sizeof(opt_encoding)); - config_msg = "Unknown variable name"; - return ERR; + if (!strcmp(argv[0], "status-untracked-dirs")) + return parse_bool(&opt_untracked_dirs_content, argv[2]); + + return OPT_ERR_UNKNOWN_VARIABLE_NAME; } /* Wants: mode request key */ -static int +static enum option_code option_bind_command(int argc, const char *argv[]) { enum request request; int keymap = -1; int key; - if (argc < 3) { - config_msg = "Wrong number of arguments given to bind command"; - return ERR; - } + if (argc < 3) + return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS; - if (!set_keymap(&keymap, argv[0])) { - config_msg = "Unknown key map"; - return ERR; - } + if (!set_keymap(&keymap, argv[0])) + return OPT_ERR_UNKNOWN_KEY_MAP; key = get_key_value(argv[1]); - if (key == ERR) { - config_msg = "Unknown key"; - return ERR; - } + if (key == ERR) + return OPT_ERR_UNKNOWN_KEY; request = get_request(argv[2]); if (request == REQ_UNKNOWN) { @@ -2031,32 +2066,27 @@ option_bind_command(int argc, const char *argv[]) if (map_enum(&alias, obsolete, argv[2])) { if (alias != REQ_NONE) add_keybinding(keymap, alias, key); - config_msg = "Obsolete request name"; - return ERR; + return OPT_ERR_OBSOLETE_REQUEST_NAME; } } if (request == REQ_UNKNOWN && *argv[2]++ == '!') request = add_run_request(keymap, key, argv + 2); - if (request == REQ_UNKNOWN) { - config_msg = "Unknown request name"; - return ERR; - } + if (request == REQ_UNKNOWN) + return OPT_ERR_UNKNOWN_REQUEST_NAME; add_keybinding(keymap, request, key); - return OK; + return OPT_OK; } -static int +static enum option_code set_option(const char *opt, char *value) { const char *argv[SIZEOF_ARG]; int argc = 0; - if (!argv_from_string(argv, &argc, value)) { - config_msg = "Too many option arguments"; - return ERR; - } + if (!argv_from_string(argv, &argc, value)) + return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS; if (!strcmp(opt, "color")) return option_color_command(argc, argv); @@ -2067,17 +2097,21 @@ set_option(const char *opt, char *value) if (!strcmp(opt, "bind")) return option_bind_command(argc, argv); - config_msg = "Unknown option command"; - return ERR; + return OPT_ERR_UNKNOWN_OPTION_COMMAND; } +struct config_state { + int lineno; + bool errors; +}; + static int -read_option(char *opt, size_t optlen, char *value, size_t valuelen) +read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data) { - int status = OK; + struct config_state *config = data; + enum option_code status = OPT_ERR_NO_OPTION_VALUE; - config_lineno++; - config_msg = "Internal error"; + config->lineno++; /* Check for comment markers, since read_properties() will * only ensure opt and value are split at first " \t". */ @@ -2085,11 +2119,7 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen) if (optlen == 0) return OK; - if (opt[optlen] != 0) { - config_msg = "No option value"; - status = ERR; - - } else { + if (opt[optlen] == 0) { /* Look for comment endings in the value. */ size_t len = strcspn(value, "#"); @@ -2101,10 +2131,10 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen) status = set_option(opt, value); } - if (status == ERR) { + if (status != OPT_OK) { warn("Error on line %d, near '%.*s': %s", - config_lineno, (int) optlen, opt, config_msg); - config_errors = TRUE; + config->lineno, (int) optlen, opt, option_errors[status]); + config->errors = TRUE; } /* Always keep going if errors are encountered. */ @@ -2114,17 +2144,15 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen) static void load_option_file(const char *path) { + struct config_state config = { 0, FALSE }; struct io io; /* It's OK that the file doesn't exist. */ if (!io_open(&io, "%s", path)) return; - config_lineno = 0; - config_errors = FALSE; - - if (io_load(&io, " \t", read_option) == ERR || - config_errors == TRUE) + if (io_load(&io, " \t", read_option, &config) == ERR || + config.errors == TRUE) warn("Errors while loading %s.", path); } @@ -2134,6 +2162,7 @@ load_options(void) const char *home = getenv("HOME"); const char *tigrc_user = getenv("TIGRC_USER"); const char *tigrc_system = getenv("TIGRC_SYSTEM"); + const char *tig_diff_opts = getenv("TIG_DIFF_OPTS"); char buf[SIZEOF_STR]; if (!tigrc_system) @@ -2151,6 +2180,17 @@ load_options(void) * that conflict with keybindings. */ add_builtin_run_requests(); + if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) { + static const char *diff_opts[SIZEOF_ARG] = { NULL }; + int argc = 0; + + if (!string_format(buf, "%s", tig_diff_opts) || + !argv_from_string(diff_opts, &argc, buf)) + die("TIG_DIFF_OPTS contains too many arguments"); + else if (!argv_copy(&opt_diff_argv, diff_opts)) + die("Failed to format TIG_DIFF_OPTS arguments"); + } + return OK; } @@ -2295,7 +2335,7 @@ static struct view views[] = { VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit), VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head), VIEW_(HELP, "help", &help_ops, FALSE, ""), - VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"), + VIEW_(PAGER, "pager", &pager_ops, FALSE, ""), VIEW_(STATUS, "status", &status_ops, TRUE, ""), VIEW_(STAGE, "stage", &stage_ops, TRUE, ""), }; @@ -2365,11 +2405,12 @@ draw_chars(struct view *view, enum line_type type, const char *string, } waddnstr(view->win, string, len); - } - if (trimmed && use_tilde) { - set_view_attr(view, LINE_DELIMITER); - waddch(view->win, '~'); - col++; + + if (trimmed && use_tilde) { + set_view_attr(view, LINE_DELIMITER); + waddch(view->win, '~'); + col++; + } } return col; @@ -2396,7 +2437,15 @@ draw_space(struct view *view, enum line_type type, int max, int spaces) static bool draw_text(struct view *view, enum line_type type, const char *string, bool trim) { - view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim); + char text[SIZEOF_STR]; + + do { + size_t pos = string_expand(text, sizeof(text), string, opt_tab_size); + + view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim); + string += pos; + } while (*string && view->width + view->yoffset > view->col); + return view->width + view->yoffset <= view->col; } @@ -2715,49 +2764,56 @@ redraw_display(bool clear) * Option management */ -static void -toggle_enum_option_do(unsigned int *opt, const char *help, - const struct enum_map *map, size_t size) -{ - *opt = (*opt + 1) % size; - redraw_display(FALSE); - report("Displaying %s %s", enum_name(map[*opt]), help); -} - -#define toggle_enum_option(opt, help, map) \ - toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map)) - -#define toggle_date() toggle_enum_option(&opt_date, "dates", date_map) -#define toggle_author() toggle_enum_option(&opt_author, "author names", author_map) +#define TOGGLE_MENU \ + TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \ + TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \ + TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \ + TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \ + TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL) static void -toggle_view_option(bool *option, const char *help) -{ - *option = !*option; - redraw_display(FALSE); - report("%sabling %s", *option ? "En" : "Dis", help); -} - -static void -open_option_menu(void) -{ +toggle_option(enum request request) +{ + const struct { + enum request request; + const struct enum_map *map; + size_t map_size; + } data[] = { +#define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) }, + TOGGLE_MENU +#undef TOGGLE_ + }; 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 }, +#define TOGGLE_(id, key, help, value, map) { key, help, value }, + TOGGLE_MENU +#undef TOGGLE_ { 0 } }; - int selected = 0; + int i = 0; - if (prompt_menu("Toggle option", menu, &selected)) { - if (menu[selected].data == &opt_date) - toggle_date(); - else if (menu[selected].data == &opt_author) - toggle_author(); - else - toggle_view_option(menu[selected].data, menu[selected].text); + if (request == REQ_OPTIONS) { + if (!prompt_menu("Toggle option", menu, &i)) + return; + } else { + while (i < ARRAY_SIZE(data) && data[i].request != request) + i++; + if (i >= ARRAY_SIZE(data)) + die("Invalid request (%d)", request); + } + + if (data[i].map != NULL) { + unsigned int *opt = menu[i].data; + + *opt = (*opt + 1) % data[i].map_size; + redraw_display(FALSE); + report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text); + + } else { + bool *option = menu[i].data; + + *option = !*option; + redraw_display(FALSE); + report("%sabling %s", *option ? "En" : "Dis", menu[i].text); } } @@ -2857,6 +2913,11 @@ scroll_view(struct view *view, enum request request) assert(view_is_displayed(view)); switch (request) { + case REQ_SCROLL_FIRST_COL: + view->yoffset = 0; + redraw_view_from(view, 0); + report(""); + return; case REQ_SCROLL_LEFT: if (view->yoffset == 0) { report("Cannot scroll beyond the first column"); @@ -3168,7 +3229,7 @@ format_arg(const char *name) } static bool -format_argv(const char ***dst_argv, const char *src_argv[], bool replace) +format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first) { char buf[SIZEOF_STR]; int argc; @@ -3179,18 +3240,19 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace) const char *arg = src_argv[argc]; size_t bufpos = 0; - if (!strcmp(arg, "%(file-args)")) { - if (!argv_append_array(dst_argv, opt_file_args)) + if (!strcmp(arg, "%(fileargs)")) { + if (!argv_append_array(dst_argv, opt_file_argv)) break; continue; - } else if (!strcmp(arg, "%(diff-args)")) { - if (!argv_append_array(dst_argv, opt_diff_args)) + } else if (!strcmp(arg, "%(diffargs)")) { + if (!argv_append_array(dst_argv, opt_diff_argv)) break; continue; - } else if (!strcmp(arg, "%(rev-args)")) { - if (!argv_append_array(dst_argv, opt_rev_args)) + } else if (!strcmp(arg, "%(revargs)") || + (first && !strcmp(arg, "%(commit)"))) { + if (!argv_append_array(dst_argv, opt_rev_argv)) break; continue; } @@ -3275,7 +3337,7 @@ static bool prepare_io(struct view *view, const char *dir, const char *argv[], bool replace) { view->dir = dir; - return format_argv(&view->argv, argv, replace); + return format_argv(&view->argv, argv, replace, !view->prev); } static bool @@ -3612,7 +3674,7 @@ open_run_request(enum request request) return; } - if (format_argv(&argv, req->argv, TRUE)) + if (format_argv(&argv, req->argv, TRUE, FALSE)) open_external_viewer(argv, NULL); if (argv) argv_free(argv); @@ -3651,6 +3713,7 @@ view_driver(struct view *view, enum request request) move_view(view, request); break; + case REQ_SCROLL_FIRST_COL: case REQ_SCROLL_LEFT: case REQ_SCROLL_RIGHT: case REQ_SCROLL_LINE_DOWN: @@ -3679,6 +3742,13 @@ view_driver(struct view *view, enum request request) break; case REQ_VIEW_PAGER: + if (view == NULL) { + if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, "")) + die("Failed to open stdin"); + open_view(view, request, OPEN_PREPARED); + break; + } + if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) { report("No pager content, press %s to run command from prompt", get_key(view->keymap, REQ_PROMPT)); @@ -3758,27 +3828,12 @@ view_driver(struct view *view, enum request request) 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_date(); - break; - case REQ_TOGGLE_AUTHOR: - toggle_author(); - break; - case REQ_TOGGLE_REV_GRAPH: - toggle_view_option(&opt_rev_graph, "revision graph display"); - break; - case REQ_TOGGLE_REFS: - toggle_view_option(&opt_show_refs, "reference display"); + toggle_option(request); break; case REQ_TOGGLE_SORT_FIELD: @@ -3984,13 +4039,10 @@ parse_author_line(char *ident, const char **author, struct time *time) static bool pager_draw(struct view *view, struct line *line, unsigned int lineno) { - char text[SIZEOF_STR]; - if (opt_line_number && draw_lineno(view, lineno)) return TRUE; - string_expand(text, sizeof(text), line->data, opt_tab_size); - draw_text(view, line->type, text, TRUE); + draw_text(view, line->type, line->data, TRUE); return TRUE; } @@ -4165,14 +4217,41 @@ static struct view_ops log_ops = { static const char *diff_argv[SIZEOF_ARG] = { "git", "show", "--pretty=fuller", "--no-color", "--root", "--patch-with-stat", "--find-copies-harder", "-C", - "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL + "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL }; +static bool +diff_read(struct view *view, char *data) +{ + if (!data) { + /* Fall back to retry if no diff will be shown. */ + if (view->lines == 0 && opt_file_argv) { + int pos = argv_size(view->argv) + - argv_size(opt_file_argv) - 1; + + if (pos > 0 && !strcmp(view->argv[pos], "--")) { + for (; view->argv[pos]; pos++) { + free((void *) view->argv[pos]); + view->argv[pos] = NULL; + } + + if (view->pipe) + io_done(view->pipe); + if (io_run(&view->io, IO_RD, view->dir, view->argv)) + return FALSE; + } + } + return TRUE; + } + + return pager_read(view, data); +} + static struct view_ops diff_ops = { "line", diff_argv, NULL, - pager_read, + diff_read, pager_draw, pager_request, pager_grep, @@ -5070,7 +5149,6 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno) struct blame *blame = line->data; struct time *time = NULL; const char *id = NULL, *author = NULL; - char text[SIZEOF_STR]; if (blame->commit && *blame->commit->filename) { id = blame->commit->id; @@ -5090,8 +5168,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno) if (draw_lineno(view, lineno)) return TRUE; - string_expand(text, sizeof(text), blame->text, opt_tab_size); - draw_text(view, LINE_DEFAULT, text, TRUE); + draw_text(view, LINE_DEFAULT, blame->text, TRUE); return TRUE; } @@ -5618,7 +5695,7 @@ static const char *status_diff_files_argv[] = { }; static const char *status_list_other_argv[] = { - "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL + "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL, }; static const char *status_list_no_head_argv[] = { @@ -5723,6 +5800,9 @@ status_open(struct view *view) return FALSE; } + if (!opt_untracked_dirs_content) + status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory"; + if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) || !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED)) return FALSE; @@ -6691,8 +6771,8 @@ update_rev_graph(struct view *view, struct rev_graph *graph) static const char *main_argv[SIZEOF_ARG] = { "git", "log", "--no-color", "--pretty=raw", "--parents", - "--topo-order", "%(diff-args)", "%(rev-args)", - "--", "%(file-args)", NULL + "--topo-order", "%(diffargs)", "%(revargs)", + "--", "%(fileargs)", NULL }; static bool @@ -6852,6 +6932,8 @@ main_request(struct view *view, enum request request, struct line *line) switch (request) { case REQ_ENTER: + if (view_is_displayed(view) && display[0] != view) + maximize_view(view); open_view(view, REQ_VIEW_DIFF, flags); break; case REQ_REFRESH: @@ -7021,7 +7103,11 @@ init_display(void) keypad(status_win, TRUE); wbkgdset(status_win, get_line_attr(LINE_STATUS)); +#if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119) + set_tabsize(opt_tab_size); +#else TABSIZE = opt_tab_size; +#endif term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM"); if (term && !strcmp(term, "gnome-terminal")) { @@ -7341,7 +7427,7 @@ get_ref_list(const char *id) } static int -read_ref(char *id, size_t idlen, char *name, size_t namelen) +read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data) { struct ref *ref = NULL; bool tag = FALSE; @@ -7459,7 +7545,7 @@ load_refs(void) for (i = 0; i < refs_size; i++) refs[i]->id[0] = 0; - if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR) + if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR) return ERR; /* Update the ref lists to reflect changes. */ @@ -7494,19 +7580,19 @@ set_remote_branch(const char *name, const char *value, size_t valuelen) } static void -set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **)) +set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **)) { const char *argv[SIZEOF_ARG] = { name, "=" }; int argc = 1 + (cmd == option_set_command); - int error = ERR; + enum option_code error; if (!argv_from_string(argv, &argc, value)) - config_msg = "Too many option arguments"; + error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS; else error = cmd(argc, argv); - if (error == ERR) - warn("Option 'tig.%s': %s", name, config_msg); + if (error != OPT_OK) + warn("Option 'tig.%s': %s", name, option_errors[error]); } static bool @@ -7548,7 +7634,7 @@ set_work_tree(const char *value) } static int -read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen) +read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data) { if (!strcmp(name, "i18n.commitencoding")) string_ncopy(opt_encoding, value, valuelen); @@ -7580,11 +7666,11 @@ load_git_config(void) { const char *config_list_argv[] = { "git", "config", "--list", NULL }; - return io_run_load(config_list_argv, "=", read_repo_config_option); + return io_run_load(config_list_argv, "=", read_repo_config_option, NULL); } static int -read_repo_info(char *name, size_t namelen, char *value, size_t valuelen) +read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data) { if (!opt_git_dir[0]) { string_ncopy(opt_git_dir, name, namelen); @@ -7615,7 +7701,7 @@ load_repo_info(void) "--show-cdup", "--show-prefix", NULL }; - return io_run_load(rev_parse_argv, "=", read_repo_info); + return io_run_load(rev_parse_argv, "=", read_repo_info, NULL); } @@ -7673,11 +7759,11 @@ warn(const char *msg, ...) va_end(args); } -static const char ***filter_args; - static int -read_filter_args(char *name, size_t namelen, char *value, size_t valuelen) +read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data) { + const char ***filter_args = data; + return argv_append(filter_args, name) ? OK : ERR; } @@ -7687,10 +7773,9 @@ filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const c const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 }; const char **all_argv = NULL; - filter_args = args; if (!argv_append_array(&all_argv, rev_parse_argv) || !argv_append_array(&all_argv, argv) || - !io_run_load(all_argv, "\n", read_filter_args) == ERR) + !io_run_load(all_argv, "\n", read_filter_args, args) == ERR) die("Failed to split arguments"); argv_free(all_argv); free(all_argv); @@ -7699,9 +7784,9 @@ filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const c static void filter_options(const char *argv[]) { - filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv); - filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv); - filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv); + filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv); + filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv); + filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv); } static enum request @@ -7713,10 +7798,8 @@ parse_options(int argc, const char *argv[]) const char **filter_argv = NULL; int i; - if (!isatty(STDIN_FILENO)) { - io_open(&VIEW(REQ_VIEW_PAGER)->io, ""); + if (!isatty(STDIN_FILENO)) return REQ_VIEW_PAGER; - } if (argc <= 1) return REQ_VIEW_MAIN; @@ -7751,7 +7834,7 @@ parse_options(int argc, const char *argv[]) const char *opt = argv[i]; if (seen_dashdash) { - argv_append(&opt_file_args, opt); + argv_append(&opt_file_argv, opt); continue; } else if (!strcmp(opt, "--")) { @@ -7767,7 +7850,7 @@ parse_options(int argc, const char *argv[]) quit(0); } else if (!strcmp(opt, "--all")) { - argv_append(&opt_rev_args, opt); + argv_append(&opt_rev_argv, opt); continue; }