X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/cb7267eefe872b5f62559405ba1bda059867b776..9e21ce5c3b27e32aec6bbb9ecb41cb3a55e30838:/tig.c diff --git a/tig.c b/tig.c index f6efeb3..7b8fc7d 100644 --- a/tig.c +++ b/tig.c @@ -12,7 +12,7 @@ */ #ifndef VERSION -#define VERSION "tig-0.3" +#define VERSION "tig-0.4.git" #endif #ifndef DEBUG @@ -30,6 +30,10 @@ #include #include +#include +#include +#include + #include #if __GNUC__ >= 3 @@ -50,13 +54,15 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #define STRING_SIZE(x) (sizeof(x) - 1) +#define SIZEOF_STR 1024 /* Default string size. */ #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */ -#define SIZEOF_CMD 1024 /* Size of command buffer. */ #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */ /* This color name can be used to refer to the default term colors. */ #define COLOR_DEFAULT (-1) +#define ICONV_NONE ((iconv_t) -1) + /* 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 ") @@ -74,13 +80,13 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, "git ls-remote . 2>/dev/null" #define TIG_DIFF_CMD \ - "git show --patch-with-stat --find-copies-harder -B -C %s" + "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" #define TIG_LOG_CMD \ - "git log --cc --stat -n100 %s" + "git log --cc --stat -n100 %s 2>/dev/null" #define TIG_MAIN_CMD \ - "git log --topo-order --stat --pretty=raw %s" + "git log --topo-order --pretty=raw %s 2>/dev/null" /* XXX: Needs to be defined to the empty string. */ #define TIG_HELP_CMD "" @@ -218,11 +224,11 @@ string_enum_compare(const char *str1, const char *str2, int len) */ static size_t -sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src) +sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) { char c; -#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0) +#define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0) BUFPUT('\''); while ((c = *src++)) { @@ -360,10 +366,12 @@ static bool opt_rev_graph = TRUE; static int opt_num_interval = NUMBER_INTERVAL; static int opt_tab_size = TABSIZE; static enum request opt_request = REQ_VIEW_MAIN; -static char opt_cmd[SIZEOF_CMD] = ""; -static char opt_encoding[20] = ""; -static bool opt_utf8 = TRUE; +static char opt_cmd[SIZEOF_STR] = ""; static FILE *opt_pipe = NULL; +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; enum option_type { OPT_NONE, @@ -679,7 +687,7 @@ static struct keybinding default_keybindings[] = { { 'v', REQ_SHOW_VERSION }, { 'r', REQ_SCREEN_REDRAW }, { 'n', REQ_TOGGLE_LINENO }, - { 'g', REQ_TOGGLE_REV_GRAPH}, + { 'g', REQ_TOGGLE_REV_GRAPH }, { ':', REQ_PROMPT }, /* wgetch() with nodelay() enabled returns ERR when there's no input. */ @@ -770,6 +778,7 @@ static struct key key_table[] = { { "Down", KEY_DOWN }, { "Insert", KEY_IC }, { "Delete", KEY_DC }, + { "Hash", '#' }, { "Home", KEY_HOME }, { "End", KEY_END }, { "PageUp", KEY_PPAGE }, @@ -900,12 +909,8 @@ option_color_command(int argc, char *argv[]) return ERR; } - if (set_color(&info->fg, argv[1]) == ERR) { - config_msg = "Unknown color"; - return ERR; - } - - if (set_color(&info->bg, argv[2]) == ERR) { + if (set_color(&info->fg, argv[1]) == ERR || + set_color(&info->bg, argv[2]) == ERR) { config_msg = "Unknown color"; return ERR; } @@ -950,10 +955,25 @@ option_set_command(int argc, char *argv[]) } if (!strcmp(argv[0], "commit-encoding")) { - string_copy(opt_encoding, argv[2]); - return OK; + char *arg = argv[2]; + int delimiter = *arg; + int i; + + switch (delimiter) { + case '"': + case '\'': + for (arg++, i = 0; arg[i]; i++) + if (arg[i] == delimiter) { + arg[i] = 0; + break; + } + default: + string_copy(opt_encoding, arg); + return OK; + } } + config_msg = "Unknown variable name"; return ERR; } @@ -1021,33 +1041,42 @@ set_option(char *opt, char *value) if (!strcmp(opt, "bind")) return option_bind_command(argc, argv); + config_msg = "Unknown option command"; return ERR; } static int read_option(char *opt, int optlen, char *value, int valuelen) { + int status = OK; + config_lineno++; config_msg = "Internal error"; - optlen = strcspn(opt, "#;"); - if (optlen == 0) { - /* The whole line is a commend or empty. */ + /* Check for comment markers, since read_properties() will + * only ensure opt and value are split at first " \t". */ + optlen = strcspn(opt, "#"); + if (optlen == 0) return OK; - } else if (opt[optlen] != 0) { - /* Part of the option name is a comment, so the value part - * should be ignored. */ - valuelen = 0; - opt[optlen] = value[valuelen] = 0; - } else { - /* Else look for comment endings in the value. */ - valuelen = strcspn(value, "#;"); - value[valuelen] = 0; + if (opt[optlen] != 0) { + config_msg = "No option value"; + status = ERR; + + } else { + /* Look for comment endings in the value. */ + int len = strcspn(value, "#"); + + if (len < valuelen) { + valuelen = len; + value[valuelen] = 0; + } + + status = set_option(opt, value); } - if (set_option(opt, value) == ERR) { - fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n", + if (status == ERR) { + fprintf(stderr, "Error on line %d, near '%.*s': %s\n", config_lineno, optlen, opt, config_msg); config_errors = TRUE; } @@ -1060,7 +1089,7 @@ static int load_options(void) { char *home = getenv("HOME"); - char buf[1024]; + char buf[SIZEOF_STR]; FILE *file; config_lineno = 0; @@ -1112,7 +1141,7 @@ struct view { enum keymap keymap; /* What keymap does this view have */ - char cmd[SIZEOF_CMD]; /* Command buffer */ + char cmd[SIZEOF_STR]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */ @@ -1595,7 +1624,8 @@ realloc_lines(struct view *view, size_t line_size) static bool update_view(struct view *view) { - char buffer[BUFSIZ]; + char in_buffer[BUFSIZ]; + char out_buffer[BUFSIZ * 2]; char *line; /* The number of lines to read. If too low it will cause too much * redrawing (and possible flickering), if too high responsiveness @@ -1613,12 +1643,28 @@ update_view(struct view *view) if (!realloc_lines(view, view->lines + lines)) goto alloc_error; - while ((line = fgets(buffer, sizeof(buffer), view->pipe))) { - int linelen = strlen(line); + while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) { + size_t linelen = strlen(line); if (linelen) line[linelen - 1] = 0; + if (opt_iconv != ICONV_NONE) { + char *inbuf = line; + size_t inlen = linelen; + + char *outbuf = out_buffer; + size_t outlen = sizeof(out_buffer); + + size_t ret; + + ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + line = out_buffer; + linelen = strlen(out_buffer); + } + } + if (!view->ops->read(view, line)) goto alloc_error; @@ -2021,20 +2067,52 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno) return TRUE; } +static bool +add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep) +{ + char refbuf[SIZEOF_STR]; + char *ref = NULL; + FILE *pipe; + + if (!string_format(refbuf, "git describe %s", commit_id)) + return TRUE; + + pipe = popen(refbuf, "r"); + if (!pipe) + return TRUE; + + if ((ref = fgets(refbuf, sizeof(refbuf), pipe))) + ref = chomp_string(ref); + pclose(pipe); + + if (!ref || !*ref) + return TRUE; + + /* This is the only fatal call, since it can "corrupt" the buffer. */ + if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref)) + return FALSE; + + return TRUE; +} + static void add_pager_refs(struct view *view, struct line *line) { - char buf[1024]; - char *data = line->data; + char buf[SIZEOF_STR]; + char *commit_id = line->data + STRING_SIZE("commit "); struct ref **refs; int bufpos = 0, refpos = 0; const char *sep = "Refs: "; + bool is_tag = FALSE; assert(line->type == LINE_COMMIT); - refs = get_refs(data + STRING_SIZE("commit ")); - if (!refs) + refs = get_refs(commit_id); + if (!refs) { + if (view == VIEW(REQ_VIEW_DIFF)) + goto try_add_describe_ref; return; + } do { struct ref *ref = refs[refpos]; @@ -2043,8 +2121,16 @@ add_pager_refs(struct view *view, struct line *line) if (!string_format_from(buf, &bufpos, fmt, sep, ref->name)) return; sep = ", "; + if (ref->tag) + is_tag = TRUE; } while (refs[refpos++]->next); + if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) { +try_add_describe_ref: + if (!add_describe_ref(buf, &bufpos, commit_id, sep)) + return; + } + if (!realloc_lines(view, view->line_size + 1)) return; @@ -2265,10 +2351,22 @@ main_read(struct view *view, char *line) break; if (end) { + char *email = end + 1; + for (; end > ident && isspace(end[-1]); end--) ; + + if (end == ident && *email) { + ident = email; + end = strchr(ident, '>'); + for (; end > ident && isspace(end[-1]); end--) ; + } *end = 0; } + /* End is NULL or ident meaning there's no author. */ + if (end <= ident) + ident = "Unknown"; + string_copy(commit->author, ident); /* Parse epoch and timezone */ @@ -2562,6 +2660,8 @@ init_display(void) /* Leave stdin and stdout alone when acting as a pager. */ FILE *io = fopen("/dev/tty", "r+"); + if (!io) + die("Failed to open /dev/tty"); cursed = !!newterm(NULL, io, io); } @@ -2586,6 +2686,65 @@ init_display(void) wbkgdset(status_win, get_line_attr(LINE_STATUS)); } +static char * +read_prompt(const char *prompt) +{ + enum { READING, STOP, CANCEL } status = READING; + static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")]; + int pos = 0; + + while (status == READING) { + struct view *view; + int i, key; + + foreach_view (view, i) + update_view(view); + + report("%s%.*s", prompt, pos, buf); + /* Refresh, accept single keystroke of input */ + key = wgetch(status_win); + switch (key) { + case KEY_RETURN: + case KEY_ENTER: + case '\n': + status = pos ? STOP : CANCEL; + break; + + case KEY_BACKSPACE: + if (pos > 0) + pos--; + else + status = CANCEL; + break; + + case KEY_ESC: + status = CANCEL; + break; + + case ERR: + break; + + default: + if (pos >= sizeof(buf)) { + report("Input string too long"); + return NULL; + } + + if (isprint(key)) + buf[pos++] = (char) key; + } + } + + if (status == CANCEL) { + /* Clear the status window */ + report(""); + return NULL; + } + + buf[pos++] = 0; + + return buf; +} /* * Repository references @@ -2794,6 +2953,10 @@ main(int argc, char *argv[]) signal(SIGINT, quit); + if (setlocale(LC_ALL, "")) { + string_copy(opt_codeset, nl_langinfo(CODESET)); + } + if (load_options() == ERR) die("Failed to load user config."); @@ -2805,6 +2968,12 @@ main(int argc, char *argv[]) if (!parse_options(argc, argv)) return 0; + if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) { + opt_iconv = iconv_open(opt_codeset, opt_encoding); + if (opt_iconv == (iconv_t) -1) + die("Failed to initialize character set conversion"); + } + if (load_refs() == ERR) die("Failed to load refs."); @@ -2835,25 +3004,21 @@ main(int argc, char *argv[]) * status_win restricted. */ switch (request) { case REQ_PROMPT: - report(":"); - /* Temporarily switch to line-oriented and echoed - * input. */ - nocbreak(); - echo(); - - if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) { - memcpy(opt_cmd, "git ", 4); - opt_request = REQ_VIEW_PAGER; - } else { - report("Prompt interrupted by loading view, " - "press 'z' to stop loading views"); - request = REQ_SCREEN_UPDATE; + { + char *cmd = read_prompt(":"); + + if (cmd && string_format(opt_cmd, "git %s", cmd)) { + if (strncmp(cmd, "show", 4) && isspace(cmd[4])) { + opt_request = REQ_VIEW_DIFF; + } else { + opt_request = REQ_VIEW_PAGER; + } + break; } - noecho(); - cbreak(); + request = REQ_SCREEN_UPDATE; break; - + } case REQ_SCREEN_RESIZE: { int height, width;