X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=a2fe255bfda8599b94c1d05c4d079cc01043ec35;hb=53c089443cd9885fa677becee4bf7ffd56c3c357;hp=58d7bd09137dd73ac495d2079eed24874934e97f;hpb=6a72f9bcd5aa5ef11500357077eb00a3c271bb61;p=tig.git diff --git a/tig.c b/tig.c index 58d7bd0..a2fe255 100644 --- a/tig.c +++ b/tig.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2006-2009 Jonas Fonseca +/* Copyright (c) 2006-2010 Jonas Fonseca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -67,11 +68,10 @@ static void __NORETURN die(const char *err, ...); static void warn(const char *msg, ...); static void report(const char *msg, ...); -static void set_nonblocking_input(bool loading); -static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve); #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 +102,17 @@ 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 +#define AUTHOR_COLS 19 -/* 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' @@ -143,18 +135,11 @@ struct ref_list { struct ref **refs; /* References for this ID. */ }; +static struct ref *get_ref_head(); static struct ref_list *get_ref_list(const char *id); -static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data); +static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data); 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. */ -}; - -static bool format_argv(const char *dst[], const char *src[], enum format_flags flags); - enum input_status { INPUT_OK, INPUT_SKIP, @@ -304,6 +289,9 @@ string_enum_compare(const char *str1, const char *str2, int len) return 0; } +#define enum_equals(entry, str, len) \ + ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len)) + struct enum_map { const char *name; int namelen; @@ -312,6 +300,24 @@ struct enum_map { #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value } +static char * +enum_map_name(const char *name, size_t namelen) +{ + static char buf[SIZEOF_STR]; + int bufpos; + + for (bufpos = 0; bufpos <= namelen; bufpos++) { + buf[bufpos] = tolower(name[bufpos]); + if (buf[bufpos] == '_') + buf[bufpos] = '-'; + } + + buf[bufpos] = 0; + return buf; +} + +#define enum_name(entry) enum_map_name((entry).name, (entry).namelen) + static bool map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name) { @@ -319,8 +325,7 @@ map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char int i; for (i = 0; i < map_size; i++) - if (namelen == map[i].namelen && - !string_enum_compare(name, map[i].name, namelen)) { + if (enum_equals(map[i], name, namelen)) { *value = map[i].value; return TRUE; } @@ -344,17 +349,299 @@ 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) + +enum date { +#define DATE_(name) DATE_##name + DATE_INFO +#undef DATE_ +}; + +static const struct enum_map date_map[] = { +#define DATE_(name) ENUM_MAP(#name, DATE_##name) + DATE_INFO +#undef DATE_ +}; + +struct time { + time_t sec; + int tz; +}; + +static inline int timecmp(const struct time *t1, const struct time *t2) +{ + return t1->sec - t2->sec; +} + static const char * -mkdate(const time_t *time) +mkdate(const struct time *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; - gmtime_r(time, &tm); + if (!date || !time || !time->sec) + return ""; + + if (date == DATE_RELATIVE) { + struct timeval now; + time_t date = time->sec + time->tz; + 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; + } + } + + 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; } +#define AUTHOR_VALUES \ + AUTHOR_(NO), \ + AUTHOR_(FULL), \ + AUTHOR_(ABBREVIATED) + +enum author { +#define AUTHOR_(name) AUTHOR_##name + AUTHOR_VALUES, +#undef AUTHOR_ + AUTHOR_DEFAULT = AUTHOR_FULL +}; + +static const struct enum_map author_map[] = { +#define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name) + AUTHOR_VALUES +#undef AUTHOR_ +}; + +static const char * +get_author_initials(const char *author) +{ + static char initials[AUTHOR_COLS * 6 + 1]; + size_t pos = 0; + const char *end = strchr(author, '\0'); + +#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-') + + memset(initials, 0, sizeof(initials)); + while (author < end) { + unsigned char bytes; + size_t i; + + while (is_initial_sep(*author)) + author++; + + bytes = utf8_char_length(author, end); + if (bytes < sizeof(initials) - 1 - pos) { + while (bytes--) { + initials[pos++] = *author++; + } + } + + for (i = pos; author < end && !is_initial_sep(*author); author++) { + if (i < sizeof(initials) - 1) + initials[i++] = *author; + } + + initials[i++] = 0; + } + + return initials; +} + + static bool argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) { @@ -373,7 +660,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; @@ -381,8 +668,59 @@ 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; + + if (!argv) + return; + for (argc = 0; argv[argc]; argc++) + free((void *) argv[argc]); + argv[0] = NULL; +} + +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++; + + if (!argv_realloc(argv, argc, 2)) + return FALSE; + + (*argv)[argc++] = strdup(arg); + (*argv)[argc] = NULL; + return TRUE; +} + +static bool +argv_append_array(const char ***dst_argv, const char *src_argv[]) +{ + int i; + + for (i = 0; src_argv && src_argv[i]; i++) + if (!argv_append(dst_argv, src_argv[i])) + return FALSE; + return TRUE; +} + +static bool +argv_copy(const char ***dst, const char *src[]) +{ + int argc; + + for (argc = 0; src[argc]; argc++) + if (!argv_append(dst, src[argc])) + return FALSE; + return TRUE; } @@ -400,12 +738,9 @@ 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. */ int pipe; /* Pipe end for reading or writing. */ + pid_t pid; /* PID of spawned process. */ int error; /* Error status. */ - const char *argv[SIZEOF_ARG]; /* Shell command arguments. */ char *buf; /* Read buffer. */ size_t bufalloc; /* Allocated buffer size. */ size_t bufsize; /* Buffer content size. */ @@ -414,36 +749,29 @@ struct io { }; static void -reset_io(struct io *io) +io_init(struct io *io) { + memset(io, 0, sizeof(*io)); io->pipe = -1; - io->pid = 0; - io->buf = io->bufpos = NULL; - io->bufalloc = io->bufsize = 0; - io->error = 0; - io->eof = 0; -} - -static void -init_io(struct io *io, const char *dir, enum io_type type) -{ - reset_io(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) +io_open(struct io *io, const char *fmt, ...) { - init_io(io, dir, IO_RD); - return format_argv(io->argv, argv, flags); -} + char name[SIZEOF_STR] = ""; + bool fits; + va_list args; -static bool -io_open(struct io *io, const char *name) -{ - init_io(io, NULL, IO_FD); + io_init(io); + + 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; @@ -451,20 +779,20 @@ io_open(struct io *io, const char *name) } 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_init(io); while (pid > 0) { int status; @@ -473,7 +801,7 @@ done_io(struct io *io) if (waiting < 0) { if (errno == EINTR) continue; - report("waitpid failed (%s)", strerror(errno)); + io->error = errno; return FALSE; } @@ -487,32 +815,37 @@ done_io(struct io *io) } static bool -start_io(struct io *io) +io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...) { int pipefds[2] = { -1, -1 }; + va_list args; - if (io->type == IO_FD) - return TRUE; + io_init(io); - if ((io->type == IO_RD || io->type == IO_WR) && - pipe(pipefds) < 0) + if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) { + io->error = errno; return FALSE; - else if (io->type == IO_AP) - pipefds[1] = io->pipe; + } else if (type == IO_AP) { + va_start(args, argv); + pipefds[1] = va_arg(args, int); + va_end(args); + } if ((io->pid = fork())) { - if (pipefds[!(io->type == IO_WR)] != -1) - close(pipefds[!(io->type == IO_WR)]); + if (io->pid == -1) + io->error = errno; + if (pipefds[!(type == IO_WR)] != -1) + close(pipefds[!(type == IO_WR)]); if (io->pid != -1) { - io->pipe = pipefds[!!(io->type == IO_WR)]; + io->pipe = pipefds[!!(type == IO_WR)]; return TRUE; } } else { - if (io->type != IO_FG) { + if (type != IO_FG) { int devnull = open("/dev/null", O_RDWR); - int readfd = io->type == IO_WR ? pipefds[0] : devnull; - int writefd = (io->type == IO_RD || io->type == IO_AP) + int readfd = type == IO_WR ? pipefds[0] : devnull; + int writefd = (type == IO_RD || type == IO_AP) ? pipefds[1] : devnull; dup2(readfd, STDIN_FILENO); @@ -526,72 +859,42 @@ start_io(struct io *io) close(pipefds[1]); } - if (io->dir && *io->dir && chdir(io->dir) == -1) - die("Failed to change directory: %s", strerror(errno)); + if (dir && *dir && chdir(dir) == -1) + exit(errno); - execvp(io->argv[0], (char *const*) io->argv); - die("Failed to execute program: %s", strerror(errno)); + execvp(argv[0], (char *const*) argv); + exit(errno); } - if (pipefds[!!(io->type == IO_WR)] != -1) - close(pipefds[!!(io->type == IO_WR)]); + if (pipefds[!!(type == IO_WR)] != -1) + close(pipefds[!!(type == IO_WR)]); return FALSE; } 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_complete(enum io_type type, const char **argv, const char *dir, int fd) { - return start_io(io) && done_io(io); -} - -static int -run_io_bg(const char **argv) -{ - struct io io = {}; + struct io io; - init_io(&io, NULL, IO_BG); - if (!format_argv(io.argv, argv, FORMAT_NONE)) - return FALSE; - return run_io_do(&io); + return io_run(&io, type, dir, argv, fd) && 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, enum format_flags flags) +io_run_append(const char **argv, int fd) { - return init_io_rd(io, argv, NULL, flags) && start_io(io); + return io_complete(IO_AP, argv, NULL, fd); } static bool @@ -640,7 +943,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) @@ -677,7 +980,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; } @@ -720,15 +1023,15 @@ 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 = {}; + struct io io; - return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize); + return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize); } static int @@ -738,9 +1041,6 @@ io_load(struct io *io, const char *separators, char *name; int state = OK; - if (!start_io(io)) - return ERR; - while (state == OK && (name = io_get(io, '\n', TRUE))) { char *value; size_t namelen; @@ -764,19 +1064,20 @@ 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 = {}; + struct io io; - return init_io_rd(&io, argv, NULL, FORMAT_NONE) - ? io_load(&io, separators, read_property) : ERR; + if (!io_run(&io, IO_RD, NULL, argv)) + return ERR; + return io_load(&io, separators, read_property); } @@ -842,6 +1143,7 @@ run_io_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)"), \ @@ -863,7 +1165,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 @@ -892,11 +1195,10 @@ get_request(const char *name) int i; for (i = 0; i < ARRAY_SIZE(req_info); i++) - if (req_info[i].namelen == namelen && - !string_enum_compare(req_info[i].name, name, namelen)) + if (enum_equals(req_info[i], name, namelen)) return req_info[i].request; - return REQ_NONE; + return REQ_UNKNOWN; } @@ -905,26 +1207,25 @@ get_request(const char *name) */ /* Option and state variables. */ -static bool opt_date = TRUE; -static bool opt_author = TRUE; +static enum date opt_date = DATE_DEFAULT; +static enum author opt_author = AUTHOR_DEFAULT; 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 = AUTHOR_COLS; static char opt_path[SIZEOF_STR] = ""; static char opt_file[SIZEOF_STR] = ""; static char opt_ref[SIZEOF_REF] = ""; static char opt_head[SIZEOF_REF] = ""; -static char opt_head_rev[SIZEOF_REV] = ""; static char opt_remote[SIZEOF_REF] = ""; static char opt_encoding[20] = "UTF-8"; -static bool opt_utf8 = TRUE; -static char opt_codeset[20] = "UTF-8"; -static iconv_t opt_iconv = ICONV_NONE; +static iconv_t opt_iconv_in = ICONV_NONE; +static iconv_t opt_iconv_out = ICONV_NONE; static char opt_search[SIZEOF_STR] = ""; static char opt_cdup[SIZEOF_STR] = ""; static char opt_prefix[SIZEOF_STR] = ""; @@ -932,9 +1233,12 @@ 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; -#define is_initial_commit() (!*opt_head_rev) -#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev))) +#define is_initial_commit() (!get_ref_head()) +#define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id))) /* @@ -970,6 +1274,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), \ @@ -996,6 +1302,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 { @@ -1050,8 +1358,7 @@ get_line_info(const char *name) enum line_type type; for (type = 0; type < ARRAY_SIZE(line_info); type++) - if (namelen == line_info[type].namelen && - !string_enum_compare(line_info[type].name, name, namelen)) + if (enum_equals(line_info[type], name, namelen)) return &line_info[type]; return NULL; @@ -1087,6 +1394,7 @@ struct line { unsigned int selected:1; unsigned int dirty:1; unsigned int cleareol:1; + unsigned int other:16; void *data; /* User data */ }; @@ -1101,7 +1409,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 }, @@ -1211,12 +1519,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 @@ -1314,26 +1638,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; @@ -1342,7 +1708,7 @@ get_key(enum request request) struct run_request { enum keymap keymap; int key; - const char *argv[SIZEOF_ARG]; + const char **argv; }; static struct run_request *run_request; @@ -1351,22 +1717,19 @@ static size_t run_requests; DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8) static enum request -add_run_request(enum keymap keymap, int key, int argc, const char **argv) +add_run_request(enum keymap keymap, int key, const char **argv) { struct run_request *req; - if (argc >= ARRAY_SIZE(req->argv) - 1) - return REQ_NONE; - if (!realloc_run_requests(&run_request, run_requests, 1)) return REQ_NONE; req = &run_request[run_requests]; req->keymap = keymap; req->key = key; - req->argv[0] = NULL; + req->argv = NULL; - if (!format_argv(req->argv, argv, FORMAT_NONE)) + if (!argv_copy(&req->argv, argv)) return REQ_NONE; return REQ_NONE + ++run_requests; @@ -1384,24 +1747,23 @@ 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 { - enum keymap keymap; - int key; - int argc; - const char **argv; - } reqs[] = { - { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick }, - { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit }, - { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc }, + struct run_request reqs[] = { + { KEYMAP_MAIN, 'C', cherry_pick }, + { KEYMAP_STATUS, 'C', commit }, + { KEYMAP_BRANCH, 'C', checkout }, + { KEYMAP_GENERIC, 'G', 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); - req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv); + if (req != reqs[i].key) + continue; + req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv); if (req != REQ_NONE) add_keybinding(reqs[i].keymap, req, reqs[i].key); } @@ -1492,7 +1854,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; } @@ -1519,9 +1881,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; @@ -1534,6 +1902,26 @@ static int parse_bool(bool *opt, const char *arg) return OK; } +static int 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; + + *opt = is_true ? map[1].value : map[0].value; + return OK; +} + +#define parse_enum(opt, arg, map) \ + parse_enum_do(opt, arg, map, ARRAY_SIZE(map)) + static int parse_string(char *opt, const char *arg, size_t optsize) { @@ -1568,10 +1956,10 @@ option_set_command(int argc, const char *argv[]) } if (!strcmp(argv[0], "show-author")) - return parse_bool(&opt_author, argv[2]); + return parse_enum(&opt_author, argv[2], author_map); if (!strcmp(argv[0], "show-date")) - return parse_bool(&opt_date, argv[2]); + return parse_enum(&opt_date, argv[2], date_map); if (!strcmp(argv[0], "show-rev-graph")) return parse_bool(&opt_rev_graph, argv[2]); @@ -1594,6 +1982,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); @@ -1609,7 +2000,7 @@ static int option_bind_command(int argc, const char *argv[]) { enum request request; - int keymap; + int keymap = -1; int key; if (argc < 3) { @@ -1617,7 +2008,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; } @@ -1629,7 +2020,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), @@ -1644,9 +2035,9 @@ option_bind_command(int argc, const char *argv[]) return ERR; } } - if (request == REQ_NONE && *argv[2]++ == '!') - request = add_run_request(keymap, key, argc - 2, argv + 2); - if (request == REQ_NONE) { + 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; } @@ -1723,10 +2114,10 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen) static void load_option_file(const char *path) { - struct io io = {}; + 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; @@ -1745,8 +2136,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); @@ -1758,6 +2147,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; } @@ -1782,8 +2175,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} */ @@ -1816,6 +2225,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 */ @@ -1829,6 +2239,8 @@ struct view { bool has_scrolled; /* View was scrolled. */ /* Loading */ + const char **argv; /* Shell command arguments. */ + const char *dir; /* Directory from which to execute. */ struct io io; struct io *pipe; time_t start_time; @@ -1852,6 +2264,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; @@ -1866,12 +2280,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), @@ -1888,7 +2301,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++) @@ -1896,20 +2308,24 @@ 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]); +} -enum line_graphic { - LINE_GRAPHIC_VLINE -}; -static chtype line_graphics[] = { - /* LINE_GRAPHIC_VLINE: */ '|' -}; +/* + * View drawing. + */ static inline void set_view_attr(struct view *view, enum line_type type) { if (!view->curline->selected && view->curtype != type) { - wattrset(view->win, get_line_attr(type)); + (void) wattrset(view->win, get_line_attr(type)); wchgat(view->win, -1, 0, type, NULL); view->curtype = type; } @@ -1919,6 +2335,7 @@ static int draw_chars(struct view *view, enum line_type type, const char *string, int max_len, bool use_tilde) { + static char out_buffer[BUFSIZ * 2]; int len = 0; int col = 0; int trimmed = FALSE; @@ -1927,22 +2344,28 @@ draw_chars(struct view *view, enum line_type type, const char *string, if (max_len <= 0) return 0; - if (opt_utf8) { - len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde); - } else { - col = len = strlen(string); - if (len > max_len) { - if (use_tilde) { - max_len -= 1; + len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size); + + set_view_attr(view, type); + if (len > 0) { + if (opt_iconv_out != ICONV_NONE) { + ICONV_CONST char *inbuf = (ICONV_CONST char *) string; + size_t inlen = len + 1; + + char *outbuf = out_buffer; + size_t outlen = sizeof(out_buffer); + + size_t ret; + + ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + string = out_buffer; + len = sizeof(out_buffer) - outlen; } - col = len = max_len; - trimmed = TRUE; } - } - set_view_attr(view, type); - if (len > 0) waddnstr(view->win, string, len); + } if (trimmed && use_tilde) { set_view_attr(view, LINE_DELIMITER); waddch(view->win, '~'); @@ -2018,35 +2441,22 @@ 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) +draw_date(struct view *view, struct time *time) { - const char *date = 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, DATE_COLS, FALSE); + return draw_field(view, LINE_DATE, date, cols, FALSE); } static bool draw_author(struct view *view, const char *author) { - bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author; + bool trim = opt_author_cols == 0 || opt_author_cols > 5; + bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim; - if (!trim) { - static char initials[10]; - size_t pos; - -#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@') - - memset(initials, 0, sizeof(initials)); - for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) { - while (is_initial_sep(*author)) - author++; - strncpy(&initials[pos], author, sizeof(initials) - 1 - pos); - while (*author && !is_initial_sep(author[1])) - author++; - } - - author = initials; - } + if (abbreviate && author) + author = get_author_initials(author); return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim); } @@ -2079,6 +2489,7 @@ draw_lineno(struct view *view, unsigned int lineno) int digits3 = view->digits < 3 ? 3 : view->digits; int max = MIN(view->width + view->yoffset - view->col, digits3); char *text = NULL; + chtype separator = opt_line_graphics ? ACS_VLINE : '|'; lineno += view->offset + 1; if (lineno == 1 || (lineno % opt_num_interval) == 0) { @@ -2092,7 +2503,7 @@ draw_lineno(struct view *view, unsigned int lineno) view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE); else view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3); - return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1); + return draw_graphic(view, LINE_DEFAULT, &separator, 1); } static bool @@ -2177,7 +2588,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 @@ -2223,6 +2634,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) { @@ -2240,7 +2660,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. */ @@ -2288,6 +2710,26 @@ 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) + static void toggle_view_option(bool *option, const char *help) { @@ -2309,8 +2751,14 @@ open_option_menu(void) }; int selected = 0; - if (prompt_menu("Toggle option", menu, &selected)) - toggle_view_option(menu[selected].data, menu[selected].text); + 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); + } } static void @@ -2353,15 +2801,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) @@ -2699,75 +3138,90 @@ reset_view(struct view *view) view->update_secs = 0; } -static void -free_argv(const char *argv[]) +static const char * +format_arg(const char *name) { - int argc; + static struct { + const char *name; + size_t namelen; + const char *value; + const char *value_if_empty; + } vars[] = { +#define FORMAT_VAR(name, value, value_if_empty) \ + { name, STRING_SIZE(name), value, value_if_empty } + FORMAT_VAR("%(directory)", opt_path, ""), + FORMAT_VAR("%(file)", opt_file, ""), + FORMAT_VAR("%(ref)", opt_ref, "HEAD"), + FORMAT_VAR("%(head)", ref_head, ""), + FORMAT_VAR("%(commit)", ref_commit, ""), + FORMAT_VAR("%(blob)", ref_blob, ""), + FORMAT_VAR("%(branch)", ref_branch, ""), + }; + int i; - for (argc = 0; argv[argc]; argc++) - free((void *) argv[argc]); + for (i = 0; i < ARRAY_SIZE(vars); i++) + 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) +format_argv(const char ***dst_argv, const char *src_argv[], bool replace) { char buf[SIZEOF_STR]; 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]; size_t bufpos = 0; + if (!strcmp(arg, "%(file-args)")) { + if (!argv_append_array(dst_argv, opt_file_args)) + break; + continue; + + } else if (!strcmp(arg, "%(diff-args)")) { + if (!argv_append_array(dst_argv, opt_diff_args)) + break; + continue; + + } else if (!strcmp(arg, "%(rev-args)")) { + if (!argv_append_array(dst_argv, opt_rev_args)) + break; + continue; + } + while (arg) { char *next = strstr(arg, "%("); int len = next - arg; const char *value; - if (!next || noreplace) { - if (flags == FORMAT_DASH && !strcmp(arg, "--")) - noreplace = TRUE; + if (!next || !replace) { len = strlen(arg); value = ""; - } else if (!prefixcmp(next, "%(directory)")) { - value = opt_path; - - } else if (!prefixcmp(next, "%(file)")) { - value = opt_file; - - } else if (!prefixcmp(next, "%(ref)")) { - value = *opt_ref ? opt_ref : "HEAD"; - - } else if (!prefixcmp(next, "%(head)")) { - value = ref_head; - - } else if (!prefixcmp(next, "%(commit)")) { - value = ref_commit; - - } else if (!prefixcmp(next, "%(blob)")) { - value = ref_blob; - } else { - report("Unknown replacement: `%s`", next); - return FALSE; + value = format_arg(next); + + if (!value) { + return FALSE; + } } if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value)) return FALSE; - arg = next && !noreplace ? strchr(next, ')') + 1 : NULL; + arg = next && replace ? strchr(next, ')') + 1 : NULL; } - dst_argv[argc] = strdup(buf); - if (!dst_argv[argc]) + if (!argv_append(dst_argv, buf)) break; } - dst_argv[argc] = NULL; - return src_argv[argc] == NULL; } @@ -2802,17 +3256,15 @@ end_update(struct view *view, bool force) while (!view->ops->read(view, NULL)) if (!force) return; - set_nonblocking_input(FALSE); if (force) - kill_io(view->pipe); - done_io(view->pipe); + io_kill(view->pipe); + io_done(view->pipe); view->pipe = NULL; } static void setup_update(struct view *view, const char *vid) { - set_nonblocking_input(TRUE); reset_view(view); string_copy_rev(view->vid, vid); view->pipe = &view->io; @@ -2820,12 +3272,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) +{ + view->dir = dir; + return format_argv(&view->argv, argv, replace); +} + +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_run(&view->io, IO_RD, dir, view->argv); } static bool @@ -2833,7 +3300,8 @@ prepare_update_file(struct view *view, const char *name) { if (view->pipe) end_update(view, TRUE); - return io_open(&view->io, name); + argv_free(view->argv); + return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name); } static bool @@ -2842,16 +3310,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 (!prepare_io(view, NULL, view->ops->argv, TRUE)) { return FALSE; + } /* Put the current ref_* value to the view title ref * member. This is needed by the blob view. Most other @@ -2860,6 +3325,10 @@ begin_update(struct view *view, bool refresh) string_copy_rev(view->ref, view->id); } + if (view->argv && view->argv[0] && + !io_run(&view->io, IO_RD, view->dir, view->argv)) + return FALSE; + setup_update(view, view->id); return TRUE; @@ -2893,7 +3362,7 @@ update_view(struct view *view) } for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) { - if (opt_iconv != ICONV_NONE) { + if (opt_iconv_in != ICONV_NONE) { ICONV_CONST char *inbuf = line; size_t inlen = strlen(line) + 1; @@ -2902,7 +3371,7 @@ update_view(struct view *view) size_t ret; - ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen); + ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen); if (ret != (size_t) -1) line = out_buffer; } @@ -2924,7 +3393,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; } } @@ -2934,7 +3403,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); } @@ -3031,6 +3501,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)); @@ -3038,6 +3509,11 @@ open_view(struct view *prev, enum request request, enum open_flags flags) display[current_view] = view; } + /* No prev signals that this is the first loaded view. */ + if (prev && view != prev) { + view->prev = prev; + } + /* Resize the view when switching between split- and full-screen, * or when switching between two different full-screen views. */ if (nviews != displayed_views() || @@ -3068,13 +3544,9 @@ open_view(struct view *prev, enum request request, enum open_flags flags) do_scroll_view(prev, lines); } - if (prev && view != prev) { - if (split) { - /* "Blur" the previous view. */ - update_view_title(prev); - } - - view->parent = prev; + if (prev && view != prev && split && view_is_displayed(prev)) { + /* "Blur" the previous view. */ + update_view_title(prev); } if (view->pipe && view->lines == 0) { @@ -3094,7 +3566,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(); @@ -3110,7 +3582,7 @@ open_mergetool(const char *file) } static void -open_editor(bool from_root, const char *file) +open_editor(const char *file) { const char *editor_argv[] = { "vi", file, NULL }; const char *editor; @@ -3126,23 +3598,25 @@ open_editor(bool from_root, const char *file) editor = "vi"; editor_argv[0] = editor; - open_external_viewer(editor_argv, from_root ? opt_cdup : NULL); + open_external_viewer(editor_argv, opt_cdup); } static void open_run_request(enum request request) { struct run_request *req = get_run_request(request); - const char *argv[ARRAY_SIZE(req->argv)] = { NULL }; + const char **argv = NULL; if (!req) { report("Unknown run request"); return; } - if (format_argv(argv, req->argv, FORMAT_ALL)) + if (format_argv(&argv, req->argv, TRUE)) open_external_viewer(argv, NULL); - free_argv(argv); + if (argv) + argv_free(argv); + free(argv); } /* @@ -3159,22 +3633,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: @@ -3198,7 +3663,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); @@ -3207,7 +3672,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); @@ -3216,7 +3681,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); @@ -3225,7 +3690,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); @@ -3252,16 +3717,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; @@ -3270,9 +3726,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); } @@ -3312,11 +3766,11 @@ view_driver(struct view *view, enum request request) break; case REQ_TOGGLE_DATE: - toggle_view_option(&opt_date, "date display"); + toggle_date(); break; case REQ_TOGGLE_AUTHOR: - toggle_view_option(&opt_author, "author display"); + toggle_author(); break; case REQ_TOGGLE_REV_GRAPH: @@ -3343,8 +3797,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); @@ -3368,13 +3821,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 */ @@ -3382,7 +3834,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; } @@ -3469,26 +3922,33 @@ get_author(const char *name) } static void -parse_timezone(time_t *time, const char *zone) +parse_timesec(struct time *time, const char *sec) +{ + time->sec = (time_t) atol(sec); +} + +static void +parse_timezone(struct time *time, const char *zone) { long tz; 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; - *time -= tz; + time->tz = tz; + time->sec -= tz; } /* Parse author lines where the name may be empty: * author 1138474660 +0100 */ static void -parse_author_line(char *ident, const char **author, time_t *time) +parse_author_line(char *ident, const char **author, struct time *time) { char *nameend = strchr(ident, '<'); char *emailend = strchr(ident, '>'); @@ -3510,7 +3970,7 @@ parse_author_line(char *ident, const char **author, time_t *time) char *secs = emailend + 2; char *zone = strchr(secs, ' '); - *time = (time_t) atol(secs); + parse_timesec(time, secs); if (zone && strlen(zone) == STRING_SIZE(" +0700")) parse_timezone(time, zone + 1); @@ -3535,7 +3995,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; @@ -3562,7 +4022,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; @@ -3575,7 +4035,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]); @@ -3605,7 +4067,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. */ @@ -3629,7 +4091,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; } @@ -3646,7 +4108,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)) @@ -3672,8 +4134,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; @@ -3688,8 +4150,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; } @@ -3722,7 +4184,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); } @@ -3769,7 +4231,8 @@ 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", "%(commit)", NULL + "--patch-with-stat", "--find-copies-harder", "-C", + "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL }; static struct view_ops diff_ops = { @@ -3787,71 +4250,120 @@ static struct view_ops diff_ops = { * Help backend */ +static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)]; + static bool -help_open(struct view *view) +help_open_keymap_title(struct view *view, enum keymap keymap) +{ + struct line *line; + + line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings", + help_keymap_hidden[keymap] ? '+' : '-', + enum_name(keymap_table[keymap])); + if (line) + line->other = keymap; + + 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; - if (view->lines > 0) - return TRUE; - - add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT); - 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, + enum_name(req_info[i]), 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, " %-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); + } - add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`", - keymap_table[req->keymap].name, key, buf); + return REQ_NONE; + default: + return pager_request(view, request, line); } - - return TRUE; } static struct view_ops help_ops = { @@ -3860,7 +4372,7 @@ static struct view_ops help_ops = { help_open, NULL, pager_draw, - pager_request, + help_request, pager_grep, pager_select, }; @@ -3931,7 +4443,7 @@ push_tree_stack_entry(const char *name, unsigned long lineno) struct tree_entry { char id[SIZEOF_REV]; mode_t mode; - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ const char *author; /* Author of the commit. */ char name[1]; }; @@ -3970,7 +4482,7 @@ tree_compare(const void *l1, const void *l2) switch (get_sort_field(tree_sort_state)) { case ORDERBY_DATE: - return sort_order(tree_sort_state, entry1->time - entry2->time); + return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time)); case ORDERBY_AUTHOR: return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author)); @@ -4007,7 +4519,7 @@ static bool tree_read_date(struct view *view, char *text, bool *read_date) { static const char *author_name; - static time_t author_time; + static struct time author_time; if (!text && *read_date) { *read_date = FALSE; @@ -4020,7 +4532,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); @@ -4028,13 +4539,11 @@ tree_read_date(struct view *view, char *text, bool *read_date) return TRUE; } - if (!run_io_rd(&io, log_file, 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; @@ -4051,8 +4560,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, '/'); @@ -4074,7 +4581,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; } @@ -4156,7 +4663,7 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno) if (opt_author && draw_author(view, entry->author)) return TRUE; - if (opt_date && draw_date(view, entry->author ? &entry->time : NULL)) + if (opt_date && draw_date(view, &entry->time)) return TRUE; } if (draw_text(view, line->type, entry->name, TRUE)) @@ -4165,17 +4672,18 @@ 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(FALSE, file); + open_editor(file); if (fd != -1) unlink(file); } @@ -4184,6 +4692,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: @@ -4199,9 +4708,9 @@ 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(TRUE, opt_file); + open_editor(opt_file); } return REQ_NONE; @@ -4250,7 +4759,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; @@ -4272,7 +4781,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 }; @@ -4295,6 +4804,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 prepare_io(view, opt_cdup, view->ops->argv, TRUE); +} + static const char *tree_argv[SIZEOF_ARG] = { "git", "ls-tree", "%(commit)", "%(directory)", NULL }; @@ -4308,6 +4843,7 @@ static struct view_ops tree_ops = { tree_request, tree_grep, tree_select, + tree_prepare, }; static bool @@ -4323,7 +4859,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); @@ -4356,23 +4892,11 @@ 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. */ const char *author; /* Author of the commit. */ - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ char filename[128]; /* Name of file. */ bool has_previous; /* Was a "previous" line detected. */ }; @@ -4386,8 +4910,21 @@ 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->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)) { + 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; } @@ -4477,19 +5014,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, 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; @@ -4551,7 +5088,7 @@ blame_read(struct view *view, char *line) commit->author = get_author(line); } else if (match_blame_header("author-time ", &line)) { - commit->time = (time_t) atol(line); + parse_timesec(&commit->time, line); } else if (match_blame_header("author-tz ", &line)) { parse_timezone(&commit->time, line); @@ -4574,7 +5111,7 @@ static bool blame_draw(struct view *view, struct line *line, unsigned int lineno) { struct blame *blame = line->data; - time_t *time = NULL; + struct time *time = NULL; const char *id = NULL, *author = NULL; char text[SIZEOF_STR]; @@ -4620,12 +5157,12 @@ setup_blame_parent_line(struct view *view, struct blame *blame) "git", "diff-tree", "-U0", blame->commit->id, "--", blame->commit->filename, NULL }; - struct io io = {}; + struct io io; int parent_lineno = -1; int blamed_lineno = -1; char *line; - if (!run_io(&io, diff_tree_argv, NULL, IO_RD)) + if (!io_run(&io, IO_RD, NULL, diff_tree_argv)) return; while ((line = io_get(&io, '\n', TRUE))) { @@ -4646,13 +5183,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) { @@ -4698,7 +5235,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; } @@ -4727,7 +5264,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 }; @@ -4766,10 +5303,12 @@ static struct view_ops blame_ops = { struct branch { const char *author; /* Author of the last commit. */ - time_t time; /* Date of the last activity. */ - struct ref *ref; /* Name and commit ID information. */ + struct time time; /* Date of the last activity. */ + const struct ref *ref; /* Name and commit ID information. */ }; +static const struct ref branch_all; + static const enum sort_field branch_sort_fields[] = { ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR }; @@ -4783,7 +5322,7 @@ branch_compare(const void *l1, const void *l2) switch (get_sort_field(branch_sort_state)) { case ORDERBY_DATE: - return sort_order(branch_sort_state, branch1->time - branch2->time); + return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time)); case ORDERBY_AUTHOR: return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author)); @@ -4800,19 +5339,21 @@ branch_draw(struct view *view, struct line *line, unsigned int lineno) struct branch *branch = line->data; enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT; - if (opt_date && draw_date(view, branch->author ? &branch->time : NULL)) + if (opt_date && draw_date(view, &branch->time)) return TRUE; if (opt_author && draw_author(view, branch->author)) return TRUE; - draw_text(view, type, branch->ref->name, TRUE); + draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE); return TRUE; } static enum request branch_request(struct view *view, enum request request, struct line *line) { + struct branch *branch = line->data; + switch (request) { case REQ_REFRESH: load_refs(); @@ -4825,9 +5366,21 @@ branch_request(struct view *view, enum request request, struct line *line) return REQ_NONE; case REQ_ENTER: - open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT); - return REQ_NONE; + { + const struct ref *ref = branch->ref; + const char *all_branches_argv[] = { + "git", "log", "--no-color", "--pretty=raw", "--parents", + "--topo-order", + ref == &branch_all ? "--all" : ref->name, NULL + }; + struct view *main_view = VIEW(REQ_VIEW_MAIN); + if (!prepare_update(main_view, all_branches_argv, NULL)) + report("Failed to load view of all branches"); + else + open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT); + return REQ_NONE; + } default: return request; } @@ -4875,7 +5428,7 @@ branch_read(struct view *view, char *line) } static bool -branch_open_visitor(void *data, struct ref *ref) +branch_open_visitor(void *data, const struct ref *ref) { struct view *view = data; struct branch *branch; @@ -4899,12 +5452,13 @@ branch_open(struct view *view) "--simplify-by-decoration", "--all", NULL }; - if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) { + if (!start_update(view, branch_log, NULL)) { report("Failed to load branch data"); return TRUE; } setup_update(view, view->id); + branch_open_visitor(view, &branch_all); foreach_ref(branch_open_visitor, view); view->p_restore = TRUE; @@ -4932,6 +5486,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 = { @@ -5016,9 +5571,9 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty { struct status *unmerged = NULL; char *buf; - struct io io = {}; + struct io io; - if (!run_io(&io, argv, NULL, IO_RD)) + if (!io_run(&io, IO_RD, opt_cdup, argv)) return FALSE; add_line_data(view, NULL, type); @@ -5077,14 +5632,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; } @@ -5099,7 +5654,7 @@ static const char *status_diff_files_argv[] = { }; static const char *status_list_other_argv[] = { - "git", "ls-files", "-z", "--others", "--exclude-standard", NULL + "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL }; static const char *status_list_no_head_argv[] = { @@ -5166,10 +5721,9 @@ status_update_onbranch(void) continue; if (!*opt_head) { - struct io io = {}; + 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/")) @@ -5196,7 +5750,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)) @@ -5302,7 +5856,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[] = { @@ -5311,7 +5865,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); } @@ -5328,7 +5882,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"; @@ -5359,7 +5913,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) { @@ -5414,13 +5968,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, IO_WR, opt_cdup, staged_argv); case LINE_STAT_UNSTAGED: - return run_io(io, others_argv, opt_cdup, IO_WR); - case LINE_STAT_UNTRACKED: - return run_io(io, others_argv, NULL, IO_WR); + return io_run(io, IO_WR, opt_cdup, others_argv); default: die("line type %d not handled in switch", type); @@ -5459,26 +6011,26 @@ status_update_write(struct io *io, struct status *status, enum line_type type) static bool status_update_file(struct status *status, enum line_type type) { - struct io io = {}; + struct io io; bool result; if (!status_update_prepare(&io, type)) return FALSE; result = status_update_write(&io, status, type); - return done_io(&io) && result; + return io_done(&io) && result; } static bool status_update_files(struct view *view, struct line *line) { char buf[sizeof(view->ref)]; - struct io io = {}; + struct io io; bool result = TRUE; 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; @@ -5503,7 +6055,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 @@ -5546,9 +6098,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, @@ -5558,12 +6109,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 (!io_run_fg(reset_argv, opt_cdup)) + return FALSE; + if (status->old.mode == 0 && status->new.mode == 0) + return TRUE; + } + + return io_run_fg(checkout_argv, opt_cdup); } + + return FALSE; } static enum request @@ -5598,14 +6162,12 @@ status_request(struct view *view, enum request request, struct line *line) return REQ_NONE; } - open_editor(status->status != '?', status->new.name); + open_editor(status->new.name); break; case REQ_VIEW_BLAME: - if (status) { - string_copy(opt_file, status->new.name); + if (status) opt_ref[0] = 0; - } return request; case REQ_ENTER: @@ -5665,13 +6227,15 @@ 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); + if (status) + string_copy(opt_file, status->new.name); } static bool @@ -5734,7 +6298,7 @@ stage_apply_chunk(struct view *view, struct line *chunk, bool revert) "git", "apply", "--whitespace=nowarn", NULL }; struct line *diff_hdr; - struct io io = {}; + struct io io; int argc = 3; diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER); @@ -5747,15 +6311,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, IO_WR, opt_cdup, apply_argv)) 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; } @@ -5866,7 +6430,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); @@ -5880,7 +6444,7 @@ stage_request(struct view *view, enum request request, struct line *line) return REQ_NONE; } - open_editor(stage_status.status != '?', stage_status.new.name); + open_editor(stage_status.new.name); break; case REQ_REFRESH: @@ -5947,7 +6511,7 @@ struct commit { char id[SIZEOF_REV]; /* SHA1 ID. */ char title[128]; /* First line of the commit message. */ const char *author; /* Author of the commit. */ - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ struct ref_list *refs; /* Repository references. */ chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */ size_t graph_size; /* The width of the graph array. */ @@ -6074,9 +6638,7 @@ draw_rev_graph(struct rev_graph *graph) struct rev_filler *filler; size_t i; - if (opt_line_graphics) - fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE]; - + fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|'; filler = &fillers[DEFAULT]; for (i = 0; i < graph->pos; i++) { @@ -6165,7 +6727,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", "%(head)", NULL + "--topo-order", "%(diff-args)", "%(rev-args)", + "--", "%(file-args)", NULL }; static bool @@ -6231,7 +6794,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; @@ -6321,7 +6884,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: @@ -6362,7 +6925,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 }; @@ -6390,159 +6953,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) -{ - 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 opt_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, -}; - -/* 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) -{ - 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) { - int c = *(unsigned char *) string; - unsigned char bytes = utf8_bytes[c]; - 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); - 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 */ @@ -6609,17 +7019,6 @@ report(const char *msg, ...) update_view_title(view); } -/* Controls when nodelay should be in effect when polling user input. */ -static void -set_nonblocking_input(bool loading) -{ - static unsigned int loading_views; - - if ((loading == FALSE && loading_views-- == 1) || - (loading == TRUE && loading_views++ == 0)) - nodelay(status_win, loading); -} - static void init_display(void) { @@ -6659,9 +7058,6 @@ init_display(void) wbkgdset(status_win, get_line_attr(LINE_STATUS)); TABSIZE = opt_tab_size; - if (opt_line_graphics) { - line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE; - } term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM"); if (term && !strcmp(term, "gnome-terminal")) { @@ -6690,6 +7086,7 @@ get_input(int prompt_position) { struct view *view; int i, key, cursor_y, cursor_x; + bool loading = FALSE; if (prompt_position) input_mode = TRUE; @@ -6701,6 +7098,8 @@ get_input(int prompt_position) use_scroll_redrawwin) redrawwin(view->win); view->has_scrolled = FALSE; + if (view->pipe) + loading = TRUE; } /* Update the cursor position. */ @@ -6717,6 +7116,7 @@ get_input(int prompt_position) /* Refresh, accept single keystroke of input */ doupdate(); + nodelay(status_win, loading); key = wgetch(status_win); /* wgetch() with nodelay() enabled returns ERR when @@ -6899,6 +7299,7 @@ static bool prompt_menu(const char *prompt, const struct menu_item *items, int * static struct ref **refs = NULL; static size_t refs_size = 0; +static struct ref *refs_head = NULL; static struct ref_list **ref_lists = NULL; static size_t ref_lists_size = 0; @@ -6927,7 +7328,7 @@ compare_refs(const void *ref1_, const void *ref2_) } static void -foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data) +foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data) { size_t i; @@ -6936,6 +7337,12 @@ foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data) break; } +static struct ref * +get_ref_head() +{ + return refs_head; +} + static struct ref_list * get_ref_list(const char *id) { @@ -7000,11 +7407,15 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) } else if (!prefixcmp(name, "refs/heads/")) { namelen -= STRING_SIZE("refs/heads/"); name += STRING_SIZE("refs/heads/"); - head = !strncmp(opt_head, name, namelen); + if (!strncmp(opt_head, name, namelen)) + return OK; } else if (!strcmp(name, "HEAD")) { - string_ncopy(opt_head_rev, id, idlen); - return OK; + head = TRUE; + if (*opt_head) { + namelen = strlen(opt_head); + name = opt_head; + } } /* If we are reloading or it's an annotated tag, replace the @@ -7046,6 +7457,8 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) ref->tracked = tracked; string_copy_rev(ref->id, id); + if (head) + refs_head = ref; return OK; } @@ -7062,24 +7475,26 @@ 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/"); memmove(opt_head, offset, strlen(offset) + 1); } + refs_head = NULL; 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. */ @@ -7198,9 +7613,9 @@ 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); + return io_run_load(config_list_argv, "=", read_repo_config_option); } static int @@ -7235,7 +7650,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); } @@ -7293,19 +7708,45 @@ 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) +{ + return argv_append(filter_args, name) ? OK : ERR; +} + +static void +filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[]) +{ + 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) + die("Failed to split arguments"); + argv_free(all_argv); + free(all_argv); +} + +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); +} + static enum request parse_options(int argc, const char *argv[]) { enum request request = REQ_VIEW_MAIN; const char *subcommand; bool seen_dashdash = FALSE; - /* XXX: This is vulnerable to the user overriding options - * required for the main view parser. */ - const char *custom_argv[SIZEOF_ARG] = { - "git", "log", "--no-color", "--pretty=raw", "--parents", - "--topo-order", NULL - }; - int i, j = 6; + const char **filter_argv = NULL; + int i; if (!isatty(STDIN_FILENO)) { io_open(&VIEW(REQ_VIEW_PAGER)->io, ""); @@ -7313,7 +7754,7 @@ parse_options(int argc, const char *argv[]) } if (argc <= 1) - return REQ_NONE; + return REQ_VIEW_MAIN; subcommand = argv[1]; if (!strcmp(subcommand, "status")) { @@ -7341,16 +7782,16 @@ parse_options(int argc, const char *argv[]) subcommand = NULL; } - if (subcommand) { - custom_argv[1] = subcommand; - j = 2; - } - for (i = 1 + !!subcommand; i < argc; i++) { const char *opt = argv[i]; - if (seen_dashdash || !strcmp(opt, "--")) { + if (seen_dashdash) { + argv_append(&opt_file_args, opt); + continue; + + } else if (!strcmp(opt, "--")) { seen_dashdash = TRUE; + continue; } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) { printf("tig version %s\n", TIG_VERSION); @@ -7359,15 +7800,18 @@ parse_options(int argc, const char *argv[]) } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) { printf("%s\n", usage); quit(0); + + } else if (!strcmp(opt, "--all")) { + argv_append(&opt_rev_args, opt); + continue; } - custom_argv[j++] = opt; - if (j >= ARRAY_SIZE(custom_argv)) + if (!argv_append(&filter_argv, opt)) die("command too long"); } - if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE)) - die("Failed to format arguments"); + if (filter_argv) + filter_options(filter_argv); return request; } @@ -7375,6 +7819,7 @@ parse_options(int argc, const char *argv[]) int main(int argc, const char *argv[]) { + const char *codeset = "UTF-8"; enum request request = parse_options(argc, argv); struct view *view; size_t i; @@ -7383,9 +7828,7 @@ main(int argc, const char *argv[]) signal(SIGPIPE, SIG_IGN); if (setlocale(LC_ALL, "")) { - char *codeset = nl_langinfo(CODESET); - - string_ncopy(opt_codeset, codeset, strlen(codeset)); + codeset = nl_langinfo(CODESET); } if (load_repo_info() == ERR) @@ -7401,12 +7844,15 @@ main(int argc, const char *argv[]) if (!opt_git_dir[0] && request != REQ_VIEW_PAGER) die("Not a git repository"); - if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) - opt_utf8 = FALSE; + if (*opt_encoding && strcmp(codeset, "UTF-8")) { + opt_iconv_in = iconv_open("UTF-8", opt_encoding); + if (opt_iconv_in == ICONV_NONE) + die("Failed to initialize character set conversion"); + } - if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) { - opt_iconv = iconv_open(opt_codeset, opt_encoding); - if (opt_iconv == ICONV_NONE) + if (codeset && strcmp(codeset, "UTF-8")) { + opt_iconv_out = iconv_open(codeset, "UTF-8"); + if (opt_iconv_out == ICONV_NONE) die("Failed to initialize character set conversion"); } @@ -7414,14 +7860,12 @@ 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(); - if (request != REQ_NONE) - open_view(NULL, request, OPEN_PREPARED); - request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE; - while (view_driver(display[current_view], request)) { int key = get_input(0); @@ -7431,6 +7875,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(":"); @@ -7457,7 +7905,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);