1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 #define ABS(x) ((x) >= 0 ? (x) : -(x))
72 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
75 #define STRING_SIZE(x) (sizeof(x) - 1)
77 #define SIZEOF_STR 1024 /* Default string size. */
78 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
79 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
83 #define REVGRAPH_INIT 'I'
84 #define REVGRAPH_MERGE 'M'
85 #define REVGRAPH_BRANCH '+'
86 #define REVGRAPH_COMMIT '*'
87 #define REVGRAPH_BOUND '^'
89 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
91 /* This color name can be used to refer to the default term colors. */
92 #define COLOR_DEFAULT (-1)
94 #define ICONV_NONE ((iconv_t) -1)
96 #define ICONV_CONST /* nothing */
99 /* The format and size of the date column in the main view. */
100 #define DATE_FORMAT "%Y-%m-%d %H:%M"
101 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
103 #define AUTHOR_COLS 20
106 /* The default interval between line numbers. */
107 #define NUMBER_INTERVAL 5
111 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
113 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define GIT_CONFIG "git config"
119 #define TIG_LS_REMOTE \
120 "git ls-remote . 2>/dev/null"
122 #define TIG_DIFF_CMD \
123 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
125 #define TIG_LOG_CMD \
126 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
128 #define TIG_MAIN_CMD \
129 "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
131 #define TIG_TREE_CMD \
134 #define TIG_BLOB_CMD \
135 "git cat-file blob %s"
137 /* XXX: Needs to be defined to the empty string. */
138 #define TIG_HELP_CMD ""
139 #define TIG_PAGER_CMD ""
140 #define TIG_STATUS_CMD ""
141 #define TIG_STAGE_CMD ""
142 #define TIG_BLAME_CMD ""
144 /* Some ascii-shorthands fitted into the ncurses namespace. */
146 #define KEY_RETURN '\r'
151 char *name; /* Ref name; tag or head names are shortened. */
152 char id[SIZEOF_REV]; /* Commit SHA1 ID */
153 unsigned int head:1; /* Is it the current HEAD? */
154 unsigned int tag:1; /* Is it a tag? */
155 unsigned int ltag:1; /* If so, is the tag local? */
156 unsigned int remote:1; /* Is it a remote ref? */
157 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
158 unsigned int next:1; /* For ref lists: are there more refs? */
161 static struct ref **get_refs(char *id);
170 set_from_int_map(struct int_map *map, size_t map_size,
171 int *value, const char *name, int namelen)
176 for (i = 0; i < map_size; i++)
177 if (namelen == map[i].namelen &&
178 !strncasecmp(name, map[i].name, namelen)) {
179 *value = map[i].value;
192 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
194 if (srclen > dstlen - 1)
197 strncpy(dst, src, srclen);
201 /* Shorthands for safely copying into a fixed buffer. */
203 #define string_copy(dst, src) \
204 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
206 #define string_ncopy(dst, src, srclen) \
207 string_ncopy_do(dst, sizeof(dst), src, srclen)
209 #define string_copy_rev(dst, src) \
210 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
212 #define string_add(dst, from, src) \
213 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
216 chomp_string(char *name)
220 while (isspace(*name))
223 namelen = strlen(name) - 1;
224 while (namelen > 0 && isspace(name[namelen]))
231 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
234 size_t pos = bufpos ? *bufpos : 0;
237 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
243 return pos >= bufsize ? FALSE : TRUE;
246 #define string_format(buf, fmt, args...) \
247 string_nformat(buf, sizeof(buf), NULL, fmt, args)
249 #define string_format_from(buf, from, fmt, args...) \
250 string_nformat(buf, sizeof(buf), from, fmt, args)
253 string_enum_compare(const char *str1, const char *str2, int len)
257 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
259 /* Diff-Header == DIFF_HEADER */
260 for (i = 0; i < len; i++) {
261 if (toupper(str1[i]) == toupper(str2[i]))
264 if (string_enum_sep(str1[i]) &&
265 string_enum_sep(str2[i]))
268 return str1[i] - str2[i];
276 * NOTE: The following is a slightly modified copy of the git project's shell
277 * quoting routines found in the quote.c file.
279 * Help to copy the thing properly quoted for the shell safety. any single
280 * quote is replaced with '\'', any exclamation point is replaced with '\!',
281 * and the whole thing is enclosed in a
284 * original sq_quote result
285 * name ==> name ==> 'name'
286 * a b ==> a b ==> 'a b'
287 * a'b ==> a'\''b ==> 'a'\''b'
288 * a!b ==> a'\!'b ==> 'a'\!'b'
292 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
296 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
299 while ((c = *src++)) {
300 if (c == '\'' || c == '!') {
311 if (bufsize < SIZEOF_STR)
323 /* XXX: Keep the view request first and in sync with views[]. */ \
324 REQ_GROUP("View switching") \
325 REQ_(VIEW_MAIN, "Show main view"), \
326 REQ_(VIEW_DIFF, "Show diff view"), \
327 REQ_(VIEW_LOG, "Show log view"), \
328 REQ_(VIEW_TREE, "Show tree view"), \
329 REQ_(VIEW_BLOB, "Show blob view"), \
330 REQ_(VIEW_BLAME, "Show blame view"), \
331 REQ_(VIEW_HELP, "Show help page"), \
332 REQ_(VIEW_PAGER, "Show pager view"), \
333 REQ_(VIEW_STATUS, "Show status view"), \
334 REQ_(VIEW_STAGE, "Show stage view"), \
336 REQ_GROUP("View manipulation") \
337 REQ_(ENTER, "Enter current line and scroll"), \
338 REQ_(NEXT, "Move to next"), \
339 REQ_(PREVIOUS, "Move to previous"), \
340 REQ_(VIEW_NEXT, "Move focus to next view"), \
341 REQ_(REFRESH, "Reload and refresh"), \
342 REQ_(MAXIMIZE, "Maximize the current view"), \
343 REQ_(VIEW_CLOSE, "Close the current view"), \
344 REQ_(QUIT, "Close all views and quit"), \
346 REQ_GROUP("Cursor navigation") \
347 REQ_(MOVE_UP, "Move cursor one line up"), \
348 REQ_(MOVE_DOWN, "Move cursor one line down"), \
349 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
350 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
351 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
352 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
354 REQ_GROUP("Scrolling") \
355 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
356 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
357 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
358 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
360 REQ_GROUP("Searching") \
361 REQ_(SEARCH, "Search the view"), \
362 REQ_(SEARCH_BACK, "Search backwards in the view"), \
363 REQ_(FIND_NEXT, "Find next search match"), \
364 REQ_(FIND_PREV, "Find previous search match"), \
367 REQ_(PROMPT, "Bring up the prompt"), \
368 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
369 REQ_(SCREEN_RESIZE, "Resize the screen"), \
370 REQ_(SHOW_VERSION, "Show version information"), \
371 REQ_(STOP_LOADING, "Stop all loading views"), \
372 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
373 REQ_(TOGGLE_DATE, "Toggle date display"), \
374 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
375 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
376 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
377 REQ_(STATUS_UPDATE, "Update file status"), \
378 REQ_(STATUS_MERGE, "Merge file using external tool"), \
379 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
380 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
381 REQ_(EDIT, "Open in editor"), \
382 REQ_(NONE, "Do nothing")
385 /* User action requests. */
387 #define REQ_GROUP(help)
388 #define REQ_(req, help) REQ_##req
390 /* Offset all requests to avoid conflicts with ncurses getch values. */
391 REQ_OFFSET = KEY_MAX + 1,
398 struct request_info {
399 enum request request;
405 static struct request_info req_info[] = {
406 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
407 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
414 get_request(const char *name)
416 int namelen = strlen(name);
419 for (i = 0; i < ARRAY_SIZE(req_info); i++)
420 if (req_info[i].namelen == namelen &&
421 !string_enum_compare(req_info[i].name, name, namelen))
422 return req_info[i].request;
432 static const char usage[] =
433 "tig " TIG_VERSION " (" __DATE__ ")\n"
435 "Usage: tig [options] [revs] [--] [paths]\n"
436 " or: tig show [options] [revs] [--] [paths]\n"
437 " or: tig blame [rev] path\n"
439 " or: tig < [git command output]\n"
442 " -v, --version Show version and exit\n"
443 " -h, --help Show help message and exit";
445 /* Option and state variables. */
446 static bool opt_date = TRUE;
447 static bool opt_author = TRUE;
448 static bool opt_line_number = FALSE;
449 static bool opt_line_graphics = TRUE;
450 static bool opt_rev_graph = FALSE;
451 static bool opt_show_refs = TRUE;
452 static int opt_num_interval = NUMBER_INTERVAL;
453 static int opt_tab_size = TAB_SIZE;
454 static int opt_author_cols = AUTHOR_COLS-1;
455 static enum request opt_request = REQ_VIEW_MAIN;
456 static char opt_cmd[SIZEOF_STR] = "";
457 static char opt_path[SIZEOF_STR] = "";
458 static char opt_file[SIZEOF_STR] = "";
459 static char opt_ref[SIZEOF_REF] = "";
460 static char opt_head[SIZEOF_REF] = "";
461 static char opt_remote[SIZEOF_REF] = "";
462 static bool opt_no_head = TRUE;
463 static FILE *opt_pipe = NULL;
464 static char opt_encoding[20] = "UTF-8";
465 static bool opt_utf8 = TRUE;
466 static char opt_codeset[20] = "UTF-8";
467 static iconv_t opt_iconv = ICONV_NONE;
468 static char opt_search[SIZEOF_STR] = "";
469 static char opt_cdup[SIZEOF_STR] = "";
470 static char opt_git_dir[SIZEOF_STR] = "";
471 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
472 static char opt_editor[SIZEOF_STR] = "";
475 parse_options(int argc, char *argv[])
479 bool seen_dashdash = FALSE;
482 if (!isatty(STDIN_FILENO)) {
483 opt_request = REQ_VIEW_PAGER;
491 subcommand = argv[1];
492 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
493 opt_request = REQ_VIEW_STATUS;
494 if (!strcmp(subcommand, "-S"))
495 warn("`-S' has been deprecated; use `tig status' instead");
497 warn("ignoring arguments after `%s'", subcommand);
500 } else if (!strcmp(subcommand, "blame")) {
501 opt_request = REQ_VIEW_BLAME;
502 if (argc <= 2 || argc > 4)
503 die("invalid number of options to blame\n\n%s", usage);
507 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
511 string_ncopy(opt_file, argv[i], strlen(argv[i]));
514 } else if (!strcmp(subcommand, "show")) {
515 opt_request = REQ_VIEW_DIFF;
517 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
518 opt_request = subcommand[0] == 'l'
519 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
520 warn("`tig %s' has been deprecated", subcommand);
527 /* XXX: This is vulnerable to the user overriding
528 * options required for the main view parser. */
529 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
531 string_format(opt_cmd, "git %s", subcommand);
533 buf_size = strlen(opt_cmd);
535 for (i = 1 + !!subcommand; i < argc; i++) {
538 if (seen_dashdash || !strcmp(opt, "--")) {
539 seen_dashdash = TRUE;
541 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
542 printf("tig version %s\n", TIG_VERSION);
545 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
546 printf("%s\n", usage);
550 opt_cmd[buf_size++] = ' ';
551 buf_size = sq_quote(opt_cmd, buf_size, opt);
552 if (buf_size >= sizeof(opt_cmd))
553 die("command too long");
556 opt_cmd[buf_size] = 0;
563 * Line-oriented content detection.
567 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
569 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
570 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
571 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
572 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
573 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
574 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
580 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
581 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
582 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
583 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
584 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
587 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
588 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
589 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
590 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
591 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
592 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
593 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
595 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
596 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
597 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
598 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
599 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
600 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
601 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
602 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
603 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
604 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
605 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
606 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
607 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
609 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
610 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
611 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
612 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
613 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
614 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
615 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
616 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
617 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
618 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
619 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
620 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
623 #define LINE(type, line, fg, bg, attr) \
631 const char *name; /* Option name. */
632 int namelen; /* Size of option name. */
633 const char *line; /* The start of line to match. */
634 int linelen; /* Size of string to match. */
635 int fg, bg, attr; /* Color and text attributes for the lines. */
638 static struct line_info line_info[] = {
639 #define LINE(type, line, fg, bg, attr) \
640 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
645 static enum line_type
646 get_line_type(char *line)
648 int linelen = strlen(line);
651 for (type = 0; type < ARRAY_SIZE(line_info); type++)
652 /* Case insensitive search matches Signed-off-by lines better. */
653 if (linelen >= line_info[type].linelen &&
654 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
661 get_line_attr(enum line_type type)
663 assert(type < ARRAY_SIZE(line_info));
664 return COLOR_PAIR(type) | line_info[type].attr;
667 static struct line_info *
668 get_line_info(char *name)
670 size_t namelen = strlen(name);
673 for (type = 0; type < ARRAY_SIZE(line_info); type++)
674 if (namelen == line_info[type].namelen &&
675 !string_enum_compare(line_info[type].name, name, namelen))
676 return &line_info[type];
684 int default_bg = line_info[LINE_DEFAULT].bg;
685 int default_fg = line_info[LINE_DEFAULT].fg;
690 if (assume_default_colors(default_fg, default_bg) == ERR) {
691 default_bg = COLOR_BLACK;
692 default_fg = COLOR_WHITE;
695 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
696 struct line_info *info = &line_info[type];
697 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
698 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
700 init_pair(type, fg, bg);
708 unsigned int selected:1;
709 unsigned int dirty:1;
711 void *data; /* User data */
721 enum request request;
722 struct keybinding *next;
725 static struct keybinding default_keybindings[] = {
727 { 'm', REQ_VIEW_MAIN },
728 { 'd', REQ_VIEW_DIFF },
729 { 'l', REQ_VIEW_LOG },
730 { 't', REQ_VIEW_TREE },
731 { 'f', REQ_VIEW_BLOB },
732 { 'B', REQ_VIEW_BLAME },
733 { 'p', REQ_VIEW_PAGER },
734 { 'h', REQ_VIEW_HELP },
735 { 'S', REQ_VIEW_STATUS },
736 { 'c', REQ_VIEW_STAGE },
738 /* View manipulation */
739 { 'q', REQ_VIEW_CLOSE },
740 { KEY_TAB, REQ_VIEW_NEXT },
741 { KEY_RETURN, REQ_ENTER },
742 { KEY_UP, REQ_PREVIOUS },
743 { KEY_DOWN, REQ_NEXT },
744 { 'R', REQ_REFRESH },
745 { KEY_F(5), REQ_REFRESH },
746 { 'O', REQ_MAXIMIZE },
748 /* Cursor navigation */
749 { 'k', REQ_MOVE_UP },
750 { 'j', REQ_MOVE_DOWN },
751 { KEY_HOME, REQ_MOVE_FIRST_LINE },
752 { KEY_END, REQ_MOVE_LAST_LINE },
753 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
754 { ' ', REQ_MOVE_PAGE_DOWN },
755 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
756 { 'b', REQ_MOVE_PAGE_UP },
757 { '-', REQ_MOVE_PAGE_UP },
760 { KEY_IC, REQ_SCROLL_LINE_UP },
761 { KEY_DC, REQ_SCROLL_LINE_DOWN },
762 { 'w', REQ_SCROLL_PAGE_UP },
763 { 's', REQ_SCROLL_PAGE_DOWN },
767 { '?', REQ_SEARCH_BACK },
768 { 'n', REQ_FIND_NEXT },
769 { 'N', REQ_FIND_PREV },
773 { 'z', REQ_STOP_LOADING },
774 { 'v', REQ_SHOW_VERSION },
775 { 'r', REQ_SCREEN_REDRAW },
776 { '.', REQ_TOGGLE_LINENO },
777 { 'D', REQ_TOGGLE_DATE },
778 { 'A', REQ_TOGGLE_AUTHOR },
779 { 'g', REQ_TOGGLE_REV_GRAPH },
780 { 'F', REQ_TOGGLE_REFS },
782 { 'u', REQ_STATUS_UPDATE },
783 { 'M', REQ_STATUS_MERGE },
784 { '@', REQ_STAGE_NEXT },
785 { ',', REQ_TREE_PARENT },
788 /* Using the ncurses SIGWINCH handler. */
789 { KEY_RESIZE, REQ_SCREEN_RESIZE },
792 #define KEYMAP_INFO \
806 #define KEYMAP_(name) KEYMAP_##name
811 static struct int_map keymap_table[] = {
812 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
817 #define set_keymap(map, name) \
818 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
820 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
823 add_keybinding(enum keymap keymap, enum request request, int key)
825 struct keybinding *keybinding;
827 keybinding = calloc(1, sizeof(*keybinding));
829 die("Failed to allocate keybinding");
831 keybinding->alias = key;
832 keybinding->request = request;
833 keybinding->next = keybindings[keymap];
834 keybindings[keymap] = keybinding;
837 /* Looks for a key binding first in the given map, then in the generic map, and
838 * lastly in the default keybindings. */
840 get_keybinding(enum keymap keymap, int key)
842 struct keybinding *kbd;
845 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
846 if (kbd->alias == key)
849 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
850 if (kbd->alias == key)
853 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
854 if (default_keybindings[i].alias == key)
855 return default_keybindings[i].request;
857 return (enum request) key;
866 static struct key key_table[] = {
867 { "Enter", KEY_RETURN },
869 { "Backspace", KEY_BACKSPACE },
871 { "Escape", KEY_ESC },
872 { "Left", KEY_LEFT },
873 { "Right", KEY_RIGHT },
875 { "Down", KEY_DOWN },
876 { "Insert", KEY_IC },
877 { "Delete", KEY_DC },
879 { "Home", KEY_HOME },
881 { "PageUp", KEY_PPAGE },
882 { "PageDown", KEY_NPAGE },
892 { "F10", KEY_F(10) },
893 { "F11", KEY_F(11) },
894 { "F12", KEY_F(12) },
898 get_key_value(const char *name)
902 for (i = 0; i < ARRAY_SIZE(key_table); i++)
903 if (!strcasecmp(key_table[i].name, name))
904 return key_table[i].value;
906 if (strlen(name) == 1 && isprint(*name))
913 get_key_name(int key_value)
915 static char key_char[] = "'X'";
919 for (key = 0; key < ARRAY_SIZE(key_table); key++)
920 if (key_table[key].value == key_value)
921 seq = key_table[key].name;
925 isprint(key_value)) {
926 key_char[1] = (char) key_value;
930 return seq ? seq : "'?'";
934 get_key(enum request request)
936 static char buf[BUFSIZ];
943 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
944 struct keybinding *keybinding = &default_keybindings[i];
946 if (keybinding->request != request)
949 if (!string_format_from(buf, &pos, "%s%s", sep,
950 get_key_name(keybinding->alias)))
951 return "Too many keybindings!";
961 char cmd[SIZEOF_STR];
964 static struct run_request *run_request;
965 static size_t run_requests;
968 add_run_request(enum keymap keymap, int key, int argc, char **argv)
970 struct run_request *req;
971 char cmd[SIZEOF_STR];
974 for (bufpos = 0; argc > 0; argc--, argv++)
975 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
978 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
983 req = &run_request[run_requests++];
984 string_copy(req->cmd, cmd);
985 req->keymap = keymap;
988 return REQ_NONE + run_requests;
991 static struct run_request *
992 get_run_request(enum request request)
994 if (request <= REQ_NONE)
996 return &run_request[request - REQ_NONE - 1];
1000 add_builtin_run_requests(void)
1007 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1008 { KEYMAP_GENERIC, 'G', { "git gc" } },
1012 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1015 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1016 if (req != REQ_NONE)
1017 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1022 * User config file handling.
1025 static struct int_map color_map[] = {
1026 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1038 #define set_color(color, name) \
1039 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1041 static struct int_map attr_map[] = {
1042 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1049 ATTR_MAP(UNDERLINE),
1052 #define set_attribute(attr, name) \
1053 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1055 static int config_lineno;
1056 static bool config_errors;
1057 static char *config_msg;
1059 /* Wants: object fgcolor bgcolor [attr] */
1061 option_color_command(int argc, char *argv[])
1063 struct line_info *info;
1065 if (argc != 3 && argc != 4) {
1066 config_msg = "Wrong number of arguments given to color command";
1070 info = get_line_info(argv[0]);
1072 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1073 info = get_line_info("delimiter");
1075 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1076 info = get_line_info("date");
1079 config_msg = "Unknown color name";
1084 if (set_color(&info->fg, argv[1]) == ERR ||
1085 set_color(&info->bg, argv[2]) == ERR) {
1086 config_msg = "Unknown color";
1090 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1091 config_msg = "Unknown attribute";
1098 static bool parse_bool(const char *s)
1100 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1101 !strcmp(s, "yes")) ? TRUE : FALSE;
1105 parse_int(const char *s, int default_value, int min, int max)
1107 int value = atoi(s);
1109 return (value < min || value > max) ? default_value : value;
1112 /* Wants: name = value */
1114 option_set_command(int argc, char *argv[])
1117 config_msg = "Wrong number of arguments given to set command";
1121 if (strcmp(argv[1], "=")) {
1122 config_msg = "No value assigned";
1126 if (!strcmp(argv[0], "show-author")) {
1127 opt_author = parse_bool(argv[2]);
1131 if (!strcmp(argv[0], "show-date")) {
1132 opt_date = parse_bool(argv[2]);
1136 if (!strcmp(argv[0], "show-rev-graph")) {
1137 opt_rev_graph = parse_bool(argv[2]);
1141 if (!strcmp(argv[0], "show-refs")) {
1142 opt_show_refs = parse_bool(argv[2]);
1146 if (!strcmp(argv[0], "show-line-numbers")) {
1147 opt_line_number = parse_bool(argv[2]);
1151 if (!strcmp(argv[0], "line-graphics")) {
1152 opt_line_graphics = parse_bool(argv[2]);
1156 if (!strcmp(argv[0], "line-number-interval")) {
1157 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1161 if (!strcmp(argv[0], "author-width")) {
1162 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1166 if (!strcmp(argv[0], "tab-size")) {
1167 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1171 if (!strcmp(argv[0], "commit-encoding")) {
1172 char *arg = argv[2];
1173 int delimiter = *arg;
1176 switch (delimiter) {
1179 for (arg++, i = 0; arg[i]; i++)
1180 if (arg[i] == delimiter) {
1185 string_ncopy(opt_encoding, arg, strlen(arg));
1190 config_msg = "Unknown variable name";
1194 /* Wants: mode request key */
1196 option_bind_command(int argc, char *argv[])
1198 enum request request;
1203 config_msg = "Wrong number of arguments given to bind command";
1207 if (set_keymap(&keymap, argv[0]) == ERR) {
1208 config_msg = "Unknown key map";
1212 key = get_key_value(argv[1]);
1214 config_msg = "Unknown key";
1218 request = get_request(argv[2]);
1219 if (request == REQ_NONE) {
1220 const char *obsolete[] = { "cherry-pick" };
1221 size_t namelen = strlen(argv[2]);
1224 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1225 if (namelen == strlen(obsolete[i]) &&
1226 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1227 config_msg = "Obsolete request name";
1232 if (request == REQ_NONE && *argv[2]++ == '!')
1233 request = add_run_request(keymap, key, argc - 2, argv + 2);
1234 if (request == REQ_NONE) {
1235 config_msg = "Unknown request name";
1239 add_keybinding(keymap, request, key);
1245 set_option(char *opt, char *value)
1252 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1253 argv[argc++] = value;
1256 /* Nothing more to tokenize or last available token. */
1257 if (!*value || argc >= ARRAY_SIZE(argv))
1261 while (isspace(*value))
1265 if (!strcmp(opt, "color"))
1266 return option_color_command(argc, argv);
1268 if (!strcmp(opt, "set"))
1269 return option_set_command(argc, argv);
1271 if (!strcmp(opt, "bind"))
1272 return option_bind_command(argc, argv);
1274 config_msg = "Unknown option command";
1279 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1284 config_msg = "Internal error";
1286 /* Check for comment markers, since read_properties() will
1287 * only ensure opt and value are split at first " \t". */
1288 optlen = strcspn(opt, "#");
1292 if (opt[optlen] != 0) {
1293 config_msg = "No option value";
1297 /* Look for comment endings in the value. */
1298 size_t len = strcspn(value, "#");
1300 if (len < valuelen) {
1302 value[valuelen] = 0;
1305 status = set_option(opt, value);
1308 if (status == ERR) {
1309 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1310 config_lineno, (int) optlen, opt, config_msg);
1311 config_errors = TRUE;
1314 /* Always keep going if errors are encountered. */
1319 load_option_file(const char *path)
1323 /* It's ok that the file doesn't exist. */
1324 file = fopen(path, "r");
1329 config_errors = FALSE;
1331 if (read_properties(file, " \t", read_option) == ERR ||
1332 config_errors == TRUE)
1333 fprintf(stderr, "Errors while loading %s.\n", path);
1339 char *home = getenv("HOME");
1340 char *tigrc_user = getenv("TIGRC_USER");
1341 char *tigrc_system = getenv("TIGRC_SYSTEM");
1342 char buf[SIZEOF_STR];
1344 add_builtin_run_requests();
1346 if (!tigrc_system) {
1347 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1351 load_option_file(tigrc_system);
1354 if (!home || !string_format(buf, "%s/.tigrc", home))
1358 load_option_file(tigrc_user);
1371 /* The display array of active views and the index of the current view. */
1372 static struct view *display[2];
1373 static unsigned int current_view;
1375 /* Reading from the prompt? */
1376 static bool input_mode = FALSE;
1378 #define foreach_displayed_view(view, i) \
1379 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1381 #define displayed_views() (display[1] != NULL ? 2 : 1)
1383 /* Current head and commit ID */
1384 static char ref_blob[SIZEOF_REF] = "";
1385 static char ref_commit[SIZEOF_REF] = "HEAD";
1386 static char ref_head[SIZEOF_REF] = "HEAD";
1389 const char *name; /* View name */
1390 const char *cmd_fmt; /* Default command line format */
1391 const char *cmd_env; /* Command line set via environment */
1392 const char *id; /* Points to either of ref_{head,commit,blob} */
1394 struct view_ops *ops; /* View operations */
1396 enum keymap keymap; /* What keymap does this view have */
1397 bool git_dir; /* Whether the view requires a git directory. */
1399 char cmd[SIZEOF_STR]; /* Command buffer */
1400 char ref[SIZEOF_REF]; /* Hovered commit reference */
1401 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1403 int height, width; /* The width and height of the main window */
1404 WINDOW *win; /* The main window */
1405 WINDOW *title; /* The title window living below the main window */
1408 unsigned long offset; /* Offset of the window top */
1409 unsigned long lineno; /* Current line number */
1412 char grep[SIZEOF_STR]; /* Search string */
1413 regex_t *regex; /* Pre-compiled regex */
1415 /* If non-NULL, points to the view that opened this view. If this view
1416 * is closed tig will switch back to the parent view. */
1417 struct view *parent;
1420 size_t lines; /* Total number of lines */
1421 struct line *line; /* Line index */
1422 size_t line_alloc; /* Total number of allocated lines */
1423 size_t line_size; /* Total number of used lines */
1424 unsigned int digits; /* Number of digits in the lines member. */
1427 struct line *curline; /* Line currently being drawn. */
1428 enum line_type curtype; /* Attribute currently used for drawing. */
1429 unsigned long col; /* Column when drawing. */
1437 /* What type of content being displayed. Used in the title bar. */
1439 /* Open and reads in all view content. */
1440 bool (*open)(struct view *view);
1441 /* Read one line; updates view->line. */
1442 bool (*read)(struct view *view, char *data);
1443 /* Draw one line; @lineno must be < view->height. */
1444 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1445 /* Depending on view handle a special requests. */
1446 enum request (*request)(struct view *view, enum request request, struct line *line);
1447 /* Search for regex in a line. */
1448 bool (*grep)(struct view *view, struct line *line);
1450 void (*select)(struct view *view, struct line *line);
1453 static struct view_ops pager_ops;
1454 static struct view_ops main_ops;
1455 static struct view_ops tree_ops;
1456 static struct view_ops blob_ops;
1457 static struct view_ops blame_ops;
1458 static struct view_ops help_ops;
1459 static struct view_ops status_ops;
1460 static struct view_ops stage_ops;
1462 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1463 { name, cmd, #env, ref, ops, map, git }
1465 #define VIEW_(id, name, ops, git, ref) \
1466 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1469 static struct view views[] = {
1470 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1471 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1472 VIEW_(LOG, "log", &pager_ops, TRUE, ref_head),
1473 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1474 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1475 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1476 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1477 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1478 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1479 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1482 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1483 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1485 #define foreach_view(view, i) \
1486 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1488 #define view_is_displayed(view) \
1489 (view == display[0] || view == display[1])
1496 static int line_graphics[] = {
1497 /* LINE_GRAPHIC_VLINE: */ '|'
1501 set_view_attr(struct view *view, enum line_type type)
1503 if (!view->curline->selected && view->curtype != type) {
1504 wattrset(view->win, get_line_attr(type));
1505 wchgat(view->win, -1, 0, type, NULL);
1506 view->curtype = type;
1511 draw_chars(struct view *view, enum line_type type, const char *string,
1512 int max_len, bool use_tilde)
1516 int trimmed = FALSE;
1522 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1524 col = len = strlen(string);
1525 if (len > max_len) {
1529 col = len = max_len;
1534 set_view_attr(view, type);
1535 waddnstr(view->win, string, len);
1536 if (trimmed && use_tilde) {
1537 set_view_attr(view, LINE_DELIMITER);
1538 waddch(view->win, '~');
1546 draw_space(struct view *view, enum line_type type, int max, int spaces)
1548 static char space[] = " ";
1551 spaces = MIN(max, spaces);
1553 while (spaces > 0) {
1554 int len = MIN(spaces, sizeof(space) - 1);
1556 col += draw_chars(view, type, space, spaces, FALSE);
1564 draw_lineno(struct view *view, unsigned int lineno)
1567 int digits3 = view->digits < 3 ? 3 : view->digits;
1568 int max_number = MIN(digits3, STRING_SIZE(number));
1569 int max = view->width - view->col;
1572 if (max < max_number)
1575 lineno += view->offset + 1;
1576 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1577 static char fmt[] = "%1ld";
1579 if (view->digits <= 9)
1580 fmt[1] = '0' + digits3;
1582 if (!string_format(number, fmt, lineno))
1584 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1586 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1590 set_view_attr(view, LINE_DEFAULT);
1591 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1596 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1599 return view->width - view->col <= 0;
1603 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1605 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1606 return view->width - view->col <= 0;
1610 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1612 int max = view->width - view->col;
1618 set_view_attr(view, type);
1619 /* Using waddch() instead of waddnstr() ensures that
1620 * they'll be rendered correctly for the cursor line. */
1621 for (i = 0; i < size; i++)
1622 waddch(view->win, graphic[i]);
1626 waddch(view->win, ' ');
1630 return view->width - view->col <= 0;
1634 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1636 int max = MIN(view->width - view->col, len);
1640 col = draw_chars(view, type, text, max - 1, trim);
1642 col = draw_space(view, type, max - 1, max - 1);
1644 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1645 return view->width - view->col <= 0;
1649 draw_date(struct view *view, struct tm *time)
1651 char buf[DATE_COLS];
1656 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1657 date = timelen ? buf : NULL;
1659 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1663 draw_view_line(struct view *view, unsigned int lineno)
1666 bool selected = (view->offset + lineno == view->lineno);
1669 assert(view_is_displayed(view));
1671 if (view->offset + lineno >= view->lines)
1674 line = &view->line[view->offset + lineno];
1676 wmove(view->win, lineno, 0);
1678 view->curline = line;
1679 view->curtype = LINE_NONE;
1680 line->selected = FALSE;
1683 set_view_attr(view, LINE_CURSOR);
1684 line->selected = TRUE;
1685 view->ops->select(view, line);
1686 } else if (line->selected) {
1687 wclrtoeol(view->win);
1690 scrollok(view->win, FALSE);
1691 draw_ok = view->ops->draw(view, line, lineno);
1692 scrollok(view->win, TRUE);
1698 redraw_view_dirty(struct view *view)
1703 for (lineno = 0; lineno < view->height; lineno++) {
1704 struct line *line = &view->line[view->offset + lineno];
1710 if (!draw_view_line(view, lineno))
1716 redrawwin(view->win);
1718 wnoutrefresh(view->win);
1720 wrefresh(view->win);
1724 redraw_view_from(struct view *view, int lineno)
1726 assert(0 <= lineno && lineno < view->height);
1728 for (; lineno < view->height; lineno++) {
1729 if (!draw_view_line(view, lineno))
1733 redrawwin(view->win);
1735 wnoutrefresh(view->win);
1737 wrefresh(view->win);
1741 redraw_view(struct view *view)
1744 redraw_view_from(view, 0);
1749 update_view_title(struct view *view)
1751 char buf[SIZEOF_STR];
1752 char state[SIZEOF_STR];
1753 size_t bufpos = 0, statelen = 0;
1755 assert(view_is_displayed(view));
1757 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1758 unsigned int view_lines = view->offset + view->height;
1759 unsigned int lines = view->lines
1760 ? MIN(view_lines, view->lines) * 100 / view->lines
1763 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1770 time_t secs = time(NULL) - view->start_time;
1772 /* Three git seconds are a long time ... */
1774 string_format_from(state, &statelen, " %lds", secs);
1778 string_format_from(buf, &bufpos, "[%s]", view->name);
1779 if (*view->ref && bufpos < view->width) {
1780 size_t refsize = strlen(view->ref);
1781 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1783 if (minsize < view->width)
1784 refsize = view->width - minsize + 7;
1785 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1788 if (statelen && bufpos < view->width) {
1789 string_format_from(buf, &bufpos, " %s", state);
1792 if (view == display[current_view])
1793 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1795 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1797 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1798 wclrtoeol(view->title);
1799 wmove(view->title, 0, view->width - 1);
1802 wnoutrefresh(view->title);
1804 wrefresh(view->title);
1808 resize_display(void)
1811 struct view *base = display[0];
1812 struct view *view = display[1] ? display[1] : display[0];
1814 /* Setup window dimensions */
1816 getmaxyx(stdscr, base->height, base->width);
1818 /* Make room for the status window. */
1822 /* Horizontal split. */
1823 view->width = base->width;
1824 view->height = SCALE_SPLIT_VIEW(base->height);
1825 base->height -= view->height;
1827 /* Make room for the title bar. */
1831 /* Make room for the title bar. */
1836 foreach_displayed_view (view, i) {
1838 view->win = newwin(view->height, 0, offset, 0);
1840 die("Failed to create %s view", view->name);
1842 scrollok(view->win, TRUE);
1844 view->title = newwin(1, 0, offset + view->height, 0);
1846 die("Failed to create title window");
1849 wresize(view->win, view->height, view->width);
1850 mvwin(view->win, offset, 0);
1851 mvwin(view->title, offset + view->height, 0);
1854 offset += view->height + 1;
1859 redraw_display(void)
1864 foreach_displayed_view (view, i) {
1866 update_view_title(view);
1871 update_display_cursor(struct view *view)
1873 /* Move the cursor to the right-most column of the cursor line.
1875 * XXX: This could turn out to be a bit expensive, but it ensures that
1876 * the cursor does not jump around. */
1878 wmove(view->win, view->lineno - view->offset, view->width - 1);
1879 wrefresh(view->win);
1887 /* Scrolling backend */
1889 do_scroll_view(struct view *view, int lines)
1891 bool redraw_current_line = FALSE;
1893 /* The rendering expects the new offset. */
1894 view->offset += lines;
1896 assert(0 <= view->offset && view->offset < view->lines);
1899 /* Move current line into the view. */
1900 if (view->lineno < view->offset) {
1901 view->lineno = view->offset;
1902 redraw_current_line = TRUE;
1903 } else if (view->lineno >= view->offset + view->height) {
1904 view->lineno = view->offset + view->height - 1;
1905 redraw_current_line = TRUE;
1908 assert(view->offset <= view->lineno && view->lineno < view->lines);
1910 /* Redraw the whole screen if scrolling is pointless. */
1911 if (view->height < ABS(lines)) {
1915 int line = lines > 0 ? view->height - lines : 0;
1916 int end = line + ABS(lines);
1918 wscrl(view->win, lines);
1920 for (; line < end; line++) {
1921 if (!draw_view_line(view, line))
1925 if (redraw_current_line)
1926 draw_view_line(view, view->lineno - view->offset);
1929 redrawwin(view->win);
1930 wrefresh(view->win);
1934 /* Scroll frontend */
1936 scroll_view(struct view *view, enum request request)
1940 assert(view_is_displayed(view));
1943 case REQ_SCROLL_PAGE_DOWN:
1944 lines = view->height;
1945 case REQ_SCROLL_LINE_DOWN:
1946 if (view->offset + lines > view->lines)
1947 lines = view->lines - view->offset;
1949 if (lines == 0 || view->offset + view->height >= view->lines) {
1950 report("Cannot scroll beyond the last line");
1955 case REQ_SCROLL_PAGE_UP:
1956 lines = view->height;
1957 case REQ_SCROLL_LINE_UP:
1958 if (lines > view->offset)
1959 lines = view->offset;
1962 report("Cannot scroll beyond the first line");
1970 die("request %d not handled in switch", request);
1973 do_scroll_view(view, lines);
1978 move_view(struct view *view, enum request request)
1980 int scroll_steps = 0;
1984 case REQ_MOVE_FIRST_LINE:
1985 steps = -view->lineno;
1988 case REQ_MOVE_LAST_LINE:
1989 steps = view->lines - view->lineno - 1;
1992 case REQ_MOVE_PAGE_UP:
1993 steps = view->height > view->lineno
1994 ? -view->lineno : -view->height;
1997 case REQ_MOVE_PAGE_DOWN:
1998 steps = view->lineno + view->height >= view->lines
1999 ? view->lines - view->lineno - 1 : view->height;
2011 die("request %d not handled in switch", request);
2014 if (steps <= 0 && view->lineno == 0) {
2015 report("Cannot move beyond the first line");
2018 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2019 report("Cannot move beyond the last line");
2023 /* Move the current line */
2024 view->lineno += steps;
2025 assert(0 <= view->lineno && view->lineno < view->lines);
2027 /* Check whether the view needs to be scrolled */
2028 if (view->lineno < view->offset ||
2029 view->lineno >= view->offset + view->height) {
2030 scroll_steps = steps;
2031 if (steps < 0 && -steps > view->offset) {
2032 scroll_steps = -view->offset;
2034 } else if (steps > 0) {
2035 if (view->lineno == view->lines - 1 &&
2036 view->lines > view->height) {
2037 scroll_steps = view->lines - view->offset - 1;
2038 if (scroll_steps >= view->height)
2039 scroll_steps -= view->height - 1;
2044 if (!view_is_displayed(view)) {
2045 view->offset += scroll_steps;
2046 assert(0 <= view->offset && view->offset < view->lines);
2047 view->ops->select(view, &view->line[view->lineno]);
2051 /* Repaint the old "current" line if we be scrolling */
2052 if (ABS(steps) < view->height)
2053 draw_view_line(view, view->lineno - steps - view->offset);
2056 do_scroll_view(view, scroll_steps);
2060 /* Draw the current line */
2061 draw_view_line(view, view->lineno - view->offset);
2063 redrawwin(view->win);
2064 wrefresh(view->win);
2073 static void search_view(struct view *view, enum request request);
2076 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2078 assert(view_is_displayed(view));
2080 if (!view->ops->grep(view, line))
2083 if (lineno - view->offset >= view->height) {
2084 view->offset = lineno;
2085 view->lineno = lineno;
2089 unsigned long old_lineno = view->lineno - view->offset;
2091 view->lineno = lineno;
2092 draw_view_line(view, old_lineno);
2094 draw_view_line(view, view->lineno - view->offset);
2095 redrawwin(view->win);
2096 wrefresh(view->win);
2099 report("Line %ld matches '%s'", lineno + 1, view->grep);
2104 find_next(struct view *view, enum request request)
2106 unsigned long lineno = view->lineno;
2111 report("No previous search");
2113 search_view(view, request);
2123 case REQ_SEARCH_BACK:
2132 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2133 lineno += direction;
2135 /* Note, lineno is unsigned long so will wrap around in which case it
2136 * will become bigger than view->lines. */
2137 for (; lineno < view->lines; lineno += direction) {
2138 struct line *line = &view->line[lineno];
2140 if (find_next_line(view, lineno, line))
2144 report("No match found for '%s'", view->grep);
2148 search_view(struct view *view, enum request request)
2153 regfree(view->regex);
2156 view->regex = calloc(1, sizeof(*view->regex));
2161 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2162 if (regex_err != 0) {
2163 char buf[SIZEOF_STR] = "unknown error";
2165 regerror(regex_err, view->regex, buf, sizeof(buf));
2166 report("Search failed: %s", buf);
2170 string_copy(view->grep, opt_search);
2172 find_next(view, request);
2176 * Incremental updating
2180 end_update(struct view *view, bool force)
2184 while (!view->ops->read(view, NULL))
2187 set_nonblocking_input(FALSE);
2188 if (view->pipe == stdin)
2196 begin_update(struct view *view)
2199 string_copy(view->cmd, opt_cmd);
2201 /* When running random commands, initially show the
2202 * command in the title. However, it maybe later be
2203 * overwritten if a commit line is selected. */
2204 if (view == VIEW(REQ_VIEW_PAGER))
2205 string_copy(view->ref, view->cmd);
2209 } else if (view == VIEW(REQ_VIEW_TREE)) {
2210 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2211 char path[SIZEOF_STR];
2213 if (strcmp(view->vid, view->id))
2214 opt_path[0] = path[0] = 0;
2215 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2218 if (!string_format(view->cmd, format, view->id, path))
2222 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2223 const char *id = view->id;
2225 if (!string_format(view->cmd, format, id, id, id, id, id))
2228 /* Put the current ref_* value to the view title ref
2229 * member. This is needed by the blob view. Most other
2230 * views sets it automatically after loading because the
2231 * first line is a commit line. */
2232 string_copy_rev(view->ref, view->id);
2235 /* Special case for the pager view. */
2237 view->pipe = opt_pipe;
2240 view->pipe = popen(view->cmd, "r");
2246 set_nonblocking_input(TRUE);
2251 string_copy_rev(view->vid, view->id);
2256 for (i = 0; i < view->lines; i++)
2257 if (view->line[i].data)
2258 free(view->line[i].data);
2264 view->start_time = time(NULL);
2269 #define ITEM_CHUNK_SIZE 256
2271 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2273 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2274 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2276 if (mem == NULL || num_chunks != num_chunks_new) {
2277 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2278 mem = realloc(mem, *size * item_size);
2284 static struct line *
2285 realloc_lines(struct view *view, size_t line_size)
2287 size_t alloc = view->line_alloc;
2288 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2289 sizeof(*view->line));
2295 view->line_alloc = alloc;
2296 view->line_size = line_size;
2301 update_view(struct view *view)
2303 char in_buffer[BUFSIZ];
2304 char out_buffer[BUFSIZ * 2];
2306 /* The number of lines to read. If too low it will cause too much
2307 * redrawing (and possible flickering), if too high responsiveness
2309 unsigned long lines = view->height;
2310 int redraw_from = -1;
2315 /* Only redraw if lines are visible. */
2316 if (view->offset + view->height >= view->lines)
2317 redraw_from = view->lines - view->offset;
2319 /* FIXME: This is probably not perfect for backgrounded views. */
2320 if (!realloc_lines(view, view->lines + lines))
2323 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2324 size_t linelen = strlen(line);
2327 line[linelen - 1] = 0;
2329 if (opt_iconv != ICONV_NONE) {
2330 ICONV_CONST char *inbuf = line;
2331 size_t inlen = linelen;
2333 char *outbuf = out_buffer;
2334 size_t outlen = sizeof(out_buffer);
2338 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2339 if (ret != (size_t) -1) {
2341 linelen = strlen(out_buffer);
2345 if (!view->ops->read(view, line))
2355 lines = view->lines;
2356 for (digits = 0; lines; digits++)
2359 /* Keep the displayed view in sync with line number scaling. */
2360 if (digits != view->digits) {
2361 view->digits = digits;
2366 if (!view_is_displayed(view))
2369 if (view == VIEW(REQ_VIEW_TREE)) {
2370 /* Clear the view and redraw everything since the tree sorting
2371 * might have rearranged things. */
2374 } else if (redraw_from >= 0) {
2375 /* If this is an incremental update, redraw the previous line
2376 * since for commits some members could have changed when
2377 * loading the main view. */
2378 if (redraw_from > 0)
2381 /* Since revision graph visualization requires knowledge
2382 * about the parent commit, it causes a further one-off
2383 * needed to be redrawn for incremental updates. */
2384 if (redraw_from > 0 && opt_rev_graph)
2387 /* Incrementally draw avoids flickering. */
2388 redraw_view_from(view, redraw_from);
2391 if (view == VIEW(REQ_VIEW_BLAME))
2392 redraw_view_dirty(view);
2394 /* Update the title _after_ the redraw so that if the redraw picks up a
2395 * commit reference in view->ref it'll be available here. */
2396 update_view_title(view);
2399 if (ferror(view->pipe)) {
2400 report("Failed to read: %s", strerror(errno));
2401 end_update(view, TRUE);
2403 } else if (feof(view->pipe)) {
2405 end_update(view, FALSE);
2411 report("Allocation failure");
2412 end_update(view, TRUE);
2416 static struct line *
2417 add_line_data(struct view *view, void *data, enum line_type type)
2419 struct line *line = &view->line[view->lines++];
2421 memset(line, 0, sizeof(*line));
2428 static struct line *
2429 add_line_text(struct view *view, char *data, enum line_type type)
2432 data = strdup(data);
2434 return data ? add_line_data(view, data, type) : NULL;
2443 OPEN_DEFAULT = 0, /* Use default view switching. */
2444 OPEN_SPLIT = 1, /* Split current view. */
2445 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2446 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2447 OPEN_NOMAXIMIZE = 8 /* Do not maximize the current view. */
2451 open_view(struct view *prev, enum request request, enum open_flags flags)
2453 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2454 bool split = !!(flags & OPEN_SPLIT);
2455 bool reload = !!(flags & OPEN_RELOAD);
2456 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2457 struct view *view = VIEW(request);
2458 int nviews = displayed_views();
2459 struct view *base_view = display[0];
2461 if (view == prev && nviews == 1 && !reload) {
2462 report("Already in %s view", view->name);
2466 if (view->git_dir && !opt_git_dir[0]) {
2467 report("The %s view is disabled in pager view", view->name);
2475 } else if (!nomaximize) {
2476 /* Maximize the current view. */
2477 memset(display, 0, sizeof(display));
2479 display[current_view] = view;
2482 /* Resize the view when switching between split- and full-screen,
2483 * or when switching between two different full-screen views. */
2484 if (nviews != displayed_views() ||
2485 (nviews == 1 && base_view != display[0]))
2489 end_update(view, TRUE);
2491 if (view->ops->open) {
2492 if (!view->ops->open(view)) {
2493 report("Failed to load %s view", view->name);
2497 } else if ((reload || strcmp(view->vid, view->id)) &&
2498 !begin_update(view)) {
2499 report("Failed to load %s view", view->name);
2503 if (split && prev->lineno - prev->offset >= prev->height) {
2504 /* Take the title line into account. */
2505 int lines = prev->lineno - prev->offset - prev->height + 1;
2507 /* Scroll the view that was split if the current line is
2508 * outside the new limited view. */
2509 do_scroll_view(prev, lines);
2512 if (prev && view != prev) {
2513 if (split && !backgrounded) {
2514 /* "Blur" the previous view. */
2515 update_view_title(prev);
2518 view->parent = prev;
2521 if (view->pipe && view->lines == 0) {
2522 /* Clear the old view and let the incremental updating refill
2531 /* If the view is backgrounded the above calls to report()
2532 * won't redraw the view title. */
2534 update_view_title(view);
2538 open_external_viewer(const char *cmd)
2540 def_prog_mode(); /* save current tty modes */
2541 endwin(); /* restore original tty modes */
2543 fprintf(stderr, "Press Enter to continue");
2550 open_mergetool(const char *file)
2552 char cmd[SIZEOF_STR];
2553 char file_sq[SIZEOF_STR];
2555 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2556 string_format(cmd, "git mergetool %s", file_sq)) {
2557 open_external_viewer(cmd);
2562 open_editor(bool from_root, const char *file)
2564 char cmd[SIZEOF_STR];
2565 char file_sq[SIZEOF_STR];
2567 char *prefix = from_root ? opt_cdup : "";
2569 editor = getenv("GIT_EDITOR");
2570 if (!editor && *opt_editor)
2571 editor = opt_editor;
2573 editor = getenv("VISUAL");
2575 editor = getenv("EDITOR");
2579 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2580 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2581 open_external_viewer(cmd);
2586 open_run_request(enum request request)
2588 struct run_request *req = get_run_request(request);
2589 char buf[SIZEOF_STR * 2];
2594 report("Unknown run request");
2602 char *next = strstr(cmd, "%(");
2603 int len = next - cmd;
2610 } else if (!strncmp(next, "%(head)", 7)) {
2613 } else if (!strncmp(next, "%(commit)", 9)) {
2616 } else if (!strncmp(next, "%(blob)", 7)) {
2620 report("Unknown replacement in run request: `%s`", req->cmd);
2624 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2628 next = strchr(next, ')') + 1;
2632 open_external_viewer(buf);
2636 * User request switch noodle
2640 view_driver(struct view *view, enum request request)
2644 if (request == REQ_NONE) {
2649 if (request > REQ_NONE) {
2650 open_run_request(request);
2651 /* FIXME: When all views can refresh always do this. */
2652 if (view == VIEW(REQ_VIEW_STATUS) ||
2653 view == VIEW(REQ_VIEW_STAGE))
2654 request = REQ_REFRESH;
2659 if (view && view->lines) {
2660 request = view->ops->request(view, request, &view->line[view->lineno]);
2661 if (request == REQ_NONE)
2668 case REQ_MOVE_PAGE_UP:
2669 case REQ_MOVE_PAGE_DOWN:
2670 case REQ_MOVE_FIRST_LINE:
2671 case REQ_MOVE_LAST_LINE:
2672 move_view(view, request);
2675 case REQ_SCROLL_LINE_DOWN:
2676 case REQ_SCROLL_LINE_UP:
2677 case REQ_SCROLL_PAGE_DOWN:
2678 case REQ_SCROLL_PAGE_UP:
2679 scroll_view(view, request);
2682 case REQ_VIEW_BLAME:
2684 report("No file chosen, press %s to open tree view",
2685 get_key(REQ_VIEW_TREE));
2688 open_view(view, request, OPEN_DEFAULT);
2693 report("No file chosen, press %s to open tree view",
2694 get_key(REQ_VIEW_TREE));
2697 open_view(view, request, OPEN_DEFAULT);
2700 case REQ_VIEW_PAGER:
2701 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2702 report("No pager content, press %s to run command from prompt",
2703 get_key(REQ_PROMPT));
2706 open_view(view, request, OPEN_DEFAULT);
2709 case REQ_VIEW_STAGE:
2710 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2711 report("No stage content, press %s to open the status view and choose file",
2712 get_key(REQ_VIEW_STATUS));
2715 open_view(view, request, OPEN_DEFAULT);
2718 case REQ_VIEW_STATUS:
2719 if (opt_is_inside_work_tree == FALSE) {
2720 report("The status view requires a working tree");
2723 open_view(view, request, OPEN_DEFAULT);
2731 open_view(view, request, OPEN_DEFAULT);
2736 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2738 if ((view == VIEW(REQ_VIEW_DIFF) &&
2739 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2740 (view == VIEW(REQ_VIEW_DIFF) &&
2741 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2742 (view == VIEW(REQ_VIEW_STAGE) &&
2743 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2744 (view == VIEW(REQ_VIEW_BLOB) &&
2745 view->parent == VIEW(REQ_VIEW_TREE))) {
2748 view = view->parent;
2749 line = view->lineno;
2750 move_view(view, request);
2751 if (view_is_displayed(view))
2752 update_view_title(view);
2753 if (line != view->lineno)
2754 view->ops->request(view, REQ_ENTER,
2755 &view->line[view->lineno]);
2758 move_view(view, request);
2764 int nviews = displayed_views();
2765 int next_view = (current_view + 1) % nviews;
2767 if (next_view == current_view) {
2768 report("Only one view is displayed");
2772 current_view = next_view;
2773 /* Blur out the title of the previous view. */
2774 update_view_title(view);
2779 report("Refreshing is not yet supported for the %s view", view->name);
2783 if (displayed_views() == 2)
2784 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2787 case REQ_TOGGLE_LINENO:
2788 opt_line_number = !opt_line_number;
2792 case REQ_TOGGLE_DATE:
2793 opt_date = !opt_date;
2797 case REQ_TOGGLE_AUTHOR:
2798 opt_author = !opt_author;
2802 case REQ_TOGGLE_REV_GRAPH:
2803 opt_rev_graph = !opt_rev_graph;
2807 case REQ_TOGGLE_REFS:
2808 opt_show_refs = !opt_show_refs;
2813 /* Always reload^Wrerun commands from the prompt. */
2814 open_view(view, opt_request, OPEN_RELOAD);
2818 case REQ_SEARCH_BACK:
2819 search_view(view, request);
2824 find_next(view, request);
2827 case REQ_STOP_LOADING:
2828 for (i = 0; i < ARRAY_SIZE(views); i++) {
2831 report("Stopped loading the %s view", view->name),
2832 end_update(view, TRUE);
2836 case REQ_SHOW_VERSION:
2837 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2840 case REQ_SCREEN_RESIZE:
2843 case REQ_SCREEN_REDRAW:
2848 report("Nothing to edit");
2853 report("Nothing to enter");
2857 case REQ_VIEW_CLOSE:
2858 /* XXX: Mark closed views by letting view->parent point to the
2859 * view itself. Parents to closed view should never be
2862 view->parent->parent != view->parent) {
2863 memset(display, 0, sizeof(display));
2865 display[current_view] = view->parent;
2866 view->parent = view;
2876 /* An unknown key will show most commonly used commands. */
2877 report("Unknown key, press 'h' for help");
2890 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2892 char *text = line->data;
2894 if (opt_line_number && draw_lineno(view, lineno))
2897 draw_text(view, line->type, text, TRUE);
2902 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2904 char refbuf[SIZEOF_STR];
2908 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2911 pipe = popen(refbuf, "r");
2915 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2916 ref = chomp_string(ref);
2922 /* This is the only fatal call, since it can "corrupt" the buffer. */
2923 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2930 add_pager_refs(struct view *view, struct line *line)
2932 char buf[SIZEOF_STR];
2933 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2935 size_t bufpos = 0, refpos = 0;
2936 const char *sep = "Refs: ";
2937 bool is_tag = FALSE;
2939 assert(line->type == LINE_COMMIT);
2941 refs = get_refs(commit_id);
2943 if (view == VIEW(REQ_VIEW_DIFF))
2944 goto try_add_describe_ref;
2949 struct ref *ref = refs[refpos];
2950 char *fmt = ref->tag ? "%s[%s]" :
2951 ref->remote ? "%s<%s>" : "%s%s";
2953 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2958 } while (refs[refpos++]->next);
2960 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2961 try_add_describe_ref:
2962 /* Add <tag>-g<commit_id> "fake" reference. */
2963 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2970 if (!realloc_lines(view, view->line_size + 1))
2973 add_line_text(view, buf, LINE_PP_REFS);
2977 pager_read(struct view *view, char *data)
2984 line = add_line_text(view, data, get_line_type(data));
2988 if (line->type == LINE_COMMIT &&
2989 (view == VIEW(REQ_VIEW_DIFF) ||
2990 view == VIEW(REQ_VIEW_LOG)))
2991 add_pager_refs(view, line);
2997 pager_request(struct view *view, enum request request, struct line *line)
3001 if (request != REQ_ENTER)
3004 if (line->type == LINE_COMMIT &&
3005 (view == VIEW(REQ_VIEW_LOG) ||
3006 view == VIEW(REQ_VIEW_PAGER))) {
3007 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3011 /* Always scroll the view even if it was split. That way
3012 * you can use Enter to scroll through the log view and
3013 * split open each commit diff. */
3014 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3016 /* FIXME: A minor workaround. Scrolling the view will call report("")
3017 * but if we are scrolling a non-current view this won't properly
3018 * update the view title. */
3020 update_view_title(view);
3026 pager_grep(struct view *view, struct line *line)
3029 char *text = line->data;
3034 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3041 pager_select(struct view *view, struct line *line)
3043 if (line->type == LINE_COMMIT) {
3044 char *text = (char *)line->data + STRING_SIZE("commit ");
3046 if (view != VIEW(REQ_VIEW_PAGER))
3047 string_copy_rev(view->ref, text);
3048 string_copy_rev(ref_commit, text);
3052 static struct view_ops pager_ops = {
3068 help_open(struct view *view)
3071 int lines = ARRAY_SIZE(req_info) + 2;
3074 if (view->lines > 0)
3077 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3078 if (!req_info[i].request)
3081 lines += run_requests + 1;
3083 view->line = calloc(lines, sizeof(*view->line));
3087 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3089 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3092 if (req_info[i].request == REQ_NONE)
3095 if (!req_info[i].request) {
3096 add_line_text(view, "", LINE_DEFAULT);
3097 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3101 key = get_key(req_info[i].request);
3103 key = "(no key defined)";
3105 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3108 add_line_text(view, buf, LINE_DEFAULT);
3112 add_line_text(view, "", LINE_DEFAULT);
3113 add_line_text(view, "External commands:", LINE_DEFAULT);
3116 for (i = 0; i < run_requests; i++) {
3117 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3123 key = get_key_name(req->key);
3125 key = "(no key defined)";
3127 if (!string_format(buf, " %-10s %-14s `%s`",
3128 keymap_table[req->keymap].name,
3132 add_line_text(view, buf, LINE_DEFAULT);
3138 static struct view_ops help_ops = {
3153 struct tree_stack_entry {
3154 struct tree_stack_entry *prev; /* Entry below this in the stack */
3155 unsigned long lineno; /* Line number to restore */
3156 char *name; /* Position of name in opt_path */
3159 /* The top of the path stack. */
3160 static struct tree_stack_entry *tree_stack = NULL;
3161 unsigned long tree_lineno = 0;
3164 pop_tree_stack_entry(void)
3166 struct tree_stack_entry *entry = tree_stack;
3168 tree_lineno = entry->lineno;
3170 tree_stack = entry->prev;
3175 push_tree_stack_entry(char *name, unsigned long lineno)
3177 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3178 size_t pathlen = strlen(opt_path);
3183 entry->prev = tree_stack;
3184 entry->name = opt_path + pathlen;
3187 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3188 pop_tree_stack_entry();
3192 /* Move the current line to the first tree entry. */
3194 entry->lineno = lineno;
3197 /* Parse output from git-ls-tree(1):
3199 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3200 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3201 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3202 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3205 #define SIZEOF_TREE_ATTR \
3206 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3208 #define TREE_UP_FORMAT "040000 tree %s\t.."
3211 tree_compare_entry(enum line_type type1, char *name1,
3212 enum line_type type2, char *name2)
3214 if (type1 != type2) {
3215 if (type1 == LINE_TREE_DIR)
3220 return strcmp(name1, name2);
3224 tree_path(struct line *line)
3226 char *path = line->data;
3228 return path + SIZEOF_TREE_ATTR;
3232 tree_read(struct view *view, char *text)
3234 size_t textlen = text ? strlen(text) : 0;
3235 char buf[SIZEOF_STR];
3237 enum line_type type;
3238 bool first_read = view->lines == 0;
3242 if (textlen <= SIZEOF_TREE_ATTR)
3245 type = text[STRING_SIZE("100644 ")] == 't'
3246 ? LINE_TREE_DIR : LINE_TREE_FILE;
3249 /* Add path info line */
3250 if (!string_format(buf, "Directory path /%s", opt_path) ||
3251 !realloc_lines(view, view->line_size + 1) ||
3252 !add_line_text(view, buf, LINE_DEFAULT))
3255 /* Insert "link" to parent directory. */
3257 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3258 !realloc_lines(view, view->line_size + 1) ||
3259 !add_line_text(view, buf, LINE_TREE_DIR))
3264 /* Strip the path part ... */
3266 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3267 size_t striplen = strlen(opt_path);
3268 char *path = text + SIZEOF_TREE_ATTR;
3270 if (pathlen > striplen)
3271 memmove(path, path + striplen,
3272 pathlen - striplen + 1);
3275 /* Skip "Directory ..." and ".." line. */
3276 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3277 struct line *line = &view->line[pos];
3278 char *path1 = tree_path(line);
3279 char *path2 = text + SIZEOF_TREE_ATTR;
3280 int cmp = tree_compare_entry(line->type, path1, type, path2);
3285 text = strdup(text);
3289 if (view->lines > pos)
3290 memmove(&view->line[pos + 1], &view->line[pos],
3291 (view->lines - pos) * sizeof(*line));
3293 line = &view->line[pos];
3300 if (!add_line_text(view, text, type))
3303 if (tree_lineno > view->lineno) {
3304 view->lineno = tree_lineno;
3312 tree_request(struct view *view, enum request request, struct line *line)
3314 enum open_flags flags;
3316 if (request == REQ_VIEW_BLAME) {
3317 char *filename = tree_path(line);
3319 if (line->type == LINE_TREE_DIR) {
3320 report("Cannot show blame for directory %s", opt_path);
3324 string_copy(opt_ref, view->vid);
3325 string_format(opt_file, "%s%s", opt_path, filename);
3328 if (request == REQ_TREE_PARENT) {
3331 request = REQ_ENTER;
3332 line = &view->line[1];
3334 /* quit view if at top of tree */
3335 return REQ_VIEW_CLOSE;
3338 if (request != REQ_ENTER)
3341 /* Cleanup the stack if the tree view is at a different tree. */
3342 while (!*opt_path && tree_stack)
3343 pop_tree_stack_entry();
3345 switch (line->type) {
3347 /* Depending on whether it is a subdir or parent (updir?) link
3348 * mangle the path buffer. */
3349 if (line == &view->line[1] && *opt_path) {
3350 pop_tree_stack_entry();
3353 char *basename = tree_path(line);
3355 push_tree_stack_entry(basename, view->lineno);
3358 /* Trees and subtrees share the same ID, so they are not not
3359 * unique like blobs. */
3360 flags = OPEN_RELOAD;
3361 request = REQ_VIEW_TREE;
3364 case LINE_TREE_FILE:
3365 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3366 request = REQ_VIEW_BLOB;
3373 open_view(view, request, flags);
3374 if (request == REQ_VIEW_TREE) {
3375 view->lineno = tree_lineno;
3382 tree_select(struct view *view, struct line *line)
3384 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3386 if (line->type == LINE_TREE_FILE) {
3387 string_copy_rev(ref_blob, text);
3389 } else if (line->type != LINE_TREE_DIR) {
3393 string_copy_rev(view->ref, text);
3396 static struct view_ops tree_ops = {
3407 blob_read(struct view *view, char *line)
3411 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3414 static struct view_ops blob_ops = {
3427 * Loading the blame view is a two phase job:
3429 * 1. File content is read either using opt_file from the
3430 * filesystem or using git-cat-file.
3431 * 2. Then blame information is incrementally added by
3432 * reading output from git-blame.
3435 struct blame_commit {
3436 char id[SIZEOF_REV]; /* SHA1 ID. */
3437 char title[128]; /* First line of the commit message. */
3438 char author[75]; /* Author of the commit. */
3439 struct tm time; /* Date from the author ident. */
3440 char filename[128]; /* Name of file. */
3444 struct blame_commit *commit;
3445 unsigned int header:1;
3449 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3450 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3453 blame_open(struct view *view)
3455 char path[SIZEOF_STR];
3456 char ref[SIZEOF_STR] = "";
3458 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3461 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3465 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3468 view->pipe = fopen(opt_file, "r");
3470 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3475 view->pipe = popen(view->cmd, "r");
3479 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3482 string_format(view->ref, "%s ...", opt_file);
3483 string_copy_rev(view->vid, opt_file);
3484 set_nonblocking_input(TRUE);
3489 for (i = 0; i < view->lines; i++)
3490 free(view->line[i].data);
3494 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3495 view->offset = view->lines = view->lineno = 0;
3497 view->start_time = time(NULL);
3502 static struct blame_commit *
3503 get_blame_commit(struct view *view, const char *id)
3507 for (i = 0; i < view->lines; i++) {
3508 struct blame *blame = view->line[i].data;
3513 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3514 return blame->commit;
3518 struct blame_commit *commit = calloc(1, sizeof(*commit));
3521 string_ncopy(commit->id, id, SIZEOF_REV);
3527 parse_number(char **posref, size_t *number, size_t min, size_t max)
3529 char *pos = *posref;
3532 pos = strchr(pos + 1, ' ');
3533 if (!pos || !isdigit(pos[1]))
3535 *number = atoi(pos + 1);
3536 if (*number < min || *number > max)
3543 static struct blame_commit *
3544 parse_blame_commit(struct view *view, char *text, int *blamed)
3546 struct blame_commit *commit;
3547 struct blame *blame;
3548 char *pos = text + SIZEOF_REV - 1;
3552 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3555 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3556 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3559 commit = get_blame_commit(view, text);
3565 struct line *line = &view->line[lineno + group - 1];
3568 blame->commit = commit;
3569 blame->header = !group;
3577 blame_read_file(struct view *view, char *line)
3582 if (view->lines > 0)
3583 pipe = popen(view->cmd, "r");
3584 else if (!view->parent)
3585 die("No blame exist for %s", view->vid);
3588 report("Failed to load blame data");
3597 size_t linelen = strlen(line);
3598 struct blame *blame = malloc(sizeof(*blame) + linelen);
3600 blame->commit = NULL;
3601 strncpy(blame->text, line, linelen);
3602 blame->text[linelen] = 0;
3603 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3608 match_blame_header(const char *name, char **line)
3610 size_t namelen = strlen(name);
3611 bool matched = !strncmp(name, *line, namelen);
3620 blame_read(struct view *view, char *line)
3622 static struct blame_commit *commit = NULL;
3623 static int blamed = 0;
3624 static time_t author_time;
3627 return blame_read_file(view, line);
3633 string_format(view->ref, "%s", view->vid);
3634 if (view_is_displayed(view)) {
3635 update_view_title(view);
3636 redraw_view_from(view, 0);
3642 commit = parse_blame_commit(view, line, &blamed);
3643 string_format(view->ref, "%s %2d%%", view->vid,
3644 blamed * 100 / view->lines);
3646 } else if (match_blame_header("author ", &line)) {
3647 string_ncopy(commit->author, line, strlen(line));
3649 } else if (match_blame_header("author-time ", &line)) {
3650 author_time = (time_t) atol(line);
3652 } else if (match_blame_header("author-tz ", &line)) {
3655 tz = ('0' - line[1]) * 60 * 60 * 10;
3656 tz += ('0' - line[2]) * 60 * 60;
3657 tz += ('0' - line[3]) * 60;
3658 tz += ('0' - line[4]) * 60;
3664 gmtime_r(&author_time, &commit->time);
3666 } else if (match_blame_header("summary ", &line)) {
3667 string_ncopy(commit->title, line, strlen(line));
3669 } else if (match_blame_header("filename ", &line)) {
3670 string_ncopy(commit->filename, line, strlen(line));
3678 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3680 struct blame *blame = line->data;
3681 struct tm *time = NULL;
3682 char *id = NULL, *author = NULL;
3684 if (blame->commit && *blame->commit->filename) {
3685 id = blame->commit->id;
3686 author = blame->commit->author;
3687 time = &blame->commit->time;
3690 if (opt_date && draw_date(view, time))
3694 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3697 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3700 if (draw_lineno(view, lineno))
3703 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3708 blame_request(struct view *view, enum request request, struct line *line)
3710 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3711 struct blame *blame = line->data;
3715 if (!blame->commit) {
3716 report("No commit loaded yet");
3720 if (!strcmp(blame->commit->id, NULL_ID)) {
3721 char path[SIZEOF_STR];
3723 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3725 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3728 open_view(view, REQ_VIEW_DIFF, flags);
3739 blame_grep(struct view *view, struct line *line)
3741 struct blame *blame = line->data;
3742 struct blame_commit *commit = blame->commit;
3745 #define MATCH(text, on) \
3746 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3749 char buf[DATE_COLS + 1];
3751 if (MATCH(commit->title, 1) ||
3752 MATCH(commit->author, opt_author) ||
3753 MATCH(commit->id, opt_date))
3756 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3761 return MATCH(blame->text, 1);
3767 blame_select(struct view *view, struct line *line)
3769 struct blame *blame = line->data;
3770 struct blame_commit *commit = blame->commit;
3775 if (!strcmp(commit->id, NULL_ID))
3776 string_ncopy(ref_commit, "HEAD", 4);
3778 string_copy_rev(ref_commit, commit->id);
3781 static struct view_ops blame_ops = {
3799 char rev[SIZEOF_REV];
3800 char name[SIZEOF_STR];
3804 char rev[SIZEOF_REV];
3805 char name[SIZEOF_STR];
3809 static char status_onbranch[SIZEOF_STR];
3810 static struct status stage_status;
3811 static enum line_type stage_line_type;
3812 static size_t stage_chunks;
3813 static int *stage_chunk;
3815 /* Get fields from the diff line:
3816 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3819 status_get_diff(struct status *file, char *buf, size_t bufsize)
3821 char *old_mode = buf + 1;
3822 char *new_mode = buf + 8;
3823 char *old_rev = buf + 15;
3824 char *new_rev = buf + 56;
3825 char *status = buf + 97;
3828 old_mode[-1] != ':' ||
3829 new_mode[-1] != ' ' ||
3830 old_rev[-1] != ' ' ||
3831 new_rev[-1] != ' ' ||
3835 file->status = *status;
3837 string_copy_rev(file->old.rev, old_rev);
3838 string_copy_rev(file->new.rev, new_rev);
3840 file->old.mode = strtoul(old_mode, NULL, 8);
3841 file->new.mode = strtoul(new_mode, NULL, 8);
3843 file->old.name[0] = file->new.name[0] = 0;
3849 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3851 struct status *file = NULL;
3852 struct status *unmerged = NULL;
3853 char buf[SIZEOF_STR * 4];
3857 pipe = popen(cmd, "r");
3861 add_line_data(view, NULL, type);
3863 while (!feof(pipe) && !ferror(pipe)) {
3867 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3870 bufsize += readsize;
3872 /* Process while we have NUL chars. */
3873 while ((sep = memchr(buf, 0, bufsize))) {
3874 size_t sepsize = sep - buf + 1;
3877 if (!realloc_lines(view, view->line_size + 1))
3880 file = calloc(1, sizeof(*file));
3884 add_line_data(view, file, type);
3887 /* Parse diff info part. */
3889 file->status = status;
3891 string_copy(file->old.rev, NULL_ID);
3893 } else if (!file->status) {
3894 if (!status_get_diff(file, buf, sepsize))
3898 memmove(buf, sep + 1, bufsize);
3900 sep = memchr(buf, 0, bufsize);
3903 sepsize = sep - buf + 1;
3905 /* Collapse all 'M'odified entries that
3906 * follow a associated 'U'nmerged entry.
3908 if (file->status == 'U') {
3911 } else if (unmerged) {
3912 int collapse = !strcmp(buf, unmerged->new.name);
3923 /* Grab the old name for rename/copy. */
3924 if (!*file->old.name &&
3925 (file->status == 'R' || file->status == 'C')) {
3926 sepsize = sep - buf + 1;
3927 string_ncopy(file->old.name, buf, sepsize);
3929 memmove(buf, sep + 1, bufsize);
3931 sep = memchr(buf, 0, bufsize);
3934 sepsize = sep - buf + 1;
3937 /* git-ls-files just delivers a NUL separated
3938 * list of file names similar to the second half
3939 * of the git-diff-* output. */
3940 string_ncopy(file->new.name, buf, sepsize);
3941 if (!*file->old.name)
3942 string_copy(file->old.name, file->new.name);
3944 memmove(buf, sep + 1, bufsize);
3955 if (!view->line[view->lines - 1].data)
3956 add_line_data(view, NULL, LINE_STAT_NONE);
3962 /* Don't show unmerged entries in the staged section. */
3963 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3964 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3965 #define STATUS_LIST_OTHER_CMD \
3966 "git ls-files -z --others --exclude-per-directory=.gitignore"
3967 #define STATUS_LIST_NO_HEAD_CMD \
3968 "git ls-files -z --cached --exclude-per-directory=.gitignore"
3970 #define STATUS_DIFF_INDEX_SHOW_CMD \
3971 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3973 #define STATUS_DIFF_FILES_SHOW_CMD \
3974 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3976 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3977 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3979 /* First parse staged info using git-diff-index(1), then parse unstaged
3980 * info using git-diff-files(1), and finally untracked files using
3981 * git-ls-files(1). */
3983 status_open(struct view *view)
3985 struct stat statbuf;
3986 char exclude[SIZEOF_STR];
3987 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3988 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3989 unsigned long prev_lineno = view->lineno;
3990 char indexstatus = 0;
3993 for (i = 0; i < view->lines; i++)
3994 free(view->line[i].data);
3996 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3999 if (!realloc_lines(view, view->line_size + 7))
4002 add_line_data(view, NULL, LINE_STAT_HEAD);
4004 string_copy(status_onbranch, "Initial commit");
4005 else if (!*opt_head)
4006 string_copy(status_onbranch, "Not currently on any branch");
4007 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4011 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
4015 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4018 if (stat(exclude, &statbuf) >= 0) {
4019 size_t cmdsize = strlen(othercmd);
4021 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4022 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4025 cmdsize = strlen(indexcmd);
4027 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4028 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4032 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4034 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4035 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4036 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4039 /* If all went well restore the previous line number to stay in
4040 * the context or select a line with something that can be
4042 if (prev_lineno >= view->lines)
4043 prev_lineno = view->lines - 1;
4044 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4046 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4049 /* If the above fails, always skip the "On branch" line. */
4050 if (prev_lineno < view->lines)
4051 view->lineno = prev_lineno;
4055 if (view->lineno < view->offset)
4056 view->offset = view->lineno;
4057 else if (view->offset + view->height <= view->lineno)
4058 view->offset = view->lineno - view->height + 1;
4064 status_draw(struct view *view, struct line *line, unsigned int lineno)
4066 struct status *status = line->data;
4067 enum line_type type;
4071 switch (line->type) {
4072 case LINE_STAT_STAGED:
4073 type = LINE_STAT_SECTION;
4074 text = "Changes to be committed:";
4077 case LINE_STAT_UNSTAGED:
4078 type = LINE_STAT_SECTION;
4079 text = "Changed but not updated:";
4082 case LINE_STAT_UNTRACKED:
4083 type = LINE_STAT_SECTION;
4084 text = "Untracked files:";
4087 case LINE_STAT_NONE:
4088 type = LINE_DEFAULT;
4089 text = " (no files)";
4092 case LINE_STAT_HEAD:
4093 type = LINE_STAT_HEAD;
4094 text = status_onbranch;
4101 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4103 buf[0] = status->status;
4104 if (draw_text(view, line->type, buf, TRUE))
4106 type = LINE_DEFAULT;
4107 text = status->new.name;
4110 draw_text(view, type, text, TRUE);
4115 status_enter(struct view *view, struct line *line)
4117 struct status *status = line->data;
4118 char oldpath[SIZEOF_STR] = "";
4119 char newpath[SIZEOF_STR] = "";
4122 enum open_flags split;
4124 if (line->type == LINE_STAT_NONE ||
4125 (!status && line[1].type == LINE_STAT_NONE)) {
4126 report("No file to diff");
4131 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4133 /* Diffs for unmerged entries are empty when pasing the
4134 * new path, so leave it empty. */
4135 if (status->status != 'U' &&
4136 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4141 line->type != LINE_STAT_UNTRACKED &&
4142 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4145 switch (line->type) {
4146 case LINE_STAT_STAGED:
4148 if (!string_format_from(opt_cmd, &cmdsize,
4149 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4153 if (!string_format_from(opt_cmd, &cmdsize,
4154 STATUS_DIFF_INDEX_SHOW_CMD,
4160 info = "Staged changes to %s";
4162 info = "Staged changes";
4165 case LINE_STAT_UNSTAGED:
4166 if (!string_format_from(opt_cmd, &cmdsize,
4167 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4170 info = "Unstaged changes to %s";
4172 info = "Unstaged changes";
4175 case LINE_STAT_UNTRACKED:
4180 report("No file to show");
4184 opt_pipe = fopen(status->new.name, "r");
4185 info = "Untracked file %s";
4188 case LINE_STAT_HEAD:
4192 die("line type %d not handled in switch", line->type);
4195 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4196 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4197 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4199 stage_status = *status;
4201 memset(&stage_status, 0, sizeof(stage_status));
4204 stage_line_type = line->type;
4206 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4213 status_exists(struct status *status, enum line_type type)
4215 struct view *view = VIEW(REQ_VIEW_STATUS);
4218 for (line = view->line; line < view->line + view->lines; line++) {
4219 struct status *pos = line->data;
4221 if (line->type == type && pos &&
4222 !strcmp(status->new.name, pos->new.name))
4231 status_update_prepare(enum line_type type)
4233 char cmd[SIZEOF_STR];
4237 type != LINE_STAT_UNTRACKED &&
4238 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4242 case LINE_STAT_STAGED:
4243 string_add(cmd, cmdsize, "git update-index -z --index-info");
4246 case LINE_STAT_UNSTAGED:
4247 case LINE_STAT_UNTRACKED:
4248 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4252 die("line type %d not handled in switch", type);
4255 return popen(cmd, "w");
4259 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4261 char buf[SIZEOF_STR];
4266 case LINE_STAT_STAGED:
4267 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4270 status->old.name, 0))
4274 case LINE_STAT_UNSTAGED:
4275 case LINE_STAT_UNTRACKED:
4276 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4281 die("line type %d not handled in switch", type);
4284 while (!ferror(pipe) && written < bufsize) {
4285 written += fwrite(buf + written, 1, bufsize - written, pipe);
4288 return written == bufsize;
4292 status_update_file(struct status *status, enum line_type type)
4294 FILE *pipe = status_update_prepare(type);
4300 result = status_update_write(pipe, status, type);
4306 status_update_files(struct view *view, struct line *line)
4308 FILE *pipe = status_update_prepare(line->type);
4310 struct line *pos = view->line + view->lines;
4317 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4320 for (file = 0, done = 0; result && file < files; line++, file++) {
4321 int almost_done = file * 100 / files;
4323 if (almost_done > done) {
4325 string_format(view->ref, "updating file %u of %u (%d%% done)",
4327 update_view_title(view);
4329 result = status_update_write(pipe, line->data, line->type);
4337 status_update(struct view *view)
4339 struct line *line = &view->line[view->lineno];
4341 assert(view->lines);
4344 /* This should work even for the "On branch" line. */
4345 if (line < view->line + view->lines && !line[1].data) {
4346 report("Nothing to update");
4350 if (!status_update_files(view, line + 1)) {
4351 report("Failed to update file status");
4355 } else if (!status_update_file(line->data, line->type)) {
4356 report("Failed to update file status");
4364 status_request(struct view *view, enum request request, struct line *line)
4366 struct status *status = line->data;
4369 case REQ_STATUS_UPDATE:
4370 if (!status_update(view))
4374 case REQ_STATUS_MERGE:
4375 if (!status || status->status != 'U') {
4376 report("Merging only possible for files with unmerged status ('U').");
4379 open_mergetool(status->new.name);
4386 open_editor(status->status != '?', status->new.name);
4389 case REQ_VIEW_BLAME:
4391 string_copy(opt_file, status->new.name);
4397 /* After returning the status view has been split to
4398 * show the stage view. No further reloading is
4400 status_enter(view, line);
4404 /* Simply reload the view. */
4411 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4417 status_select(struct view *view, struct line *line)
4419 struct status *status = line->data;
4420 char file[SIZEOF_STR] = "all files";
4424 if (status && !string_format(file, "'%s'", status->new.name))
4427 if (!status && line[1].type == LINE_STAT_NONE)
4430 switch (line->type) {
4431 case LINE_STAT_STAGED:
4432 text = "Press %s to unstage %s for commit";
4435 case LINE_STAT_UNSTAGED:
4436 text = "Press %s to stage %s for commit";
4439 case LINE_STAT_UNTRACKED:
4440 text = "Press %s to stage %s for addition";
4443 case LINE_STAT_HEAD:
4444 case LINE_STAT_NONE:
4445 text = "Nothing to update";
4449 die("line type %d not handled in switch", line->type);
4452 if (status && status->status == 'U') {
4453 text = "Press %s to resolve conflict in %s";
4454 key = get_key(REQ_STATUS_MERGE);
4457 key = get_key(REQ_STATUS_UPDATE);
4460 string_format(view->ref, text, key, file);
4464 status_grep(struct view *view, struct line *line)
4466 struct status *status = line->data;
4467 enum { S_STATUS, S_NAME, S_END } state;
4474 for (state = S_STATUS; state < S_END; state++) {
4478 case S_NAME: text = status->new.name; break;
4480 buf[0] = status->status;
4488 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4495 static struct view_ops status_ops = {
4507 stage_diff_line(FILE *pipe, struct line *line)
4509 char *buf = line->data;
4510 size_t bufsize = strlen(buf);
4513 while (!ferror(pipe) && written < bufsize) {
4514 written += fwrite(buf + written, 1, bufsize - written, pipe);
4519 return written == bufsize;
4523 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4525 while (line < end) {
4526 if (!stage_diff_line(pipe, line++))
4528 if (line->type == LINE_DIFF_CHUNK ||
4529 line->type == LINE_DIFF_HEADER)
4536 static struct line *
4537 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4539 for (; view->line < line; line--)
4540 if (line->type == type)
4547 stage_update_chunk(struct view *view, struct line *chunk)
4549 char cmd[SIZEOF_STR];
4551 struct line *diff_hdr;
4554 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4559 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4562 if (!string_format_from(cmd, &cmdsize,
4563 "git apply --whitespace=nowarn --cached %s - && "
4564 "git update-index -q --unmerged --refresh 2>/dev/null",
4565 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4568 pipe = popen(cmd, "w");
4572 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4573 !stage_diff_write(pipe, chunk, view->line + view->lines))
4578 return chunk ? TRUE : FALSE;
4582 stage_update(struct view *view, struct line *line)
4584 struct line *chunk = NULL;
4586 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4587 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4590 if (!stage_update_chunk(view, chunk)) {
4591 report("Failed to apply chunk");
4595 } else if (!stage_status.status) {
4596 view = VIEW(REQ_VIEW_STATUS);
4598 for (line = view->line; line < view->line + view->lines; line++)
4599 if (line->type == stage_line_type)
4602 if (!status_update_files(view, line + 1)) {
4603 report("Failed to update files");
4607 } else if (!status_update_file(&stage_status, stage_line_type)) {
4608 report("Failed to update file");
4616 stage_next(struct view *view, struct line *line)
4620 if (!stage_chunks) {
4621 static size_t alloc = 0;
4624 for (line = view->line; line < view->line + view->lines; line++) {
4625 if (line->type != LINE_DIFF_CHUNK)
4628 tmp = realloc_items(stage_chunk, &alloc,
4629 stage_chunks, sizeof(*tmp));
4631 report("Allocation failure");
4636 stage_chunk[stage_chunks++] = line - view->line;
4640 for (i = 0; i < stage_chunks; i++) {
4641 if (stage_chunk[i] > view->lineno) {
4642 do_scroll_view(view, stage_chunk[i] - view->lineno);
4643 report("Chunk %d of %d", i + 1, stage_chunks);
4648 report("No next chunk found");
4652 stage_request(struct view *view, enum request request, struct line *line)
4655 case REQ_STATUS_UPDATE:
4656 if (!stage_update(view, line))
4660 case REQ_STAGE_NEXT:
4661 if (stage_line_type == LINE_STAT_UNTRACKED) {
4662 report("File is untracked; press %s to add",
4663 get_key(REQ_STATUS_UPDATE));
4666 stage_next(view, line);
4670 if (!stage_status.new.name[0])
4673 open_editor(stage_status.status != '?', stage_status.new.name);
4677 /* Reload everything ... */
4680 case REQ_VIEW_BLAME:
4681 if (stage_status.new.name[0]) {
4682 string_copy(opt_file, stage_status.new.name);
4688 return pager_request(view, request, line);
4694 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4696 /* Check whether the staged entry still exists, and close the
4697 * stage view if it doesn't. */
4698 if (!status_exists(&stage_status, stage_line_type))
4699 return REQ_VIEW_CLOSE;
4701 if (stage_line_type == LINE_STAT_UNTRACKED)
4702 opt_pipe = fopen(stage_status.new.name, "r");
4704 string_copy(opt_cmd, view->cmd);
4705 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4710 static struct view_ops stage_ops = {
4726 char id[SIZEOF_REV]; /* SHA1 ID. */
4727 char title[128]; /* First line of the commit message. */
4728 char author[75]; /* Author of the commit. */
4729 struct tm time; /* Date from the author ident. */
4730 struct ref **refs; /* Repository references. */
4731 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4732 size_t graph_size; /* The width of the graph array. */
4733 bool has_parents; /* Rewritten --parents seen. */
4736 /* Size of rev graph with no "padding" columns */
4737 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4740 struct rev_graph *prev, *next, *parents;
4741 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4743 struct commit *commit;
4745 unsigned int boundary:1;
4748 /* Parents of the commit being visualized. */
4749 static struct rev_graph graph_parents[4];
4751 /* The current stack of revisions on the graph. */
4752 static struct rev_graph graph_stacks[4] = {
4753 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4754 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4755 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4756 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4760 graph_parent_is_merge(struct rev_graph *graph)
4762 return graph->parents->size > 1;
4766 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4768 struct commit *commit = graph->commit;
4770 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4771 commit->graph[commit->graph_size++] = symbol;
4775 done_rev_graph(struct rev_graph *graph)
4777 if (graph_parent_is_merge(graph) &&
4778 graph->pos < graph->size - 1 &&
4779 graph->next->size == graph->size + graph->parents->size - 1) {
4780 size_t i = graph->pos + graph->parents->size - 1;
4782 graph->commit->graph_size = i * 2;
4783 while (i < graph->next->size - 1) {
4784 append_to_rev_graph(graph, ' ');
4785 append_to_rev_graph(graph, '\\');
4790 graph->size = graph->pos = 0;
4791 graph->commit = NULL;
4792 memset(graph->parents, 0, sizeof(*graph->parents));
4796 push_rev_graph(struct rev_graph *graph, char *parent)
4800 /* "Collapse" duplicate parents lines.
4802 * FIXME: This needs to also update update the drawn graph but
4803 * for now it just serves as a method for pruning graph lines. */
4804 for (i = 0; i < graph->size; i++)
4805 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4808 if (graph->size < SIZEOF_REVITEMS) {
4809 string_copy_rev(graph->rev[graph->size++], parent);
4814 get_rev_graph_symbol(struct rev_graph *graph)
4818 if (graph->boundary)
4819 symbol = REVGRAPH_BOUND;
4820 else if (graph->parents->size == 0)
4821 symbol = REVGRAPH_INIT;
4822 else if (graph_parent_is_merge(graph))
4823 symbol = REVGRAPH_MERGE;
4824 else if (graph->pos >= graph->size)
4825 symbol = REVGRAPH_BRANCH;
4827 symbol = REVGRAPH_COMMIT;
4833 draw_rev_graph(struct rev_graph *graph)
4836 chtype separator, line;
4838 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4839 static struct rev_filler fillers[] = {
4845 chtype symbol = get_rev_graph_symbol(graph);
4846 struct rev_filler *filler;
4849 if (opt_line_graphics)
4850 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4852 filler = &fillers[DEFAULT];
4854 for (i = 0; i < graph->pos; i++) {
4855 append_to_rev_graph(graph, filler->line);
4856 if (graph_parent_is_merge(graph->prev) &&
4857 graph->prev->pos == i)
4858 filler = &fillers[RSHARP];
4860 append_to_rev_graph(graph, filler->separator);
4863 /* Place the symbol for this revision. */
4864 append_to_rev_graph(graph, symbol);
4866 if (graph->prev->size > graph->size)
4867 filler = &fillers[RDIAG];
4869 filler = &fillers[DEFAULT];
4873 for (; i < graph->size; i++) {
4874 append_to_rev_graph(graph, filler->separator);
4875 append_to_rev_graph(graph, filler->line);
4876 if (graph_parent_is_merge(graph->prev) &&
4877 i < graph->prev->pos + graph->parents->size)
4878 filler = &fillers[RSHARP];
4879 if (graph->prev->size > graph->size)
4880 filler = &fillers[LDIAG];
4883 if (graph->prev->size > graph->size) {
4884 append_to_rev_graph(graph, filler->separator);
4885 if (filler->line != ' ')
4886 append_to_rev_graph(graph, filler->line);
4890 /* Prepare the next rev graph */
4892 prepare_rev_graph(struct rev_graph *graph)
4896 /* First, traverse all lines of revisions up to the active one. */
4897 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4898 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4901 push_rev_graph(graph->next, graph->rev[graph->pos]);
4904 /* Interleave the new revision parent(s). */
4905 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4906 push_rev_graph(graph->next, graph->parents->rev[i]);
4908 /* Lastly, put any remaining revisions. */
4909 for (i = graph->pos + 1; i < graph->size; i++)
4910 push_rev_graph(graph->next, graph->rev[i]);
4914 update_rev_graph(struct rev_graph *graph)
4916 /* If this is the finalizing update ... */
4918 prepare_rev_graph(graph);
4920 /* Graph visualization needs a one rev look-ahead,
4921 * so the first update doesn't visualize anything. */
4922 if (!graph->prev->commit)
4925 draw_rev_graph(graph->prev);
4926 done_rev_graph(graph->prev->prev);
4935 main_draw(struct view *view, struct line *line, unsigned int lineno)
4937 struct commit *commit = line->data;
4939 if (!*commit->author)
4942 if (opt_date && draw_date(view, &commit->time))
4946 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
4949 if (opt_rev_graph && commit->graph_size &&
4950 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4953 if (opt_show_refs && commit->refs) {
4957 enum line_type type;
4959 if (commit->refs[i]->head)
4960 type = LINE_MAIN_HEAD;
4961 else if (commit->refs[i]->ltag)
4962 type = LINE_MAIN_LOCAL_TAG;
4963 else if (commit->refs[i]->tag)
4964 type = LINE_MAIN_TAG;
4965 else if (commit->refs[i]->tracked)
4966 type = LINE_MAIN_TRACKED;
4967 else if (commit->refs[i]->remote)
4968 type = LINE_MAIN_REMOTE;
4970 type = LINE_MAIN_REF;
4972 if (draw_text(view, type, "[", TRUE) ||
4973 draw_text(view, type, commit->refs[i]->name, TRUE) ||
4974 draw_text(view, type, "]", TRUE))
4977 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4979 } while (commit->refs[i++]->next);
4982 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4986 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4988 main_read(struct view *view, char *line)
4990 static struct rev_graph *graph = graph_stacks;
4991 enum line_type type;
4992 struct commit *commit;
4995 if (!view->lines && !view->parent)
4996 die("No revisions match the given arguments.");
4997 update_rev_graph(graph);
5001 type = get_line_type(line);
5002 if (type == LINE_COMMIT) {
5003 commit = calloc(1, sizeof(struct commit));
5007 line += STRING_SIZE("commit ");
5009 graph->boundary = 1;
5013 string_copy_rev(commit->id, line);
5014 commit->refs = get_refs(commit->id);
5015 graph->commit = commit;
5016 add_line_data(view, commit, LINE_MAIN_COMMIT);
5018 while ((line = strchr(line, ' '))) {
5020 push_rev_graph(graph->parents, line);
5021 commit->has_parents = TRUE;
5028 commit = view->line[view->lines - 1].data;
5032 if (commit->has_parents)
5034 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5039 /* Parse author lines where the name may be empty:
5040 * author <email@address.tld> 1138474660 +0100
5042 char *ident = line + STRING_SIZE("author ");
5043 char *nameend = strchr(ident, '<');
5044 char *emailend = strchr(ident, '>');
5046 if (!nameend || !emailend)
5049 update_rev_graph(graph);
5050 graph = graph->next;
5052 *nameend = *emailend = 0;
5053 ident = chomp_string(ident);
5055 ident = chomp_string(nameend + 1);
5060 string_ncopy(commit->author, ident, strlen(ident));
5062 /* Parse epoch and timezone */
5063 if (emailend[1] == ' ') {
5064 char *secs = emailend + 2;
5065 char *zone = strchr(secs, ' ');
5066 time_t time = (time_t) atol(secs);
5068 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5072 tz = ('0' - zone[1]) * 60 * 60 * 10;
5073 tz += ('0' - zone[2]) * 60 * 60;
5074 tz += ('0' - zone[3]) * 60;
5075 tz += ('0' - zone[4]) * 60;
5083 gmtime_r(&time, &commit->time);
5088 /* Fill in the commit title if it has not already been set. */
5089 if (commit->title[0])
5092 /* Require titles to start with a non-space character at the
5093 * offset used by git log. */
5094 if (strncmp(line, " ", 4))
5097 /* Well, if the title starts with a whitespace character,
5098 * try to be forgiving. Otherwise we end up with no title. */
5099 while (isspace(*line))
5103 /* FIXME: More graceful handling of titles; append "..." to
5104 * shortened titles, etc. */
5106 string_ncopy(commit->title, line, strlen(line));
5113 main_request(struct view *view, enum request request, struct line *line)
5115 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5117 if (request == REQ_ENTER)
5118 open_view(view, REQ_VIEW_DIFF, flags);
5126 grep_refs(struct ref **refs, regex_t *regex)
5134 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5136 } while (refs[i++]->next);
5142 main_grep(struct view *view, struct line *line)
5144 struct commit *commit = line->data;
5145 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5146 char buf[DATE_COLS + 1];
5149 for (state = S_TITLE; state < S_END; state++) {
5153 case S_TITLE: text = commit->title; break;
5157 text = commit->author;
5162 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5169 if (grep_refs(commit->refs, view->regex) == TRUE)
5176 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5184 main_select(struct view *view, struct line *line)
5186 struct commit *commit = line->data;
5188 string_copy_rev(view->ref, commit->id);
5189 string_copy_rev(ref_commit, view->ref);
5192 static struct view_ops main_ops = {
5204 * Unicode / UTF-8 handling
5206 * NOTE: Much of the following code for dealing with unicode is derived from
5207 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5208 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5211 /* I've (over)annotated a lot of code snippets because I am not entirely
5212 * confident that the approach taken by this small UTF-8 interface is correct.
5216 unicode_width(unsigned long c)
5219 (c <= 0x115f /* Hangul Jamo */
5222 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5224 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5225 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5226 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5227 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5228 || (c >= 0xffe0 && c <= 0xffe6)
5229 || (c >= 0x20000 && c <= 0x2fffd)
5230 || (c >= 0x30000 && c <= 0x3fffd)))
5234 return opt_tab_size;
5239 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5240 * Illegal bytes are set one. */
5241 static const unsigned char utf8_bytes[256] = {
5242 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5243 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5244 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5245 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5246 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5247 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5248 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,
5249 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,
5252 /* Decode UTF-8 multi-byte representation into a unicode character. */
5253 static inline unsigned long
5254 utf8_to_unicode(const char *string, size_t length)
5256 unsigned long unicode;
5260 unicode = string[0];
5263 unicode = (string[0] & 0x1f) << 6;
5264 unicode += (string[1] & 0x3f);
5267 unicode = (string[0] & 0x0f) << 12;
5268 unicode += ((string[1] & 0x3f) << 6);
5269 unicode += (string[2] & 0x3f);
5272 unicode = (string[0] & 0x0f) << 18;
5273 unicode += ((string[1] & 0x3f) << 12);
5274 unicode += ((string[2] & 0x3f) << 6);
5275 unicode += (string[3] & 0x3f);
5278 unicode = (string[0] & 0x0f) << 24;
5279 unicode += ((string[1] & 0x3f) << 18);
5280 unicode += ((string[2] & 0x3f) << 12);
5281 unicode += ((string[3] & 0x3f) << 6);
5282 unicode += (string[4] & 0x3f);
5285 unicode = (string[0] & 0x01) << 30;
5286 unicode += ((string[1] & 0x3f) << 24);
5287 unicode += ((string[2] & 0x3f) << 18);
5288 unicode += ((string[3] & 0x3f) << 12);
5289 unicode += ((string[4] & 0x3f) << 6);
5290 unicode += (string[5] & 0x3f);
5293 die("Invalid unicode length");
5296 /* Invalid characters could return the special 0xfffd value but NUL
5297 * should be just as good. */
5298 return unicode > 0xffff ? 0 : unicode;
5301 /* Calculates how much of string can be shown within the given maximum width
5302 * and sets trimmed parameter to non-zero value if all of string could not be
5303 * shown. If the reserve flag is TRUE, it will reserve at least one
5304 * trailing character, which can be useful when drawing a delimiter.
5306 * Returns the number of bytes to output from string to satisfy max_width. */
5308 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5310 const char *start = string;
5311 const char *end = strchr(string, '\0');
5312 unsigned char last_bytes = 0;
5313 size_t last_ucwidth = 0;
5318 while (string < end) {
5319 int c = *(unsigned char *) string;
5320 unsigned char bytes = utf8_bytes[c];
5322 unsigned long unicode;
5324 if (string + bytes > end)
5327 /* Change representation to figure out whether
5328 * it is a single- or double-width character. */
5330 unicode = utf8_to_unicode(string, bytes);
5331 /* FIXME: Graceful handling of invalid unicode character. */
5335 ucwidth = unicode_width(unicode);
5337 if (*width > max_width) {
5340 if (reserve && *width == max_width) {
5341 string -= last_bytes;
5342 *width -= last_ucwidth;
5349 last_ucwidth = ucwidth;
5352 return string - start;
5360 /* Whether or not the curses interface has been initialized. */
5361 static bool cursed = FALSE;
5363 /* The status window is used for polling keystrokes. */
5364 static WINDOW *status_win;
5366 static bool status_empty = TRUE;
5368 /* Update status and title window. */
5370 report(const char *msg, ...)
5372 struct view *view = display[current_view];
5378 char buf[SIZEOF_STR];
5381 va_start(args, msg);
5382 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5383 buf[sizeof(buf) - 1] = 0;
5384 buf[sizeof(buf) - 2] = '.';
5385 buf[sizeof(buf) - 3] = '.';
5386 buf[sizeof(buf) - 4] = '.';
5392 if (!status_empty || *msg) {
5395 va_start(args, msg);
5397 wmove(status_win, 0, 0);
5399 vwprintw(status_win, msg, args);
5400 status_empty = FALSE;
5402 status_empty = TRUE;
5404 wclrtoeol(status_win);
5405 wrefresh(status_win);
5410 update_view_title(view);
5411 update_display_cursor(view);
5414 /* Controls when nodelay should be in effect when polling user input. */
5416 set_nonblocking_input(bool loading)
5418 static unsigned int loading_views;
5420 if ((loading == FALSE && loading_views-- == 1) ||
5421 (loading == TRUE && loading_views++ == 0))
5422 nodelay(status_win, loading);
5430 /* Initialize the curses library */
5431 if (isatty(STDIN_FILENO)) {
5432 cursed = !!initscr();
5434 /* Leave stdin and stdout alone when acting as a pager. */
5435 FILE *io = fopen("/dev/tty", "r+");
5438 die("Failed to open /dev/tty");
5439 cursed = !!newterm(NULL, io, io);
5443 die("Failed to initialize curses");
5445 nonl(); /* Tell curses not to do NL->CR/NL on output */
5446 cbreak(); /* Take input chars one at a time, no wait for \n */
5447 noecho(); /* Don't echo input */
5448 leaveok(stdscr, TRUE);
5453 getmaxyx(stdscr, y, x);
5454 status_win = newwin(1, 0, y - 1, 0);
5456 die("Failed to create status window");
5458 /* Enable keyboard mapping */
5459 keypad(status_win, TRUE);
5460 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5462 TABSIZE = opt_tab_size;
5463 if (opt_line_graphics) {
5464 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5469 read_prompt(const char *prompt)
5471 enum { READING, STOP, CANCEL } status = READING;
5472 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5475 while (status == READING) {
5481 foreach_view (view, i)
5486 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5487 wclrtoeol(status_win);
5489 /* Refresh, accept single keystroke of input */
5490 key = wgetch(status_win);
5495 status = pos ? STOP : CANCEL;
5513 if (pos >= sizeof(buf)) {
5514 report("Input string too long");
5519 buf[pos++] = (char) key;
5523 /* Clear the status window */
5524 status_empty = FALSE;
5527 if (status == CANCEL)
5536 * Repository references
5539 static struct ref *refs = NULL;
5540 static size_t refs_alloc = 0;
5541 static size_t refs_size = 0;
5543 /* Id <-> ref store */
5544 static struct ref ***id_refs = NULL;
5545 static size_t id_refs_alloc = 0;
5546 static size_t id_refs_size = 0;
5548 static struct ref **
5551 struct ref ***tmp_id_refs;
5552 struct ref **ref_list = NULL;
5553 size_t ref_list_alloc = 0;
5554 size_t ref_list_size = 0;
5557 for (i = 0; i < id_refs_size; i++)
5558 if (!strcmp(id, id_refs[i][0]->id))
5561 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5566 id_refs = tmp_id_refs;
5568 for (i = 0; i < refs_size; i++) {
5571 if (strcmp(id, refs[i].id))
5574 tmp = realloc_items(ref_list, &ref_list_alloc,
5575 ref_list_size + 1, sizeof(*ref_list));
5583 if (ref_list_size > 0)
5584 ref_list[ref_list_size - 1]->next = 1;
5585 ref_list[ref_list_size] = &refs[i];
5587 /* XXX: The properties of the commit chains ensures that we can
5588 * safely modify the shared ref. The repo references will
5589 * always be similar for the same id. */
5590 ref_list[ref_list_size]->next = 0;
5595 id_refs[id_refs_size++] = ref_list;
5601 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5606 bool remote = FALSE;
5607 bool tracked = FALSE;
5608 bool check_replace = FALSE;
5611 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5612 if (!strcmp(name + namelen - 3, "^{}")) {
5615 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5616 check_replace = TRUE;
5622 namelen -= STRING_SIZE("refs/tags/");
5623 name += STRING_SIZE("refs/tags/");
5625 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5627 namelen -= STRING_SIZE("refs/remotes/");
5628 name += STRING_SIZE("refs/remotes/");
5629 tracked = !strcmp(opt_remote, name);
5631 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5632 namelen -= STRING_SIZE("refs/heads/");
5633 name += STRING_SIZE("refs/heads/");
5634 head = !strncmp(opt_head, name, namelen);
5636 } else if (!strcmp(name, "HEAD")) {
5637 opt_no_head = FALSE;
5641 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5642 /* it's an annotated tag, replace the previous sha1 with the
5643 * resolved commit id; relies on the fact git-ls-remote lists
5644 * the commit id of an annotated tag right beofre the commit id
5646 refs[refs_size - 1].ltag = ltag;
5647 string_copy_rev(refs[refs_size - 1].id, id);
5651 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5655 ref = &refs[refs_size++];
5656 ref->name = malloc(namelen + 1);
5660 strncpy(ref->name, name, namelen);
5661 ref->name[namelen] = 0;
5665 ref->remote = remote;
5666 ref->tracked = tracked;
5667 string_copy_rev(ref->id, id);
5675 const char *cmd_env = getenv("TIG_LS_REMOTE");
5676 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5678 return read_properties(popen(cmd, "r"), "\t", read_ref);
5682 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5684 if (!strcmp(name, "i18n.commitencoding"))
5685 string_ncopy(opt_encoding, value, valuelen);
5687 if (!strcmp(name, "core.editor"))
5688 string_ncopy(opt_editor, value, valuelen);
5690 /* branch.<head>.remote */
5692 !strncmp(name, "branch.", 7) &&
5693 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5694 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5695 string_ncopy(opt_remote, value, valuelen);
5697 if (*opt_head && *opt_remote &&
5698 !strncmp(name, "branch.", 7) &&
5699 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5700 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5701 size_t from = strlen(opt_remote);
5703 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5704 value += STRING_SIZE("refs/heads/");
5705 valuelen -= STRING_SIZE("refs/heads/");
5708 if (!string_format_from(opt_remote, &from, "/%s", value))
5716 load_git_config(void)
5718 return read_properties(popen(GIT_CONFIG " --list", "r"),
5719 "=", read_repo_config_option);
5723 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5725 if (!opt_git_dir[0]) {
5726 string_ncopy(opt_git_dir, name, namelen);
5728 } else if (opt_is_inside_work_tree == -1) {
5729 /* This can be 3 different values depending on the
5730 * version of git being used. If git-rev-parse does not
5731 * understand --is-inside-work-tree it will simply echo
5732 * the option else either "true" or "false" is printed.
5733 * Default to true for the unknown case. */
5734 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5736 } else if (opt_cdup[0] == ' ') {
5737 string_ncopy(opt_cdup, name, namelen);
5739 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5740 namelen -= STRING_SIZE("refs/heads/");
5741 name += STRING_SIZE("refs/heads/");
5742 string_ncopy(opt_head, name, namelen);
5750 load_repo_info(void)
5753 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5754 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5756 /* XXX: The line outputted by "--show-cdup" can be empty so
5757 * initialize it to something invalid to make it possible to
5758 * detect whether it has been set or not. */
5761 result = read_properties(pipe, "=", read_repo_info);
5762 if (opt_cdup[0] == ' ')
5769 read_properties(FILE *pipe, const char *separators,
5770 int (*read_property)(char *, size_t, char *, size_t))
5772 char buffer[BUFSIZ];
5779 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5784 name = chomp_string(name);
5785 namelen = strcspn(name, separators);
5787 if (name[namelen]) {
5789 value = chomp_string(name + namelen + 1);
5790 valuelen = strlen(value);
5797 state = read_property(name, namelen, value, valuelen);
5800 if (state != ERR && ferror(pipe))
5813 static void __NORETURN
5816 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5822 static void __NORETURN
5823 die(const char *err, ...)
5829 va_start(args, err);
5830 fputs("tig: ", stderr);
5831 vfprintf(stderr, err, args);
5832 fputs("\n", stderr);
5839 warn(const char *msg, ...)
5843 va_start(args, msg);
5844 fputs("tig warning: ", stderr);
5845 vfprintf(stderr, msg, args);
5846 fputs("\n", stderr);
5851 main(int argc, char *argv[])
5854 enum request request;
5857 signal(SIGINT, quit);
5859 if (setlocale(LC_ALL, "")) {
5860 char *codeset = nl_langinfo(CODESET);
5862 string_ncopy(opt_codeset, codeset, strlen(codeset));
5865 if (load_repo_info() == ERR)
5866 die("Failed to load repo info.");
5868 if (load_options() == ERR)
5869 die("Failed to load user config.");
5871 if (load_git_config() == ERR)
5872 die("Failed to load repo config.");
5874 if (!parse_options(argc, argv))
5877 /* Require a git repository unless when running in pager mode. */
5878 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5879 die("Not a git repository");
5881 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5884 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5885 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5886 if (opt_iconv == ICONV_NONE)
5887 die("Failed to initialize character set conversion");
5890 if (*opt_git_dir && load_refs() == ERR)
5891 die("Failed to load refs.");
5893 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5894 view->cmd_env = getenv(view->cmd_env);
5896 request = opt_request;
5900 while (view_driver(display[current_view], request)) {
5904 foreach_view (view, i)
5907 /* Refresh, accept single keystroke of input */
5908 key = wgetch(status_win);
5910 /* wgetch() with nodelay() enabled returns ERR when there's no
5917 request = get_keybinding(display[current_view]->keymap, key);
5919 /* Some low-level request handling. This keeps access to
5920 * status_win restricted. */
5924 char *cmd = read_prompt(":");
5926 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5927 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5928 opt_request = REQ_VIEW_DIFF;
5930 opt_request = REQ_VIEW_PAGER;
5939 case REQ_SEARCH_BACK:
5941 const char *prompt = request == REQ_SEARCH
5943 char *search = read_prompt(prompt);
5946 string_ncopy(opt_search, search, strlen(search));
5951 case REQ_SCREEN_RESIZE:
5955 getmaxyx(stdscr, height, width);
5957 /* Resize the status view and let the view driver take
5958 * care of resizing the displayed views. */
5959 wresize(status_win, 1, width);
5960 mvwin(status_win, height - 1, 0);
5961 wrefresh(status_win);