X-Git-Url: https://git.tokkee.org/?p=tig.git;a=blobdiff_plain;f=tig.c;h=86be4a2973615219fb321863eaa648e73210a8b6;hp=54d99a721378cee897c49df99e728b61357dba55;hb=01223083701dfebf0d5588b4217452b902e3a78b;hpb=33e10c2599e16ff6690e7b0bbdd7a95e7f97c886 diff --git a/tig.c b/tig.c index 54d99a7..86be4a2 100644 --- a/tig.c +++ b/tig.c @@ -68,8 +68,6 @@ static void __NORETURN die(const char *err, ...); static void warn(const char *msg, ...); static void report(const char *msg, ...); -static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size); -static inline unsigned char utf8_char_length(const char *string, const char *end); #define ABS(x) ((x) >= 0 ? (x) : -(x)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -144,7 +142,6 @@ static int load_refs(void); enum format_flags { FORMAT_ALL, /* Perform replacement in all arguments. */ - FORMAT_DASH, /* Perform replacement up until "--". */ FORMAT_NONE /* No replacement should be performed. */ }; @@ -359,9 +356,170 @@ suffixcmp(const char *str, int slen, const char *suffix) } +/* + * Unicode / UTF-8 handling + * + * NOTE: Much of the following code for dealing with Unicode is derived from + * ELinks' UTF-8 code developed by Scrool . Origin file is + * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28. + */ + +static inline int +unicode_width(unsigned long c, int tab_size) +{ + if (c >= 0x1100 && + (c <= 0x115f /* Hangul Jamo */ + || c == 0x2329 + || c == 0x232a + || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) + /* CJK ... Yi */ + || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */ + || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */ + || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */ + || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */ + || (c >= 0xffe0 && c <= 0xffe6) + || (c >= 0x20000 && c <= 0x2fffd) + || (c >= 0x30000 && c <= 0x3fffd))) + return 2; + + if (c == '\t') + return tab_size; + + return 1; +} + +/* Number of bytes used for encoding a UTF-8 character indexed by first byte. + * Illegal bytes are set one. */ +static const unsigned char utf8_bytes[256] = { + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1, +}; + +static inline unsigned char +utf8_char_length(const char *string, const char *end) +{ + int c = *(unsigned char *) string; + + return utf8_bytes[c]; +} + +/* Decode UTF-8 multi-byte representation into a Unicode character. */ +static inline unsigned long +utf8_to_unicode(const char *string, size_t length) +{ + unsigned long unicode; + + switch (length) { + case 1: + unicode = string[0]; + break; + case 2: + unicode = (string[0] & 0x1f) << 6; + unicode += (string[1] & 0x3f); + break; + case 3: + unicode = (string[0] & 0x0f) << 12; + unicode += ((string[1] & 0x3f) << 6); + unicode += (string[2] & 0x3f); + break; + case 4: + unicode = (string[0] & 0x0f) << 18; + unicode += ((string[1] & 0x3f) << 12); + unicode += ((string[2] & 0x3f) << 6); + unicode += (string[3] & 0x3f); + break; + case 5: + unicode = (string[0] & 0x0f) << 24; + unicode += ((string[1] & 0x3f) << 18); + unicode += ((string[2] & 0x3f) << 12); + unicode += ((string[3] & 0x3f) << 6); + unicode += (string[4] & 0x3f); + break; + case 6: + unicode = (string[0] & 0x01) << 30; + unicode += ((string[1] & 0x3f) << 24); + unicode += ((string[2] & 0x3f) << 18); + unicode += ((string[3] & 0x3f) << 12); + unicode += ((string[4] & 0x3f) << 6); + unicode += (string[5] & 0x3f); + break; + default: + return 0; + } + + /* Invalid characters could return the special 0xfffd value but NUL + * should be just as good. */ + return unicode > 0xffff ? 0 : unicode; +} + +/* Calculates how much of string can be shown within the given maximum width + * and sets trimmed parameter to non-zero value if all of string could not be + * shown. If the reserve flag is TRUE, it will reserve at least one + * trailing character, which can be useful when drawing a delimiter. + * + * Returns the number of bytes to output from string to satisfy max_width. */ +static size_t +utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size) +{ + const char *string = *start; + const char *end = strchr(string, '\0'); + unsigned char last_bytes = 0; + size_t last_ucwidth = 0; + + *width = 0; + *trimmed = 0; + + while (string < end) { + unsigned char bytes = utf8_char_length(string, end); + size_t ucwidth; + unsigned long unicode; + + if (string + bytes > end) + break; + + /* Change representation to figure out whether + * it is a single- or double-width character. */ + + unicode = utf8_to_unicode(string, bytes); + /* FIXME: Graceful handling of invalid Unicode character. */ + if (!unicode) + break; + + ucwidth = unicode_width(unicode, tab_size); + if (skip > 0) { + skip -= ucwidth <= skip ? ucwidth : skip; + *start += bytes; + } + *width += ucwidth; + if (*width > max_width) { + *trimmed = 1; + *width -= ucwidth; + if (reserve && *width == max_width) { + string -= last_bytes; + *width -= last_ucwidth; + } + break; + } + + string += bytes; + last_bytes = ucwidth ? bytes : 0; + last_ucwidth = ucwidth; + } + + return string - *start; +} + + #define DATE_INFO \ DATE_(NO), \ DATE_(DEFAULT), \ + DATE_(LOCAL), \ DATE_(RELATIVE), \ DATE_(SHORT) @@ -388,7 +546,7 @@ static inline int timecmp(const struct time *t1, const struct time *t2) } static const char * -string_date(const struct time *time, enum date date) +mkdate(const struct time *time, enum date date) { static char buf[DATE_COLS + 1]; static const struct enum_map reldate[] = { @@ -401,6 +559,9 @@ string_date(const struct time *time, enum date date) }; struct tm tm; + if (!date || !time || !time->sec) + return ""; + if (date == DATE_RELATIVE) { struct timeval now; time_t date = time->sec + time->tz; @@ -423,7 +584,13 @@ string_date(const struct time *time, enum date date) } } - gmtime_r(&time->sec, &tm); + if (date == DATE_LOCAL) { + time_t date = time->sec + time->tz; + localtime_r(&date, &tm); + } + else { + gmtime_r(&time->sec, &tm); + } return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL; } @@ -500,7 +667,7 @@ argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) return *argc < SIZEOF_ARG; } -static void +static bool argv_from_env(const char **argv, const char *name) { char *env = argv ? getenv(name) : NULL; @@ -508,8 +675,28 @@ argv_from_env(const char **argv, const char *name) if (env && *env) env = strdup(env); - if (env && !argv_from_string(argv, &argc, env)) - die("Too many arguments in the `%s` environment variable", name); + return !env || argv_from_string(argv, &argc, env); +} + +static void +argv_free(const char *argv[]) +{ + int argc; + + for (argc = 0; argv[argc]; argc++) + free((void *) argv[argc]); + argv[0] = NULL; +} + +static bool +argv_copy(const char *dst[], const char *src[], bool allocate) +{ + int argc; + + for (argc = 0; src[argc]; argc++) + if (!(dst[argc] = allocate ? strdup(src[argc]) : src[argc])) + return FALSE; + return TRUE; } @@ -529,7 +716,7 @@ enum io_type { struct io { enum io_type type; /* The requested type of pipe. */ const char *dir; /* Directory from which to execute. */ - pid_t pid; /* Pipe for reading or writing. */ + pid_t pid; /* PID of spawned process. */ int pipe; /* Pipe end for reading or writing. */ int error; /* Error status. */ const char *argv[SIZEOF_ARG]; /* Shell command arguments. */ @@ -541,7 +728,7 @@ struct io { }; static void -reset_io(struct io *io) +io_reset(struct io *io) { io->pipe = -1; io->pid = 0; @@ -552,19 +739,18 @@ reset_io(struct io *io) } static void -init_io(struct io *io, const char *dir, enum io_type type) +io_init(struct io *io, const char *dir, enum io_type type) { - reset_io(io); + io_reset(io); io->type = type; io->dir = dir; } -static bool -init_io_rd(struct io *io, const char *argv[], const char *dir, - enum format_flags flags) +static void +io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[]) { - init_io(io, dir, IO_RD); - return format_argv(io->argv, argv, flags); + io_init(io, dir, type); + argv_copy(io->argv, argv, FALSE); } static bool @@ -574,7 +760,7 @@ io_open(struct io *io, const char *fmt, ...) bool fits; va_list args; - init_io(io, NULL, IO_FD); + io_init(io, NULL, IO_FD); va_start(args, fmt); fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name); @@ -591,20 +777,20 @@ io_open(struct io *io, const char *fmt, ...) } static bool -kill_io(struct io *io) +io_kill(struct io *io) { return io->pid == 0 || kill(io->pid, SIGKILL) != -1; } static bool -done_io(struct io *io) +io_done(struct io *io) { pid_t pid = io->pid; if (io->pipe != -1) close(io->pipe); free(io->buf); - reset_io(io); + io_reset(io); while (pid > 0) { int status; @@ -613,7 +799,7 @@ done_io(struct io *io) if (waiting < 0) { if (errno == EINTR) continue; - report("waitpid failed (%s)", strerror(errno)); + io->error = errno; return FALSE; } @@ -627,20 +813,23 @@ done_io(struct io *io) } static bool -start_io(struct io *io) +io_start(struct io *io) { int pipefds[2] = { -1, -1 }; if (io->type == IO_FD) return TRUE; - if ((io->type == IO_RD || io->type == IO_WR) && - pipe(pipefds) < 0) + if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) { + io->error = errno; return FALSE; - else if (io->type == IO_AP) + } else if (io->type == IO_AP) { pipefds[1] = io->pipe; + } if ((io->pid = fork())) { + if (io->pid == -1) + io->error = errno; if (pipefds[!(io->type == IO_WR)] != -1) close(pipefds[!(io->type == IO_WR)]); if (io->pid != -1) { @@ -667,10 +856,10 @@ start_io(struct io *io) } if (io->dir && *io->dir && chdir(io->dir) == -1) - die("Failed to change directory: %s", strerror(errno)); + exit(errno); execvp(io->argv[0], (char *const*) io->argv); - die("Failed to execute program: %s", strerror(errno)); + exit(errno); } if (pipefds[!!(io->type == IO_WR)] != -1) @@ -679,59 +868,38 @@ start_io(struct io *io) } static bool -run_io(struct io *io, const char **argv, const char *dir, enum io_type type) -{ - init_io(io, dir, type); - if (!format_argv(io->argv, argv, FORMAT_NONE)) - return FALSE; - return start_io(io); -} - -static int -run_io_do(struct io *io) +io_run(struct io *io, const char **argv, const char *dir, enum io_type type) { - return start_io(io) && done_io(io); + io_prepare(io, dir, type, argv); + return io_start(io); } -static int -run_io_bg(const char **argv) +static bool +io_complete(enum io_type type, const char **argv, const char *dir, int fd) { struct io io = {}; - init_io(&io, NULL, IO_BG); - if (!format_argv(io.argv, argv, FORMAT_NONE)) - return FALSE; - return run_io_do(&io); + io_prepare(&io, dir, type, argv); + io.pipe = fd; + return io_start(&io) && io_done(&io); } static bool -run_io_fg(const char **argv, const char *dir) +io_run_bg(const char **argv) { - struct io io = {}; - - init_io(&io, dir, IO_FG); - if (!format_argv(io.argv, argv, FORMAT_NONE)) - return FALSE; - return run_io_do(&io); + return io_complete(IO_BG, argv, NULL, -1); } static bool -run_io_append(const char **argv, enum format_flags flags, int fd) +io_run_fg(const char **argv, const char *dir) { - struct io io = {}; - - init_io(&io, NULL, IO_AP); - io.pipe = fd; - if (format_argv(io.argv, argv, flags)) - return run_io_do(&io); - close(fd); - return FALSE; + return io_complete(IO_FG, argv, dir, -1); } static bool -run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags) +io_run_append(const char **argv, int fd) { - return init_io_rd(io, argv, dir, flags) && start_io(io); + return io_complete(IO_AP, argv, NULL, -1); } static bool @@ -780,7 +948,7 @@ io_read(struct io *io, void *buf, size_t bufsize) } while (1); } -DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ) +DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ) static char * io_get(struct io *io, int c, bool can_read) @@ -817,7 +985,7 @@ io_get(struct io *io, int c, bool can_read) memmove(io->buf, io->bufpos, io->bufsize); if (io->bufalloc == io->bufsize) { - if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ)) + if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ)) return NULL; io->bufalloc += BUFSIZ; } @@ -860,16 +1028,16 @@ io_read_buf(struct io *io, char buf[], size_t bufsize) string_ncopy_do(buf, bufsize, result, strlen(result)); } - return done_io(io) && result; + return io_done(io) && result; } static bool -run_io_buf(const char **argv, char buf[], size_t bufsize) +io_run_buf(const char **argv, char buf[], size_t bufsize) { struct io io = {}; - return run_io_rd(&io, argv, NULL, FORMAT_NONE) - && io_read_buf(&io, buf, bufsize); + io_prepare(&io, NULL, IO_RD, argv); + return io_start(&io) && io_read_buf(&io, buf, bufsize); } static int @@ -879,7 +1047,7 @@ io_load(struct io *io, const char *separators, char *name; int state = OK; - if (!start_io(io)) + if (!io_start(io)) return ERR; while (state == OK && (name = io_get(io, '\n', TRUE))) { @@ -905,19 +1073,19 @@ io_load(struct io *io, const char *separators, if (state != ERR && io_error(io)) state = ERR; - done_io(io); + io_done(io); return state; } static int -run_io_load(const char **argv, const char *separators, +io_run_load(const char **argv, const char *separators, int (*read_property)(char *, size_t, char *, size_t)) { struct io io = {}; - return init_io_rd(&io, argv, NULL, FORMAT_NONE) - ? io_load(&io, separators, read_property) : ERR; + io_prepare(&io, NULL, IO_RD, argv); + return io_load(&io, separators, read_property); } @@ -1005,7 +1173,8 @@ enum request { #define REQ_(req, help) REQ_##req /* Offset all requests to avoid conflicts with ncurses getch values. */ - REQ_OFFSET = KEY_MAX + 1, + REQ_UNKNOWN = KEY_MAX + 1, + REQ_OFFSET, REQ_INFO #undef REQ_GROUP @@ -1037,7 +1206,7 @@ get_request(const char *name) if (enum_equals(req_info[i], name, namelen)) return req_info[i].request; - return REQ_NONE; + return REQ_UNKNOWN; } @@ -1075,7 +1244,6 @@ static FILE *opt_tty = 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))) -#define mkdate(time) string_date(time, opt_date) /* @@ -1111,6 +1279,8 @@ LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \ LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \ LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \ @@ -1244,7 +1414,7 @@ struct keybinding { enum request request; }; -static const struct keybinding default_keybindings[] = { +static struct keybinding default_keybindings[] = { /* View switching */ { 'm', REQ_VIEW_MAIN }, { 'd', REQ_VIEW_DIFF }, @@ -1354,12 +1524,28 @@ static void add_keybinding(enum keymap keymap, enum request request, int key) { struct keybinding_table *table = &keybindings[keymap]; + size_t i; + + for (i = 0; i < keybindings[keymap].size; i++) { + if (keybindings[keymap].data[i].alias == key) { + keybindings[keymap].data[i].request = request; + return; + } + } table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data)); if (!table->data) die("Failed to allocate keybinding"); table->data[table->size].alias = key; table->data[table->size++].request = request; + + if (request == REQ_NONE && keymap == KEYMAP_GENERIC) { + int i; + + for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) + if (default_keybindings[i].alias == key) + default_keybindings[i].request = REQ_NONE; + } } /* Looks for a key binding first in the given map, then in the generic map, and @@ -1551,7 +1737,7 @@ add_run_request(enum keymap keymap, int key, int argc, const char **argv) req->key = key; req->argv[0] = NULL; - if (!format_argv(req->argv, argv, FORMAT_NONE)) + if (!argv_copy(req->argv, argv, TRUE)) return REQ_NONE; return REQ_NONE + ++run_requests; @@ -1569,6 +1755,7 @@ static void add_builtin_run_requests(void) { const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL }; + const char *checkout[] = { "git", "checkout", "%(branch)", NULL }; const char *commit[] = { "git", "commit", NULL }; const char *gc[] = { "git", "gc", NULL }; struct { @@ -1579,13 +1766,16 @@ add_builtin_run_requests(void) } reqs[] = { { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick }, { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit }, + { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout }, { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc }, }; int i; for (i = 0; i < ARRAY_SIZE(reqs); i++) { - enum request req; + enum request req = get_keybinding(reqs[i].keymap, reqs[i].key); + if (req != reqs[i].key) + continue; 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); @@ -1831,7 +2021,7 @@ option_bind_command(int argc, const char *argv[]) return ERR; } - if (set_keymap(&keymap, argv[0]) == ERR) { + if (!set_keymap(&keymap, argv[0])) { config_msg = "Unknown key map"; return ERR; } @@ -1843,7 +2033,7 @@ option_bind_command(int argc, const char *argv[]) } request = get_request(argv[2]); - if (request == REQ_NONE) { + if (request == REQ_UNKNOWN) { static const struct enum_map obsolete[] = { ENUM_MAP("cherry-pick", REQ_NONE), ENUM_MAP("screen-resize", REQ_NONE), @@ -1858,9 +2048,9 @@ option_bind_command(int argc, const char *argv[]) return ERR; } } - if (request == REQ_NONE && *argv[2]++ == '!') + if (request == REQ_UNKNOWN && *argv[2]++ == '!') request = add_run_request(keymap, key, argc - 2, argv + 2); - if (request == REQ_NONE) { + if (request == REQ_UNKNOWN) { config_msg = "Unknown request name"; return ERR; } @@ -1959,8 +2149,6 @@ load_options(void) const char *tigrc_system = getenv("TIGRC_SYSTEM"); char buf[SIZEOF_STR]; - add_builtin_run_requests(); - if (!tigrc_system) tigrc_system = SYSCONFDIR "/tigrc"; load_option_file(tigrc_system); @@ -1972,6 +2160,10 @@ load_options(void) } load_option_file(tigrc_user); + /* Add _after_ loading config files to avoid adding run requests + * that conflict with keybindings. */ + add_builtin_run_requests(); + return OK; } @@ -1996,8 +2188,24 @@ static unsigned int current_view; static char ref_blob[SIZEOF_REF] = ""; static char ref_commit[SIZEOF_REF] = "HEAD"; static char ref_head[SIZEOF_REF] = "HEAD"; +static char ref_branch[SIZEOF_REF] = ""; + +enum view_type { + VIEW_MAIN, + VIEW_DIFF, + VIEW_LOG, + VIEW_TREE, + VIEW_BLOB, + VIEW_BLAME, + VIEW_BRANCH, + VIEW_HELP, + VIEW_PAGER, + VIEW_STATUS, + VIEW_STAGE, +}; struct view { + enum view_type type; /* View type */ const char *name; /* View name */ const char *cmd_env; /* Command line set via environment */ const char *id; /* Points to either of ref_{head,commit,blob} */ @@ -2030,6 +2238,7 @@ struct view { /* If non-NULL, points to the view that opened this view. If this view * is closed tig will switch back to the parent view. */ struct view *parent; + struct view *prev; /* Buffering */ size_t lines; /* Total number of lines */ @@ -2082,12 +2291,11 @@ static struct view_ops status_ops; static struct view_ops tree_ops; static struct view_ops branch_ops; -#define VIEW_STR(name, env, ref, ops, map, git) \ - { name, #env, ref, ops, map, git } +#define VIEW_STR(type, name, env, ref, ops, map, git) \ + { type, name, #env, ref, ops, map, git } #define VIEW_(id, name, ops, git, ref) \ - VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git) - + VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git) static struct view views[] = { VIEW_(MAIN, "main", &main_ops, TRUE, ref_head), @@ -2104,7 +2312,6 @@ static struct view views[] = { }; #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) -#define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1) #define foreach_view(view, i) \ for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++) @@ -2112,6 +2319,18 @@ static struct view views[] = { #define view_is_displayed(view) \ (view == display[0] || view == display[1]) +static enum request +view_request(struct view *view, enum request request) +{ + if (!view || !view->lines) + return request; + return view->ops->request(view, request, &view->line[view->lineno]); +} + + +/* + * View drawing. + */ static inline void set_view_attr(struct view *view, enum line_type type) @@ -2235,7 +2454,7 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo static bool draw_date(struct view *view, struct time *time) { - const char *date = time && time->sec ? mkdate(time) : ""; + const char *date = mkdate(time, opt_date); int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS; return draw_field(view, LINE_DATE, date, cols, FALSE); @@ -2380,7 +2599,7 @@ update_view_title(struct view *view) assert(view_is_displayed(view)); - if (view != VIEW(REQ_VIEW_STATUS) && view->lines) { + if (view->type != VIEW_STATUS && view->lines) { unsigned int view_lines = view->offset + view->height; unsigned int lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines @@ -2502,6 +2721,11 @@ 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) @@ -2925,15 +3149,6 @@ reset_view(struct view *view) view->update_secs = 0; } -static void -free_argv(const char *argv[]) -{ - int argc; - - for (argc = 0; argv[argc]; argc++) - free((void *) argv[argc]); -} - static const char * format_arg(const char *name) { @@ -2951,6 +3166,7 @@ format_arg(const char *name) FORMAT_VAR("%(head)", ref_head, ""), FORMAT_VAR("%(commit)", ref_commit, ""), FORMAT_VAR("%(blob)", ref_blob, ""), + FORMAT_VAR("%(branch)", ref_branch, ""), }; int i; @@ -2958,8 +3174,10 @@ format_arg(const char *name) if (!strncmp(name, vars[i].name, vars[i].namelen)) return *vars[i].value ? vars[i].value : vars[i].value_if_empty; + report("Unknown replacement: `%s`", name); return NULL; } + static bool format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags) { @@ -2967,7 +3185,7 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl int argc; bool noreplace = flags == FORMAT_NONE; - free_argv(dst_argv); + argv_free(dst_argv); for (argc = 0; src_argv[argc]; argc++) { const char *arg = src_argv[argc]; @@ -2979,8 +3197,6 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl const char *value; if (!next || noreplace) { - if (flags == FORMAT_DASH && !strcmp(arg, "--")) - noreplace = TRUE; len = strlen(arg); value = ""; @@ -2988,7 +3204,6 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl value = format_arg(next); if (!value) { - report("Unknown replacement: `%s`", next); return FALSE; } } @@ -3041,8 +3256,8 @@ end_update(struct view *view, bool force) if (!force) return; if (force) - kill_io(view->pipe); - done_io(view->pipe); + io_kill(view->pipe); + io_done(view->pipe); view->pipe = NULL; } @@ -3056,12 +3271,27 @@ setup_update(struct view *view, const char *vid) } static bool -prepare_update(struct view *view, const char *argv[], const char *dir, - enum format_flags flags) +prepare_io(struct view *view, const char *dir, const char *argv[], bool replace) +{ + io_init(&view->io, dir, IO_RD); + return format_argv(view->io.argv, argv, replace ? FORMAT_ALL : FORMAT_NONE); +} + +static bool +prepare_update(struct view *view, const char *argv[], const char *dir) { if (view->pipe) end_update(view, TRUE); - return init_io_rd(&view->io, argv, dir, flags); + return prepare_io(view, dir, argv, FALSE); +} + +static bool +start_update(struct view *view, const char **argv, const char *dir) +{ + if (view->pipe) + io_done(view->pipe); + return prepare_io(view, dir, argv, FALSE) && + io_start(&view->io); } static bool @@ -3082,7 +3312,7 @@ begin_update(struct view *view, bool 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)) { + } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) { return FALSE; } @@ -3093,7 +3323,7 @@ begin_update(struct view *view, bool refresh) string_copy_rev(view->ref, view->id); } - if (!start_io(&view->io)) + if (!io_start(&view->io)) return FALSE; setup_update(view, view->id); @@ -3160,7 +3390,7 @@ update_view(struct view *view) /* Keep the displayed view in sync with line number scaling. */ if (digits != view->digits) { view->digits = digits; - if (opt_line_number || view == VIEW(REQ_VIEW_BLAME)) + if (opt_line_number || view->type == VIEW_BLAME) redraw = TRUE; } } @@ -3170,7 +3400,8 @@ update_view(struct view *view) end_update(view, TRUE); } else if (io_eof(view->pipe)) { - report(""); + if (view_is_displayed(view)) + report(""); end_update(view, FALSE); } @@ -3267,6 +3498,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags) if (split) { display[1] = view; current_view = 1; + view->parent = prev; } else if (!nomaximize) { /* Maximize the current view. */ memset(display, 0, sizeof(display)); @@ -3274,9 +3506,9 @@ 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. */ + /* No prev signals that this is the first loaded view. */ if (prev && view != prev) { - view->parent = prev; + view->prev = prev; } /* Resize the view when switching between split- and full-screen, @@ -3331,7 +3563,7 @@ open_external_viewer(const char *argv[], const char *dir) { def_prog_mode(); /* save current tty modes */ endwin(); /* restore original tty modes */ - run_io_fg(argv, dir); + io_run_fg(argv, dir); fprintf(stderr, "Press Enter to continue"); getc(opt_tty); reset_prog_mode(); @@ -3379,7 +3611,7 @@ open_run_request(enum request request) if (format_argv(argv, req->argv, FORMAT_ALL)) open_external_viewer(argv, NULL); - free_argv(argv); + argv_free(argv); } /* @@ -3396,22 +3628,13 @@ view_driver(struct view *view, enum request request) if (request > REQ_NONE) { open_run_request(request); - /* FIXME: When all views can refresh always do this. */ - if (view == VIEW(REQ_VIEW_STATUS) || - view == VIEW(REQ_VIEW_MAIN) || - view == VIEW(REQ_VIEW_LOG) || - view == VIEW(REQ_VIEW_BRANCH) || - view == VIEW(REQ_VIEW_STAGE)) - request = REQ_REFRESH; - else - return TRUE; + view_request(view, REQ_REFRESH); + return TRUE; } - if (view && view->lines) { - request = view->ops->request(view, request, &view->line[view->lineno]); - if (request == REQ_NONE) - return TRUE; - } + request = view_request(view, request); + if (request == REQ_NONE) + return TRUE; switch (request) { case REQ_MOVE_UP: @@ -3489,16 +3712,7 @@ view_driver(struct view *view, enum request request) case REQ_PREVIOUS: request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP; - if ((view == VIEW(REQ_VIEW_DIFF) && - view->parent == VIEW(REQ_VIEW_MAIN)) || - (view == VIEW(REQ_VIEW_DIFF) && - view->parent == VIEW(REQ_VIEW_BLAME)) || - (view == VIEW(REQ_VIEW_STAGE) && - view->parent == VIEW(REQ_VIEW_STATUS)) || - (view == VIEW(REQ_VIEW_BLOB) && - view->parent == VIEW(REQ_VIEW_TREE)) || - (view == VIEW(REQ_VIEW_MAIN) && - view->parent == VIEW(REQ_VIEW_BRANCH))) { + if (view->parent) { int line; view = view->parent; @@ -3507,9 +3721,7 @@ view_driver(struct view *view, enum request request) if (view_is_displayed(view)) update_view_title(view); if (line != view->lineno) - view->ops->request(view, REQ_ENTER, - &view->line[view->lineno]); - + view_request(view, REQ_ENTER); } else { move_view(view, request); } @@ -3580,8 +3792,7 @@ view_driver(struct view *view, enum request request) break; case REQ_STOP_LOADING: - for (i = 0; i < ARRAY_SIZE(views); i++) { - view = &views[i]; + foreach_view(view, i) { if (view->pipe) report("Stopped loading the %s view", view->name), end_update(view, TRUE); @@ -3605,13 +3816,12 @@ view_driver(struct view *view, enum request request) break; case REQ_VIEW_CLOSE: - /* XXX: Mark closed views by letting view->parent point to the + /* XXX: Mark closed views by letting view->prev point to the * view itself. Parents to closed view should never be * followed. */ - if (view->parent && - view->parent->parent != view->parent) { - maximize_view(view->parent); - view->parent = view; + if (view->prev && view->prev != view) { + maximize_view(view->prev); + view->prev = view; break; } /* Fall-through */ @@ -3719,8 +3929,8 @@ parse_timezone(struct time *time, const char *zone) tz = ('0' - zone[1]) * 60 * 60 * 10; tz += ('0' - zone[2]) * 60 * 60; - tz += ('0' - zone[3]) * 60; - tz += ('0' - zone[4]); + tz += ('0' - zone[3]) * 60 * 10; + tz += ('0' - zone[4]) * 60; if (zone[0] == '-') tz = -tz; @@ -3780,7 +3990,7 @@ open_commit_parent_menu(char buf[SIZEOF_STR], int *parents) for (i = 0; i < *parents; i++) { string_copy_rev(rev, &buf[SIZEOF_REV * i]); - if (!run_io_buf(revlist_argv, text, sizeof(text)) || + if (!io_run_buf(revlist_argv, text, sizeof(text)) || !(items[i].text = strdup(text))) { ok = FALSE; break; @@ -3807,7 +4017,7 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) }; int parents; - if (!run_io_buf(revlist_argv, buf, sizeof(buf)) || + if (!io_run_buf(revlist_argv, buf, sizeof(buf)) || (parents = strlen(buf) / 40) < 0) { report("Failed to get parent information"); return FALSE; @@ -3820,7 +4030,9 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) return FALSE; } - if (parents > 1 && !open_commit_parent_menu(buf, &parents)) + if (parents == 1) + parents = 0; + else if (!open_commit_parent_menu(buf, &parents)) return FALSE; string_copy_rev(rev, &buf[41 * parents]); @@ -3850,7 +4062,7 @@ add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *s const char *describe_argv[] = { "git", "describe", commit_id, NULL }; char ref[SIZEOF_STR]; - if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref) + if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref) return TRUE; /* This is the only fatal call, since it can "corrupt" the buffer. */ @@ -3874,7 +4086,7 @@ add_pager_refs(struct view *view, struct line *line) list = get_ref_list(commit_id); if (!list) { - if (view == VIEW(REQ_VIEW_DIFF)) + if (view->type == VIEW_DIFF) goto try_add_describe_ref; return; } @@ -3891,7 +4103,7 @@ add_pager_refs(struct view *view, struct line *line) is_tag = TRUE; } - if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) { + if (!is_tag && view->type == VIEW_DIFF) { try_add_describe_ref: /* Add -g "fake" reference. */ if (!add_describe_ref(buf, &bufpos, commit_id, sep)) @@ -3917,8 +4129,8 @@ pager_read(struct view *view, char *data) return FALSE; if (line->type == LINE_COMMIT && - (view == VIEW(REQ_VIEW_DIFF) || - view == VIEW(REQ_VIEW_LOG))) + (view->type == VIEW_DIFF || + view->type == VIEW_LOG)) add_pager_refs(view, line); return TRUE; @@ -3933,8 +4145,8 @@ pager_request(struct view *view, enum request request, struct line *line) return request; if (line->type == LINE_COMMIT && - (view == VIEW(REQ_VIEW_LOG) || - view == VIEW(REQ_VIEW_PAGER))) { + (view->type == VIEW_LOG || + view->type == VIEW_PAGER)) { open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT); split = 1; } @@ -3967,7 +4179,7 @@ pager_select(struct view *view, struct line *line) if (line->type == LINE_COMMIT) { char *text = (char *)line->data + STRING_SIZE("commit "); - if (view != VIEW(REQ_VIEW_PAGER)) + if (view->type != VIEW_PAGER) string_copy_rev(view->ref, text); string_copy_rev(ref_commit, text); } @@ -4314,7 +4526,6 @@ tree_read_date(struct view *view, char *text, bool *read_date) "git", "log", "--no-color", "--pretty=raw", "--cc", "--raw", view->id, "--", path, NULL }; - struct io io = {}; if (!view->lines) { tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL); @@ -4322,13 +4533,11 @@ tree_read_date(struct view *view, char *text, bool *read_date) return TRUE; } - if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) { + if (!start_update(view, log_file, opt_cdup)) { report("Failed to load tree data"); return TRUE; } - done_io(view->pipe); - view->io = io; *read_date = TRUE; return FALSE; @@ -4366,7 +4575,7 @@ tree_read_date(struct view *view, char *text, bool *read_date) } if (annotated == view->lines) - kill_io(view->pipe); + io_kill(view->pipe); } return TRUE; } @@ -4457,14 +4666,15 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno) } static void -open_blob_editor() +open_blob_editor(const char *id) { + const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL }; char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX"; int fd = mkstemp(file); if (fd == -1) report("Failed to create temporary file"); - else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd)) + else if (!io_run_append(blob_argv, fd)) report("Failed to save blob data to file"); else open_editor(file); @@ -4476,6 +4686,7 @@ static enum request tree_request(struct view *view, enum request request, struct line *line) { enum open_flags flags; + struct tree_entry *entry = line->data; switch (request) { case REQ_VIEW_BLAME: @@ -4491,7 +4702,7 @@ tree_request(struct view *view, enum request request, struct line *line) if (line->type != LINE_TREE_FILE) { report("Edit only supported for files"); } else if (!is_head_commit(view->vid)) { - open_blob_editor(); + open_blob_editor(entry->id); } else { open_editor(opt_file); } @@ -4542,7 +4753,7 @@ tree_request(struct view *view, enum request request, struct line *line) break; case LINE_TREE_FILE: - flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; + flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT; request = REQ_VIEW_BLOB; break; @@ -4564,7 +4775,7 @@ tree_grep(struct view *view, struct line *line) const char *text[] = { entry->name, opt_author ? entry->author : "", - opt_date ? mkdate(&entry->time) : "", + mkdate(&entry->time, opt_date), NULL }; @@ -4610,7 +4821,7 @@ tree_prepare(struct view *view) opt_path[0] = 0; } - return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL); + return prepare_io(view, opt_cdup, view->ops->argv, TRUE); } static const char *tree_argv[SIZEOF_ARG] = { @@ -4642,7 +4853,7 @@ blob_request(struct view *view, enum request request, struct line *line) { switch (request) { case REQ_EDIT: - open_blob_editor(); + open_blob_editor(view->vid); return REQ_NONE; default: return pager_request(view, request, line); @@ -4675,18 +4886,6 @@ static struct view_ops blob_ops = { * reading output from git-blame. */ -static const char *blame_head_argv[] = { - "git", "blame", "--incremental", "--", "%(file)", NULL -}; - -static const char *blame_ref_argv[] = { - "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL -}; - -static const char *blame_cat_file_argv[] = { - "git", "cat-file", "blob", "%(ref):%(file)", NULL -}; - struct blame_commit { char id[SIZEOF_REV]; /* SHA1 ID. */ char title[128]; /* First line of the commit message. */ @@ -4707,14 +4906,19 @@ blame_open(struct view *view) { char path[SIZEOF_STR]; - if (!view->parent && *opt_prefix) { + if (!view->prev && *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)) + const char *blame_cat_file_argv[] = { + "git", "cat-file", "blob", path, NULL + }; + + if (!string_format(path, "%s:%s", opt_ref, opt_file) || + !start_update(view, blame_cat_file_argv, opt_cdup)) return FALSE; } @@ -4804,19 +5008,19 @@ static bool blame_read_file(struct view *view, const char *line, bool *read_file) { if (!line) { - const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv; - struct io io = {}; + const char *blame_argv[] = { + "git", "blame", "--incremental", + *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL + }; - if (view->lines == 0 && !view->parent) + if (view->lines == 0 && !view->prev) die("No blame exist for %s", view->vid); - if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) { + if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) { report("Failed to load blame data"); return TRUE; } - done_io(view->pipe); - view->io = io; *read_file = FALSE; return FALSE; @@ -4952,7 +5156,7 @@ setup_blame_parent_line(struct view *view, struct blame *blame) int blamed_lineno = -1; char *line; - if (!run_io(&io, diff_tree_argv, NULL, IO_RD)) + if (!io_run(&io, diff_tree_argv, NULL, IO_RD)) return; while ((line = io_get(&io, '\n', TRUE))) { @@ -4973,13 +5177,13 @@ setup_blame_parent_line(struct view *view, struct blame *blame) } } - done_io(&io); + io_done(&io); } static enum request blame_request(struct view *view, enum request request, struct line *line) { - enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; + enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT; struct blame *blame = line->data; switch (request) { @@ -5025,7 +5229,7 @@ blame_request(struct view *view, enum request request, struct line *line) diff_index_argv[7] = "/dev/null"; } - if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) { + if (!prepare_update(diff, diff_index_argv, NULL)) { report("Failed to allocate diff command"); break; } @@ -5054,7 +5258,7 @@ blame_grep(struct view *view, struct line *line) commit ? commit->title : "", commit ? commit->id : "", commit && opt_author ? commit->author : "", - commit && opt_date ? mkdate(&commit->time) : "", + commit ? mkdate(&commit->time, opt_date) : "", NULL }; @@ -5163,7 +5367,7 @@ branch_request(struct view *view, enum request request, struct line *line) }; struct view *main_view = VIEW(REQ_VIEW_MAIN); - if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) { + if (!prepare_update(main_view, all_branches_argv, NULL)) { report("Failed to load view of all branches"); return REQ_NONE; } @@ -5244,7 +5448,7 @@ branch_open(struct view *view) "--simplify-by-decoration", "--all", NULL }; - if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) { + if (!start_update(view, branch_log, NULL)) { report("Failed to load branch data"); return TRUE; } @@ -5278,6 +5482,7 @@ branch_select(struct view *view, struct line *line) string_copy_rev(view->ref, branch->ref->id); string_copy_rev(ref_commit, branch->ref->id); string_copy_rev(ref_head, branch->ref->id); + string_copy_rev(ref_branch, branch->ref->name); } static struct view_ops branch_ops = { @@ -5364,7 +5569,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty char *buf; struct io io = {}; - if (!run_io(&io, argv, opt_cdup, IO_RD)) + if (!io_run(&io, argv, opt_cdup, IO_RD)) return FALSE; add_line_data(view, NULL, type); @@ -5423,14 +5628,14 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty if (io_error(&io)) { error_out: - done_io(&io); + io_done(&io); return FALSE; } if (!view->line[view->lines - 1].data) add_line_data(view, NULL, LINE_STAT_NONE); - done_io(&io); + io_done(&io); return TRUE; } @@ -5541,7 +5746,7 @@ status_open(struct view *view) add_line_data(view, NULL, LINE_STAT_HEAD); status_update_onbranch(); - run_io_bg(update_index_argv); + io_run_bg(update_index_argv); if (is_initial_commit()) { if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED)) @@ -5647,7 +5852,7 @@ status_enter(struct view *view, struct line *line) "--", "/dev/null", newpath, NULL }; - if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH)) + if (!prepare_update(stage, no_head_diff_argv, opt_cdup)) return status_load_error(view, stage, newpath); } else { const char *index_show_argv[] = { @@ -5656,7 +5861,7 @@ status_enter(struct view *view, struct line *line) oldpath, newpath, NULL }; - if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH)) + if (!prepare_update(stage, index_show_argv, opt_cdup)) return status_load_error(view, stage, newpath); } @@ -5673,7 +5878,7 @@ status_enter(struct view *view, struct line *line) "-C", "-M", "--", oldpath, newpath, NULL }; - if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH)) + if (!prepare_update(stage, files_show_argv, opt_cdup)) return status_load_error(view, stage, newpath); if (status) info = "Unstaged changes to %s"; @@ -5704,7 +5909,7 @@ status_enter(struct view *view, struct line *line) die("line type %d not handled in switch", line->type); } - split = view_is_displayed(view) ? OPEN_SPLIT : 0; + split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT; open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split); if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) { if (status) { @@ -5759,11 +5964,11 @@ status_update_prepare(struct io *io, enum line_type type) switch (type) { case LINE_STAT_STAGED: - return run_io(io, staged_argv, opt_cdup, IO_WR); + return io_run(io, staged_argv, opt_cdup, IO_WR); case LINE_STAT_UNSTAGED: case LINE_STAT_UNTRACKED: - return run_io(io, others_argv, opt_cdup, IO_WR); + return io_run(io, others_argv, opt_cdup, IO_WR); default: die("line type %d not handled in switch", type); @@ -5809,7 +6014,7 @@ status_update_file(struct status *status, enum line_type type) return FALSE; result = status_update_write(&io, status, type); - return done_io(&io) && result; + return io_done(&io) && result; } static bool @@ -5846,7 +6051,7 @@ status_update_files(struct view *view, struct line *line) } string_copy(view->ref, buf); - return done_io(&io) && result; + return io_done(&io) && result; } static bool @@ -5909,13 +6114,13 @@ status_revert(struct status *status, enum line_type type, bool has_none) reset_argv[4] = NULL; } - if (!run_io_fg(reset_argv, opt_cdup)) + if (!io_run_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 io_run_fg(checkout_argv, opt_cdup); } return FALSE; @@ -6102,15 +6307,15 @@ stage_apply_chunk(struct view *view, struct line *chunk, bool revert) apply_argv[argc++] = "-R"; apply_argv[argc++] = "-"; apply_argv[argc++] = NULL; - if (!run_io(&io, apply_argv, opt_cdup, IO_WR)) + if (!io_run(&io, apply_argv, opt_cdup, IO_WR)) return FALSE; if (!stage_diff_write(&io, diff_hdr, chunk) || !stage_diff_write(&io, chunk, view->line + view->lines)) chunk = NULL; - done_io(&io); - run_io_bg(update_index_argv); + io_done(&io); + io_run_bg(update_index_argv); return chunk ? TRUE : FALSE; } @@ -6584,7 +6789,7 @@ main_read(struct view *view, char *line) if (!line) { int i; - if (!view->lines && !view->parent) + if (!view->lines && !view->prev) die("No revisions match the given arguments."); if (view->lines > 0) { commit = view->line[view->lines - 1].data; @@ -6674,7 +6879,7 @@ main_read(struct view *view, char *line) static enum request main_request(struct view *view, enum request request, struct line *line) { - enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT; + enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT; switch (request) { case REQ_ENTER: @@ -6715,7 +6920,7 @@ main_grep(struct view *view, struct line *line) const char *text[] = { commit->title, opt_author ? commit->author : "", - opt_date ? mkdate(&commit->time) : "", + mkdate(&commit->time, opt_date), NULL }; @@ -6743,166 +6948,6 @@ static struct view_ops main_ops = { }; -/* - * Unicode / UTF-8 handling - * - * NOTE: Much of the following code for dealing with Unicode is derived from - * ELinks' UTF-8 code developed by Scrool . Origin file is - * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28. - */ - -static inline int -unicode_width(unsigned long c, int tab_size) -{ - if (c >= 0x1100 && - (c <= 0x115f /* Hangul Jamo */ - || c == 0x2329 - || c == 0x232a - || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) - /* CJK ... Yi */ - || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */ - || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */ - || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */ - || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */ - || (c >= 0xffe0 && c <= 0xffe6) - || (c >= 0x20000 && c <= 0x2fffd) - || (c >= 0x30000 && c <= 0x3fffd))) - return 2; - - if (c == '\t') - return tab_size; - - return 1; -} - -/* Number of bytes used for encoding a UTF-8 character indexed by first byte. - * Illegal bytes are set one. */ -static const unsigned char utf8_bytes[256] = { - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1, -}; - -static inline unsigned char -utf8_char_length(const char *string, const char *end) -{ - int c = *(unsigned char *) string; - - return utf8_bytes[c]; -} - -/* Decode UTF-8 multi-byte representation into a Unicode character. */ -static inline unsigned long -utf8_to_unicode(const char *string, size_t length) -{ - unsigned long unicode; - - switch (length) { - case 1: - unicode = string[0]; - break; - case 2: - unicode = (string[0] & 0x1f) << 6; - unicode += (string[1] & 0x3f); - break; - case 3: - unicode = (string[0] & 0x0f) << 12; - unicode += ((string[1] & 0x3f) << 6); - unicode += (string[2] & 0x3f); - break; - case 4: - unicode = (string[0] & 0x0f) << 18; - unicode += ((string[1] & 0x3f) << 12); - unicode += ((string[2] & 0x3f) << 6); - unicode += (string[3] & 0x3f); - break; - case 5: - unicode = (string[0] & 0x0f) << 24; - unicode += ((string[1] & 0x3f) << 18); - unicode += ((string[2] & 0x3f) << 12); - unicode += ((string[3] & 0x3f) << 6); - unicode += (string[4] & 0x3f); - break; - case 6: - unicode = (string[0] & 0x01) << 30; - unicode += ((string[1] & 0x3f) << 24); - unicode += ((string[2] & 0x3f) << 18); - unicode += ((string[3] & 0x3f) << 12); - unicode += ((string[4] & 0x3f) << 6); - unicode += (string[5] & 0x3f); - break; - default: - die("Invalid Unicode length"); - } - - /* Invalid characters could return the special 0xfffd value but NUL - * should be just as good. */ - return unicode > 0xffff ? 0 : unicode; -} - -/* Calculates how much of string can be shown within the given maximum width - * and sets trimmed parameter to non-zero value if all of string could not be - * shown. If the reserve flag is TRUE, it will reserve at least one - * trailing character, which can be useful when drawing a delimiter. - * - * Returns the number of bytes to output from string to satisfy max_width. */ -static size_t -utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size) -{ - const char *string = *start; - const char *end = strchr(string, '\0'); - unsigned char last_bytes = 0; - size_t last_ucwidth = 0; - - *width = 0; - *trimmed = 0; - - while (string < end) { - unsigned char bytes = utf8_char_length(string, end); - size_t ucwidth; - unsigned long unicode; - - if (string + bytes > end) - break; - - /* Change representation to figure out whether - * it is a single- or double-width character. */ - - unicode = utf8_to_unicode(string, bytes); - /* FIXME: Graceful handling of invalid Unicode character. */ - if (!unicode) - break; - - ucwidth = unicode_width(unicode, tab_size); - if (skip > 0) { - skip -= ucwidth <= skip ? ucwidth : skip; - *start += bytes; - } - *width += ucwidth; - if (*width > max_width) { - *trimmed = 1; - *width -= ucwidth; - if (reserve && *width == max_width) { - string -= last_bytes; - *width -= last_ucwidth; - } - break; - } - - string += bytes; - last_bytes = ucwidth ? bytes : 0; - last_ucwidth = ucwidth; - } - - return string - *start; -} - - /* * Status management */ @@ -7425,14 +7470,15 @@ load_refs(void) size_t i; if (!init) { - argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"); + if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE")) + die("TIG_LS_REMOTE contains too many arguments"); init = TRUE; } if (!*opt_git_dir) return OK; - if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) && + if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) && !prefixcmp(opt_head, "refs/heads/")) { char *offset = opt_head + STRING_SIZE("refs/heads/"); @@ -7443,7 +7489,7 @@ load_refs(void) for (i = 0; i < refs_size; i++) refs[i]->id[0] = 0; - if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR) + if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR) return ERR; /* Update the ref lists to reflect changes. */ @@ -7564,7 +7610,7 @@ load_git_config(void) { const char *config_list_argv[] = { "git", "config", "--list", NULL }; - return run_io_load(config_list_argv, "=", read_repo_config_option); + return io_run_load(config_list_argv, "=", read_repo_config_option); } static int @@ -7599,7 +7645,7 @@ load_repo_info(void) "--show-cdup", "--show-prefix", NULL }; - return run_io_load(rev_parse_argv, "=", read_repo_info); + return io_run_load(rev_parse_argv, "=", read_repo_info); } @@ -7730,7 +7776,7 @@ parse_options(int argc, const char *argv[]) die("command too long"); } - if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE)) + if (!prepare_update(VIEW(request), custom_argv, NULL)) die("Failed to format arguments"); return request; @@ -7780,7 +7826,9 @@ main(int argc, const char *argv[]) die("Failed to load refs."); foreach_view (view, i) - argv_from_env(view->ops->argv, view->cmd_env); + if (!argv_from_env(view->ops->argv, view->cmd_env)) + die("Too many arguments in the `%s` environment variable", + view->cmd_env); init_display(); @@ -7797,6 +7845,10 @@ main(int argc, const char *argv[]) /* Some low-level request handling. This keeps access to * status_win restricted. */ switch (request) { + case REQ_NONE: + report("Unknown key, press %s for help", + get_key(view->keymap, REQ_VIEW_HELP)); + break; case REQ_PROMPT: { char *cmd = read_prompt(":"); @@ -7823,7 +7875,7 @@ main(int argc, const char *argv[]) if (!argv_from_string(argv, &argc, cmd)) { report("Too many arguments"); - } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) { + } else if (!prepare_update(next, argv, NULL)) { report("Failed to format command"); } else { open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);