Code

push_rev_graph: iterate all graph revs when looking for duplicates
[tig.git] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  *
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.
7  *
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.
12  */
14 #ifndef VERSION
15 #define VERSION "tig-0.4.git"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <sys/types.h>
34 #include <regex.h>
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
40 #include <curses.h>
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
55 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
57 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x)  (sizeof(x) - 1)
60 #define SIZEOF_STR      1024    /* Default string size. */
61 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
64 /* Revision graph */
66 #define REVGRAPH_INIT   'I'
67 #define REVGRAPH_MERGE  'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE   '|'
72 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
74 /* This color name can be used to refer to the default term colors. */
75 #define COLOR_DEFAULT   (-1)
77 #define ICONV_NONE      ((iconv_t) -1)
79 /* The format and size of the date column in the main view. */
80 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
81 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
83 #define AUTHOR_COLS     20
85 /* The default interval between line numbers. */
86 #define NUMBER_INTERVAL 1
88 #define TABSIZE         8
90 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
92 #define TIG_LS_REMOTE \
93         "git ls-remote . 2>/dev/null"
95 #define TIG_DIFF_CMD \
96         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
98 #define TIG_LOG_CMD     \
99         "git log --cc --stat -n100 %s 2>/dev/null"
101 #define TIG_MAIN_CMD \
102         "git log --topo-order --pretty=raw %s 2>/dev/null"
104 #define TIG_TREE_CMD    \
105         "git ls-tree %s %s"
107 #define TIG_BLOB_CMD    \
108         "git cat-file blob %s"
110 /* XXX: Needs to be defined to the empty string. */
111 #define TIG_HELP_CMD    ""
112 #define TIG_PAGER_CMD   ""
114 /* Some ascii-shorthands fitted into the ncurses namespace. */
115 #define KEY_TAB         '\t'
116 #define KEY_RETURN      '\r'
117 #define KEY_ESC         27
120 struct ref {
121         char *name;             /* Ref name; tag or head names are shortened. */
122         char id[SIZEOF_REV];    /* Commit SHA1 ID */
123         unsigned int tag:1;     /* Is it a tag? */
124         unsigned int next:1;    /* For ref lists: are there more refs? */
125 };
127 static struct ref **get_refs(char *id);
129 struct int_map {
130         const char *name;
131         int namelen;
132         int value;
133 };
135 static int
136 set_from_int_map(struct int_map *map, size_t map_size,
137                  int *value, const char *name, int namelen)
140         int i;
142         for (i = 0; i < map_size; i++)
143                 if (namelen == map[i].namelen &&
144                     !strncasecmp(name, map[i].name, namelen)) {
145                         *value = map[i].value;
146                         return OK;
147                 }
149         return ERR;
153 /*
154  * String helpers
155  */
157 static inline void
158 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
160         if (srclen > dstlen - 1)
161                 srclen = dstlen - 1;
163         strncpy(dst, src, srclen);
164         dst[srclen] = 0;
167 /* Shorthands for safely copying into a fixed buffer. */
169 #define string_copy(dst, src) \
170         string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
172 #define string_ncopy(dst, src, srclen) \
173         string_ncopy_do(dst, sizeof(dst), src, srclen)
175 static char *
176 chomp_string(char *name)
178         int namelen;
180         while (isspace(*name))
181                 name++;
183         namelen = strlen(name) - 1;
184         while (namelen > 0 && isspace(name[namelen]))
185                 name[namelen--] = 0;
187         return name;
190 static bool
191 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
193         va_list args;
194         size_t pos = bufpos ? *bufpos : 0;
196         va_start(args, fmt);
197         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
198         va_end(args);
200         if (bufpos)
201                 *bufpos = pos;
203         return pos >= bufsize ? FALSE : TRUE;
206 #define string_format(buf, fmt, args...) \
207         string_nformat(buf, sizeof(buf), NULL, fmt, args)
209 #define string_format_from(buf, from, fmt, args...) \
210         string_nformat(buf, sizeof(buf), from, fmt, args)
212 static int
213 string_enum_compare(const char *str1, const char *str2, int len)
215         size_t i;
217 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
219         /* Diff-Header == DIFF_HEADER */
220         for (i = 0; i < len; i++) {
221                 if (toupper(str1[i]) == toupper(str2[i]))
222                         continue;
224                 if (string_enum_sep(str1[i]) &&
225                     string_enum_sep(str2[i]))
226                         continue;
228                 return str1[i] - str2[i];
229         }
231         return 0;
234 /* Shell quoting
235  *
236  * NOTE: The following is a slightly modified copy of the git project's shell
237  * quoting routines found in the quote.c file.
238  *
239  * Help to copy the thing properly quoted for the shell safety.  any single
240  * quote is replaced with '\'', any exclamation point is replaced with '\!',
241  * and the whole thing is enclosed in a
242  *
243  * E.g.
244  *  original     sq_quote     result
245  *  name     ==> name      ==> 'name'
246  *  a b      ==> a b       ==> 'a b'
247  *  a'b      ==> a'\''b    ==> 'a'\''b'
248  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
249  */
251 static size_t
252 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
254         char c;
256 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
258         BUFPUT('\'');
259         while ((c = *src++)) {
260                 if (c == '\'' || c == '!') {
261                         BUFPUT('\'');
262                         BUFPUT('\\');
263                         BUFPUT(c);
264                         BUFPUT('\'');
265                 } else {
266                         BUFPUT(c);
267                 }
268         }
269         BUFPUT('\'');
271         return bufsize;
275 /*
276  * User requests
277  */
279 #define REQ_INFO \
280         /* XXX: Keep the view request first and in sync with views[]. */ \
281         REQ_GROUP("View switching") \
282         REQ_(VIEW_MAIN,         "Show main view"), \
283         REQ_(VIEW_DIFF,         "Show diff view"), \
284         REQ_(VIEW_LOG,          "Show log view"), \
285         REQ_(VIEW_TREE,         "Show tree view"), \
286         REQ_(VIEW_BLOB,         "Show blob view"), \
287         REQ_(VIEW_HELP,         "Show help page"), \
288         REQ_(VIEW_PAGER,        "Show pager view"), \
289         \
290         REQ_GROUP("View manipulation") \
291         REQ_(ENTER,             "Enter current line and scroll"), \
292         REQ_(NEXT,              "Move to next"), \
293         REQ_(PREVIOUS,          "Move to previous"), \
294         REQ_(VIEW_NEXT,         "Move focus to next view"), \
295         REQ_(VIEW_CLOSE,        "Close the current view"), \
296         REQ_(QUIT,              "Close all views and quit"), \
297         \
298         REQ_GROUP("Cursor navigation") \
299         REQ_(MOVE_UP,           "Move cursor one line up"), \
300         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
301         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
302         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
303         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
304         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
305         \
306         REQ_GROUP("Scrolling") \
307         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
308         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
309         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
310         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
311         \
312         REQ_GROUP("Searching") \
313         REQ_(SEARCH,            "Search the view"), \
314         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
315         REQ_(FIND_NEXT,         "Find next search match"), \
316         REQ_(FIND_PREV,         "Find previous search match"), \
317         \
318         REQ_GROUP("Misc") \
319         REQ_(NONE,              "Do nothing"), \
320         REQ_(PROMPT,            "Bring up the prompt"), \
321         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
322         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
323         REQ_(SHOW_VERSION,      "Show version information"), \
324         REQ_(STOP_LOADING,      "Stop all loading views"), \
325         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
326         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
329 /* User action requests. */
330 enum request {
331 #define REQ_GROUP(help)
332 #define REQ_(req, help) REQ_##req
334         /* Offset all requests to avoid conflicts with ncurses getch values. */
335         REQ_OFFSET = KEY_MAX + 1,
336         REQ_INFO,
337         REQ_UNKNOWN,
339 #undef  REQ_GROUP
340 #undef  REQ_
341 };
343 struct request_info {
344         enum request request;
345         char *name;
346         int namelen;
347         char *help;
348 };
350 static struct request_info req_info[] = {
351 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
352 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
353         REQ_INFO
354 #undef  REQ_GROUP
355 #undef  REQ_
356 };
358 static enum request
359 get_request(const char *name)
361         int namelen = strlen(name);
362         int i;
364         for (i = 0; i < ARRAY_SIZE(req_info); i++)
365                 if (req_info[i].namelen == namelen &&
366                     !string_enum_compare(req_info[i].name, name, namelen))
367                         return req_info[i].request;
369         return REQ_UNKNOWN;
373 /*
374  * Options
375  */
377 static const char usage[] =
378 VERSION " (" __DATE__ ")\n"
379 "\n"
380 "Usage: tig [options]\n"
381 "   or: tig [options] [--] [git log options]\n"
382 "   or: tig [options] log  [git log options]\n"
383 "   or: tig [options] diff [git diff options]\n"
384 "   or: tig [options] show [git show options]\n"
385 "   or: tig [options] <    [git command output]\n"
386 "\n"
387 "Options:\n"
388 "  -l                          Start up in log view\n"
389 "  -d                          Start up in diff view\n"
390 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
391 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
392 "  --                          Mark end of tig options\n"
393 "  -v, --version               Show version and exit\n"
394 "  -h, --help                  Show help message and exit\n";
396 /* Option and state variables. */
397 static bool opt_line_number             = FALSE;
398 static bool opt_rev_graph               = TRUE;
399 static int opt_num_interval             = NUMBER_INTERVAL;
400 static int opt_tab_size                 = TABSIZE;
401 static enum request opt_request         = REQ_VIEW_MAIN;
402 static char opt_cmd[SIZEOF_STR]         = "";
403 static char opt_path[SIZEOF_STR]        = "";
404 static FILE *opt_pipe                   = NULL;
405 static char opt_encoding[20]            = "UTF-8";
406 static bool opt_utf8                    = TRUE;
407 static char opt_codeset[20]             = "UTF-8";
408 static iconv_t opt_iconv                = ICONV_NONE;
409 static char opt_search[SIZEOF_STR]      = "";
411 enum option_type {
412         OPT_NONE,
413         OPT_INT,
414 };
416 static bool
417 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
419         va_list args;
420         char *value = "";
421         int *number;
423         if (opt[0] != '-')
424                 return FALSE;
426         if (opt[1] == '-') {
427                 int namelen = strlen(name);
429                 opt += 2;
431                 if (strncmp(opt, name, namelen))
432                         return FALSE;
434                 if (opt[namelen] == '=')
435                         value = opt + namelen + 1;
437         } else {
438                 if (!short_name || opt[1] != short_name)
439                         return FALSE;
440                 value = opt + 2;
441         }
443         va_start(args, type);
444         if (type == OPT_INT) {
445                 number = va_arg(args, int *);
446                 if (isdigit(*value))
447                         *number = atoi(value);
448         }
449         va_end(args);
451         return TRUE;
454 /* Returns the index of log or diff command or -1 to exit. */
455 static bool
456 parse_options(int argc, char *argv[])
458         int i;
460         for (i = 1; i < argc; i++) {
461                 char *opt = argv[i];
463                 if (!strcmp(opt, "-l")) {
464                         opt_request = REQ_VIEW_LOG;
465                         continue;
466                 }
468                 if (!strcmp(opt, "-d")) {
469                         opt_request = REQ_VIEW_DIFF;
470                         continue;
471                 }
473                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
474                         opt_line_number = TRUE;
475                         continue;
476                 }
478                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
479                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
480                         continue;
481                 }
483                 if (check_option(opt, 'v', "version", OPT_NONE)) {
484                         printf("tig version %s\n", VERSION);
485                         return FALSE;
486                 }
488                 if (check_option(opt, 'h', "help", OPT_NONE)) {
489                         printf(usage);
490                         return FALSE;
491                 }
493                 if (!strcmp(opt, "--")) {
494                         i++;
495                         break;
496                 }
498                 if (!strcmp(opt, "log") ||
499                     !strcmp(opt, "diff") ||
500                     !strcmp(opt, "show")) {
501                         opt_request = opt[0] == 'l'
502                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503                         break;
504                 }
506                 if (opt[0] && opt[0] != '-')
507                         break;
509                 die("unknown option '%s'\n\n%s", opt, usage);
510         }
512         if (!isatty(STDIN_FILENO)) {
513                 opt_request = REQ_VIEW_PAGER;
514                 opt_pipe = stdin;
516         } else if (i < argc) {
517                 size_t buf_size;
519                 if (opt_request == REQ_VIEW_MAIN)
520                         /* XXX: This is vulnerable to the user overriding
521                          * options required for the main view parser. */
522                         string_copy(opt_cmd, "git log --stat --pretty=raw");
523                 else
524                         string_copy(opt_cmd, "git");
525                 buf_size = strlen(opt_cmd);
527                 while (buf_size < sizeof(opt_cmd) && i < argc) {
528                         opt_cmd[buf_size++] = ' ';
529                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
530                 }
532                 if (buf_size >= sizeof(opt_cmd))
533                         die("command too long");
535                 opt_cmd[buf_size] = 0;
537         }
539         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
540                 opt_utf8 = FALSE;
542         return TRUE;
546 /*
547  * Line-oriented content detection.
548  */
550 #define LINE_INFO \
551 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
552 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
553 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
554 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
555 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
556 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
557 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
558 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
559 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
560 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
561 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
562 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
565 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
566 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
568 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
569 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
570 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
571 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
572 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
573 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
575 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
576 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
577 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
578 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
579 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
580 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
581 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
582 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
583 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
584 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
585 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
586 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
587 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
588 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
589 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
590 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
591 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
593 enum line_type {
594 #define LINE(type, line, fg, bg, attr) \
595         LINE_##type
596         LINE_INFO
597 #undef  LINE
598 };
600 struct line_info {
601         const char *name;       /* Option name. */
602         int namelen;            /* Size of option name. */
603         const char *line;       /* The start of line to match. */
604         int linelen;            /* Size of string to match. */
605         int fg, bg, attr;       /* Color and text attributes for the lines. */
606 };
608 static struct line_info line_info[] = {
609 #define LINE(type, line, fg, bg, attr) \
610         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
611         LINE_INFO
612 #undef  LINE
613 };
615 static enum line_type
616 get_line_type(char *line)
618         int linelen = strlen(line);
619         enum line_type type;
621         for (type = 0; type < ARRAY_SIZE(line_info); type++)
622                 /* Case insensitive search matches Signed-off-by lines better. */
623                 if (linelen >= line_info[type].linelen &&
624                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
625                         return type;
627         return LINE_DEFAULT;
630 static inline int
631 get_line_attr(enum line_type type)
633         assert(type < ARRAY_SIZE(line_info));
634         return COLOR_PAIR(type) | line_info[type].attr;
637 static struct line_info *
638 get_line_info(char *name, int namelen)
640         enum line_type type;
642         for (type = 0; type < ARRAY_SIZE(line_info); type++)
643                 if (namelen == line_info[type].namelen &&
644                     !string_enum_compare(line_info[type].name, name, namelen))
645                         return &line_info[type];
647         return NULL;
650 static void
651 init_colors(void)
653         int default_bg = COLOR_BLACK;
654         int default_fg = COLOR_WHITE;
655         enum line_type type;
657         start_color();
659         if (use_default_colors() != ERR) {
660                 default_bg = -1;
661                 default_fg = -1;
662         }
664         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
665                 struct line_info *info = &line_info[type];
666                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
667                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
669                 init_pair(type, fg, bg);
670         }
673 struct line {
674         enum line_type type;
676         /* State flags */
677         unsigned int selected:1;
679         void *data;             /* User data */
680 };
683 /*
684  * Keys
685  */
687 struct keybinding {
688         int alias;
689         enum request request;
690         struct keybinding *next;
691 };
693 static struct keybinding default_keybindings[] = {
694         /* View switching */
695         { 'm',          REQ_VIEW_MAIN },
696         { 'd',          REQ_VIEW_DIFF },
697         { 'l',          REQ_VIEW_LOG },
698         { 't',          REQ_VIEW_TREE },
699         { 'f',          REQ_VIEW_BLOB },
700         { 'p',          REQ_VIEW_PAGER },
701         { 'h',          REQ_VIEW_HELP },
703         /* View manipulation */
704         { 'q',          REQ_VIEW_CLOSE },
705         { KEY_TAB,      REQ_VIEW_NEXT },
706         { KEY_RETURN,   REQ_ENTER },
707         { KEY_UP,       REQ_PREVIOUS },
708         { KEY_DOWN,     REQ_NEXT },
710         /* Cursor navigation */
711         { 'k',          REQ_MOVE_UP },
712         { 'j',          REQ_MOVE_DOWN },
713         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
714         { KEY_END,      REQ_MOVE_LAST_LINE },
715         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
716         { ' ',          REQ_MOVE_PAGE_DOWN },
717         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
718         { 'b',          REQ_MOVE_PAGE_UP },
719         { '-',          REQ_MOVE_PAGE_UP },
721         /* Scrolling */
722         { KEY_IC,       REQ_SCROLL_LINE_UP },
723         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
724         { 'w',          REQ_SCROLL_PAGE_UP },
725         { 's',          REQ_SCROLL_PAGE_DOWN },
727         /* Searching */
728         { '/',          REQ_SEARCH },
729         { '?',          REQ_SEARCH_BACK },
730         { 'n',          REQ_FIND_NEXT },
731         { 'N',          REQ_FIND_PREV },
733         /* Misc */
734         { 'Q',          REQ_QUIT },
735         { 'z',          REQ_STOP_LOADING },
736         { 'v',          REQ_SHOW_VERSION },
737         { 'r',          REQ_SCREEN_REDRAW },
738         { '.',          REQ_TOGGLE_LINENO },
739         { 'g',          REQ_TOGGLE_REV_GRAPH },
740         { ':',          REQ_PROMPT },
742         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
743         { ERR,          REQ_NONE },
745         /* Using the ncurses SIGWINCH handler. */
746         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
747 };
749 #define KEYMAP_INFO \
750         KEYMAP_(GENERIC), \
751         KEYMAP_(MAIN), \
752         KEYMAP_(DIFF), \
753         KEYMAP_(LOG), \
754         KEYMAP_(TREE), \
755         KEYMAP_(BLOB), \
756         KEYMAP_(PAGER), \
757         KEYMAP_(HELP) \
759 enum keymap {
760 #define KEYMAP_(name) KEYMAP_##name
761         KEYMAP_INFO
762 #undef  KEYMAP_
763 };
765 static struct int_map keymap_table[] = {
766 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
767         KEYMAP_INFO
768 #undef  KEYMAP_
769 };
771 #define set_keymap(map, name) \
772         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
774 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
776 static void
777 add_keybinding(enum keymap keymap, enum request request, int key)
779         struct keybinding *keybinding;
781         keybinding = calloc(1, sizeof(*keybinding));
782         if (!keybinding)
783                 die("Failed to allocate keybinding");
785         keybinding->alias = key;
786         keybinding->request = request;
787         keybinding->next = keybindings[keymap];
788         keybindings[keymap] = keybinding;
791 /* Looks for a key binding first in the given map, then in the generic map, and
792  * lastly in the default keybindings. */
793 static enum request
794 get_keybinding(enum keymap keymap, int key)
796         struct keybinding *kbd;
797         int i;
799         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
800                 if (kbd->alias == key)
801                         return kbd->request;
803         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
804                 if (kbd->alias == key)
805                         return kbd->request;
807         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
808                 if (default_keybindings[i].alias == key)
809                         return default_keybindings[i].request;
811         return (enum request) key;
815 struct key {
816         char *name;
817         int value;
818 };
820 static struct key key_table[] = {
821         { "Enter",      KEY_RETURN },
822         { "Space",      ' ' },
823         { "Backspace",  KEY_BACKSPACE },
824         { "Tab",        KEY_TAB },
825         { "Escape",     KEY_ESC },
826         { "Left",       KEY_LEFT },
827         { "Right",      KEY_RIGHT },
828         { "Up",         KEY_UP },
829         { "Down",       KEY_DOWN },
830         { "Insert",     KEY_IC },
831         { "Delete",     KEY_DC },
832         { "Hash",       '#' },
833         { "Home",       KEY_HOME },
834         { "End",        KEY_END },
835         { "PageUp",     KEY_PPAGE },
836         { "PageDown",   KEY_NPAGE },
837         { "F1",         KEY_F(1) },
838         { "F2",         KEY_F(2) },
839         { "F3",         KEY_F(3) },
840         { "F4",         KEY_F(4) },
841         { "F5",         KEY_F(5) },
842         { "F6",         KEY_F(6) },
843         { "F7",         KEY_F(7) },
844         { "F8",         KEY_F(8) },
845         { "F9",         KEY_F(9) },
846         { "F10",        KEY_F(10) },
847         { "F11",        KEY_F(11) },
848         { "F12",        KEY_F(12) },
849 };
851 static int
852 get_key_value(const char *name)
854         int i;
856         for (i = 0; i < ARRAY_SIZE(key_table); i++)
857                 if (!strcasecmp(key_table[i].name, name))
858                         return key_table[i].value;
860         if (strlen(name) == 1 && isprint(*name))
861                 return (int) *name;
863         return ERR;
866 static char *
867 get_key(enum request request)
869         static char buf[BUFSIZ];
870         static char key_char[] = "'X'";
871         size_t pos = 0;
872         char *sep = "    ";
873         int i;
875         buf[pos] = 0;
877         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
878                 struct keybinding *keybinding = &default_keybindings[i];
879                 char *seq = NULL;
880                 int key;
882                 if (keybinding->request != request)
883                         continue;
885                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
886                         if (key_table[key].value == keybinding->alias)
887                                 seq = key_table[key].name;
889                 if (seq == NULL &&
890                     keybinding->alias < 127 &&
891                     isprint(keybinding->alias)) {
892                         key_char[1] = (char) keybinding->alias;
893                         seq = key_char;
894                 }
896                 if (!seq)
897                         seq = "'?'";
899                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
900                         return "Too many keybindings!";
901                 sep = ", ";
902         }
904         return buf;
908 /*
909  * User config file handling.
910  */
912 static struct int_map color_map[] = {
913 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
914         COLOR_MAP(DEFAULT),
915         COLOR_MAP(BLACK),
916         COLOR_MAP(BLUE),
917         COLOR_MAP(CYAN),
918         COLOR_MAP(GREEN),
919         COLOR_MAP(MAGENTA),
920         COLOR_MAP(RED),
921         COLOR_MAP(WHITE),
922         COLOR_MAP(YELLOW),
923 };
925 #define set_color(color, name) \
926         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
928 static struct int_map attr_map[] = {
929 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
930         ATTR_MAP(NORMAL),
931         ATTR_MAP(BLINK),
932         ATTR_MAP(BOLD),
933         ATTR_MAP(DIM),
934         ATTR_MAP(REVERSE),
935         ATTR_MAP(STANDOUT),
936         ATTR_MAP(UNDERLINE),
937 };
939 #define set_attribute(attr, name) \
940         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
942 static int   config_lineno;
943 static bool  config_errors;
944 static char *config_msg;
946 /* Wants: object fgcolor bgcolor [attr] */
947 static int
948 option_color_command(int argc, char *argv[])
950         struct line_info *info;
952         if (argc != 3 && argc != 4) {
953                 config_msg = "Wrong number of arguments given to color command";
954                 return ERR;
955         }
957         info = get_line_info(argv[0], strlen(argv[0]));
958         if (!info) {
959                 config_msg = "Unknown color name";
960                 return ERR;
961         }
963         if (set_color(&info->fg, argv[1]) == ERR ||
964             set_color(&info->bg, argv[2]) == ERR) {
965                 config_msg = "Unknown color";
966                 return ERR;
967         }
969         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
970                 config_msg = "Unknown attribute";
971                 return ERR;
972         }
974         return OK;
977 /* Wants: name = value */
978 static int
979 option_set_command(int argc, char *argv[])
981         if (argc != 3) {
982                 config_msg = "Wrong number of arguments given to set command";
983                 return ERR;
984         }
986         if (strcmp(argv[1], "=")) {
987                 config_msg = "No value assigned";
988                 return ERR;
989         }
991         if (!strcmp(argv[0], "show-rev-graph")) {
992                 opt_rev_graph = (!strcmp(argv[2], "1") ||
993                                  !strcmp(argv[2], "true") ||
994                                  !strcmp(argv[2], "yes"));
995                 return OK;
996         }
998         if (!strcmp(argv[0], "line-number-interval")) {
999                 opt_num_interval = atoi(argv[2]);
1000                 return OK;
1001         }
1003         if (!strcmp(argv[0], "tab-size")) {
1004                 opt_tab_size = atoi(argv[2]);
1005                 return OK;
1006         }
1008         if (!strcmp(argv[0], "commit-encoding")) {
1009                 char *arg = argv[2];
1010                 int delimiter = *arg;
1011                 int i;
1013                 switch (delimiter) {
1014                 case '"':
1015                 case '\'':
1016                         for (arg++, i = 0; arg[i]; i++)
1017                                 if (arg[i] == delimiter) {
1018                                         arg[i] = 0;
1019                                         break;
1020                                 }
1021                 default:
1022                         string_copy(opt_encoding, arg);
1023                         return OK;
1024                 }
1025         }
1027         config_msg = "Unknown variable name";
1028         return ERR;
1031 /* Wants: mode request key */
1032 static int
1033 option_bind_command(int argc, char *argv[])
1035         enum request request;
1036         int keymap;
1037         int key;
1039         if (argc != 3) {
1040                 config_msg = "Wrong number of arguments given to bind command";
1041                 return ERR;
1042         }
1044         if (set_keymap(&keymap, argv[0]) == ERR) {
1045                 config_msg = "Unknown key map";
1046                 return ERR;
1047         }
1049         key = get_key_value(argv[1]);
1050         if (key == ERR) {
1051                 config_msg = "Unknown key";
1052                 return ERR;
1053         }
1055         request = get_request(argv[2]);
1056         if (request == REQ_UNKNOWN) {
1057                 config_msg = "Unknown request name";
1058                 return ERR;
1059         }
1061         add_keybinding(keymap, request, key);
1063         return OK;
1066 static int
1067 set_option(char *opt, char *value)
1069         char *argv[16];
1070         int valuelen;
1071         int argc = 0;
1073         /* Tokenize */
1074         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1075                 argv[argc++] = value;
1077                 value += valuelen;
1078                 if (!*value)
1079                         break;
1081                 *value++ = 0;
1082                 while (isspace(*value))
1083                         value++;
1084         }
1086         if (!strcmp(opt, "color"))
1087                 return option_color_command(argc, argv);
1089         if (!strcmp(opt, "set"))
1090                 return option_set_command(argc, argv);
1092         if (!strcmp(opt, "bind"))
1093                 return option_bind_command(argc, argv);
1095         config_msg = "Unknown option command";
1096         return ERR;
1099 static int
1100 read_option(char *opt, int optlen, char *value, int valuelen)
1102         int status = OK;
1104         config_lineno++;
1105         config_msg = "Internal error";
1107         /* Check for comment markers, since read_properties() will
1108          * only ensure opt and value are split at first " \t". */
1109         optlen = strcspn(opt, "#");
1110         if (optlen == 0)
1111                 return OK;
1113         if (opt[optlen] != 0) {
1114                 config_msg = "No option value";
1115                 status = ERR;
1117         }  else {
1118                 /* Look for comment endings in the value. */
1119                 int len = strcspn(value, "#");
1121                 if (len < valuelen) {
1122                         valuelen = len;
1123                         value[valuelen] = 0;
1124                 }
1126                 status = set_option(opt, value);
1127         }
1129         if (status == ERR) {
1130                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1131                         config_lineno, optlen, opt, config_msg);
1132                 config_errors = TRUE;
1133         }
1135         /* Always keep going if errors are encountered. */
1136         return OK;
1139 static int
1140 load_options(void)
1142         char *home = getenv("HOME");
1143         char buf[SIZEOF_STR];
1144         FILE *file;
1146         config_lineno = 0;
1147         config_errors = FALSE;
1149         if (!home || !string_format(buf, "%s/.tigrc", home))
1150                 return ERR;
1152         /* It's ok that the file doesn't exist. */
1153         file = fopen(buf, "r");
1154         if (!file)
1155                 return OK;
1157         if (read_properties(file, " \t", read_option) == ERR ||
1158             config_errors == TRUE)
1159                 fprintf(stderr, "Errors while loading %s.\n", buf);
1161         return OK;
1165 /*
1166  * The viewer
1167  */
1169 struct view;
1170 struct view_ops;
1172 /* The display array of active views and the index of the current view. */
1173 static struct view *display[2];
1174 static unsigned int current_view;
1176 #define foreach_displayed_view(view, i) \
1177         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1179 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1181 /* Current head and commit ID */
1182 static char ref_blob[SIZEOF_REF]        = "";
1183 static char ref_commit[SIZEOF_REF]      = "HEAD";
1184 static char ref_head[SIZEOF_REF]        = "HEAD";
1186 struct view {
1187         const char *name;       /* View name */
1188         const char *cmd_fmt;    /* Default command line format */
1189         const char *cmd_env;    /* Command line set via environment */
1190         const char *id;         /* Points to either of ref_{head,commit,blob} */
1192         struct view_ops *ops;   /* View operations */
1194         enum keymap keymap;     /* What keymap does this view have */
1196         char cmd[SIZEOF_STR];   /* Command buffer */
1197         char ref[SIZEOF_REF];   /* Hovered commit reference */
1198         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1200         int height, width;      /* The width and height of the main window */
1201         WINDOW *win;            /* The main window */
1202         WINDOW *title;          /* The title window living below the main window */
1204         /* Navigation */
1205         unsigned long offset;   /* Offset of the window top */
1206         unsigned long lineno;   /* Current line number */
1208         /* Searching */
1209         char grep[SIZEOF_STR];  /* Search string */
1210         regex_t *regex;         /* Pre-compiled regex */
1212         /* If non-NULL, points to the view that opened this view. If this view
1213          * is closed tig will switch back to the parent view. */
1214         struct view *parent;
1216         /* Buffering */
1217         unsigned long lines;    /* Total number of lines */
1218         struct line *line;      /* Line index */
1219         unsigned long line_size;/* Total number of allocated lines */
1220         unsigned int digits;    /* Number of digits in the lines member. */
1222         /* Loading */
1223         FILE *pipe;
1224         time_t start_time;
1225 };
1227 struct view_ops {
1228         /* What type of content being displayed. Used in the title bar. */
1229         const char *type;
1230         /* Draw one line; @lineno must be < view->height. */
1231         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1232         /* Read one line; updates view->line. */
1233         bool (*read)(struct view *view, char *data);
1234         /* Depending on view, change display based on current line. */
1235         bool (*enter)(struct view *view, struct line *line);
1236         /* Search for regex in a line. */
1237         bool (*grep)(struct view *view, struct line *line);
1238         /* Select line */
1239         void (*select)(struct view *view, struct line *line);
1240 };
1242 static struct view_ops pager_ops;
1243 static struct view_ops main_ops;
1244 static struct view_ops tree_ops;
1245 static struct view_ops blob_ops;
1247 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1248         { name, cmd, #env, ref, ops, map}
1250 #define VIEW_(id, name, ops, ref) \
1251         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1254 static struct view views[] = {
1255         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1256         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1257         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1258         VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
1259         VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
1260         VIEW_(HELP,  "help",  &pager_ops, "static"),
1261         VIEW_(PAGER, "pager", &pager_ops, "static"),
1262 };
1264 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1266 #define foreach_view(view, i) \
1267         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1269 #define view_is_displayed(view) \
1270         (view == display[0] || view == display[1])
1272 static bool
1273 draw_view_line(struct view *view, unsigned int lineno)
1275         struct line *line;
1276         bool selected = (view->offset + lineno == view->lineno);
1277         bool draw_ok;
1279         assert(view_is_displayed(view));
1281         if (view->offset + lineno >= view->lines)
1282                 return FALSE;
1284         line = &view->line[view->offset + lineno];
1286         if (selected) {
1287                 line->selected = TRUE;
1288                 view->ops->select(view, line);
1289         } else if (line->selected) {
1290                 line->selected = FALSE;
1291                 wmove(view->win, lineno, 0);
1292                 wclrtoeol(view->win);
1293         }
1295         scrollok(view->win, FALSE);
1296         draw_ok = view->ops->draw(view, line, lineno, selected);
1297         scrollok(view->win, TRUE);
1299         return draw_ok;
1302 static void
1303 redraw_view_from(struct view *view, int lineno)
1305         assert(0 <= lineno && lineno < view->height);
1307         for (; lineno < view->height; lineno++) {
1308                 if (!draw_view_line(view, lineno))
1309                         break;
1310         }
1312         redrawwin(view->win);
1313         wrefresh(view->win);
1316 static void
1317 redraw_view(struct view *view)
1319         wclear(view->win);
1320         redraw_view_from(view, 0);
1324 static void
1325 update_view_title(struct view *view)
1327         char buf[SIZEOF_STR];
1328         char state[SIZEOF_STR];
1329         size_t bufpos = 0, statelen = 0;
1331         assert(view_is_displayed(view));
1333         if (view->lines || view->pipe) {
1334                 unsigned int view_lines = view->offset + view->height;
1335                 unsigned int lines = view->lines
1336                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1337                                    : 0;
1339                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1340                                    view->ops->type,
1341                                    view->lineno + 1,
1342                                    view->lines,
1343                                    lines);
1345                 if (view->pipe) {
1346                         time_t secs = time(NULL) - view->start_time;
1348                         /* Three git seconds are a long time ... */
1349                         if (secs > 2)
1350                                 string_format_from(state, &statelen, " %lds", secs);
1351                 }
1352         }
1354         string_format_from(buf, &bufpos, "[%s]", view->name);
1355         if (*view->ref && bufpos < view->width) {
1356                 size_t refsize = strlen(view->ref);
1357                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1359                 if (minsize < view->width)
1360                         refsize = view->width - minsize + 7;
1361                 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1362         }
1364         if (statelen && bufpos < view->width) {
1365                 string_format_from(buf, &bufpos, " %s", state);
1366         }
1368         if (view == display[current_view])
1369                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1370         else
1371                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1373         werase(view->title);
1374         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1375         wmove(view->title, 0, view->width - 1);
1376         wrefresh(view->title);
1379 static void
1380 resize_display(void)
1382         int offset, i;
1383         struct view *base = display[0];
1384         struct view *view = display[1] ? display[1] : display[0];
1386         /* Setup window dimensions */
1388         getmaxyx(stdscr, base->height, base->width);
1390         /* Make room for the status window. */
1391         base->height -= 1;
1393         if (view != base) {
1394                 /* Horizontal split. */
1395                 view->width   = base->width;
1396                 view->height  = SCALE_SPLIT_VIEW(base->height);
1397                 base->height -= view->height;
1399                 /* Make room for the title bar. */
1400                 view->height -= 1;
1401         }
1403         /* Make room for the title bar. */
1404         base->height -= 1;
1406         offset = 0;
1408         foreach_displayed_view (view, i) {
1409                 if (!view->win) {
1410                         view->win = newwin(view->height, 0, offset, 0);
1411                         if (!view->win)
1412                                 die("Failed to create %s view", view->name);
1414                         scrollok(view->win, TRUE);
1416                         view->title = newwin(1, 0, offset + view->height, 0);
1417                         if (!view->title)
1418                                 die("Failed to create title window");
1420                 } else {
1421                         wresize(view->win, view->height, view->width);
1422                         mvwin(view->win,   offset, 0);
1423                         mvwin(view->title, offset + view->height, 0);
1424                 }
1426                 offset += view->height + 1;
1427         }
1430 static void
1431 redraw_display(void)
1433         struct view *view;
1434         int i;
1436         foreach_displayed_view (view, i) {
1437                 redraw_view(view);
1438                 update_view_title(view);
1439         }
1442 static void
1443 update_display_cursor(struct view *view)
1445         /* Move the cursor to the right-most column of the cursor line.
1446          *
1447          * XXX: This could turn out to be a bit expensive, but it ensures that
1448          * the cursor does not jump around. */
1449         if (view->lines) {
1450                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1451                 wrefresh(view->win);
1452         }
1455 /*
1456  * Navigation
1457  */
1459 /* Scrolling backend */
1460 static void
1461 do_scroll_view(struct view *view, int lines)
1463         bool redraw_current_line = FALSE;
1465         /* The rendering expects the new offset. */
1466         view->offset += lines;
1468         assert(0 <= view->offset && view->offset < view->lines);
1469         assert(lines);
1471         /* Move current line into the view. */
1472         if (view->lineno < view->offset) {
1473                 view->lineno = view->offset;
1474                 redraw_current_line = TRUE;
1475         } else if (view->lineno >= view->offset + view->height) {
1476                 view->lineno = view->offset + view->height - 1;
1477                 redraw_current_line = TRUE;
1478         }
1480         assert(view->offset <= view->lineno && view->lineno < view->lines);
1482         /* Redraw the whole screen if scrolling is pointless. */
1483         if (view->height < ABS(lines)) {
1484                 redraw_view(view);
1486         } else {
1487                 int line = lines > 0 ? view->height - lines : 0;
1488                 int end = line + ABS(lines);
1490                 wscrl(view->win, lines);
1492                 for (; line < end; line++) {
1493                         if (!draw_view_line(view, line))
1494                                 break;
1495                 }
1497                 if (redraw_current_line)
1498                         draw_view_line(view, view->lineno - view->offset);
1499         }
1501         redrawwin(view->win);
1502         wrefresh(view->win);
1503         report("");
1506 /* Scroll frontend */
1507 static void
1508 scroll_view(struct view *view, enum request request)
1510         int lines = 1;
1512         assert(view_is_displayed(view));
1514         switch (request) {
1515         case REQ_SCROLL_PAGE_DOWN:
1516                 lines = view->height;
1517         case REQ_SCROLL_LINE_DOWN:
1518                 if (view->offset + lines > view->lines)
1519                         lines = view->lines - view->offset;
1521                 if (lines == 0 || view->offset + view->height >= view->lines) {
1522                         report("Cannot scroll beyond the last line");
1523                         return;
1524                 }
1525                 break;
1527         case REQ_SCROLL_PAGE_UP:
1528                 lines = view->height;
1529         case REQ_SCROLL_LINE_UP:
1530                 if (lines > view->offset)
1531                         lines = view->offset;
1533                 if (lines == 0) {
1534                         report("Cannot scroll beyond the first line");
1535                         return;
1536                 }
1538                 lines = -lines;
1539                 break;
1541         default:
1542                 die("request %d not handled in switch", request);
1543         }
1545         do_scroll_view(view, lines);
1548 /* Cursor moving */
1549 static void
1550 move_view(struct view *view, enum request request)
1552         int scroll_steps = 0;
1553         int steps;
1555         switch (request) {
1556         case REQ_MOVE_FIRST_LINE:
1557                 steps = -view->lineno;
1558                 break;
1560         case REQ_MOVE_LAST_LINE:
1561                 steps = view->lines - view->lineno - 1;
1562                 break;
1564         case REQ_MOVE_PAGE_UP:
1565                 steps = view->height > view->lineno
1566                       ? -view->lineno : -view->height;
1567                 break;
1569         case REQ_MOVE_PAGE_DOWN:
1570                 steps = view->lineno + view->height >= view->lines
1571                       ? view->lines - view->lineno - 1 : view->height;
1572                 break;
1574         case REQ_MOVE_UP:
1575                 steps = -1;
1576                 break;
1578         case REQ_MOVE_DOWN:
1579                 steps = 1;
1580                 break;
1582         default:
1583                 die("request %d not handled in switch", request);
1584         }
1586         if (steps <= 0 && view->lineno == 0) {
1587                 report("Cannot move beyond the first line");
1588                 return;
1590         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1591                 report("Cannot move beyond the last line");
1592                 return;
1593         }
1595         /* Move the current line */
1596         view->lineno += steps;
1597         assert(0 <= view->lineno && view->lineno < view->lines);
1599         /* Check whether the view needs to be scrolled */
1600         if (view->lineno < view->offset ||
1601             view->lineno >= view->offset + view->height) {
1602                 scroll_steps = steps;
1603                 if (steps < 0 && -steps > view->offset) {
1604                         scroll_steps = -view->offset;
1606                 } else if (steps > 0) {
1607                         if (view->lineno == view->lines - 1 &&
1608                             view->lines > view->height) {
1609                                 scroll_steps = view->lines - view->offset - 1;
1610                                 if (scroll_steps >= view->height)
1611                                         scroll_steps -= view->height - 1;
1612                         }
1613                 }
1614         }
1616         if (!view_is_displayed(view)) {
1617                 view->offset += steps;
1618                 view->ops->select(view, &view->line[view->lineno]);
1619                 return;
1620         }
1622         /* Repaint the old "current" line if we be scrolling */
1623         if (ABS(steps) < view->height)
1624                 draw_view_line(view, view->lineno - steps - view->offset);
1626         if (scroll_steps) {
1627                 do_scroll_view(view, scroll_steps);
1628                 return;
1629         }
1631         /* Draw the current line */
1632         draw_view_line(view, view->lineno - view->offset);
1634         redrawwin(view->win);
1635         wrefresh(view->win);
1636         report("");
1640 /*
1641  * Searching
1642  */
1644 static void search_view(struct view *view, enum request request);
1646 static bool
1647 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1649         assert(view_is_displayed(view));
1651         if (!view->ops->grep(view, line))
1652                 return FALSE;
1654         if (lineno - view->offset >= view->height) {
1655                 view->offset = lineno;
1656                 view->lineno = lineno;
1657                 redraw_view(view);
1659         } else {
1660                 unsigned long old_lineno = view->lineno - view->offset;
1662                 view->lineno = lineno;
1663                 draw_view_line(view, old_lineno);
1665                 draw_view_line(view, view->lineno - view->offset);
1666                 redrawwin(view->win);
1667                 wrefresh(view->win);
1668         }
1670         report("Line %ld matches '%s'", lineno + 1, view->grep);
1671         return TRUE;
1674 static void
1675 find_next(struct view *view, enum request request)
1677         unsigned long lineno = view->lineno;
1678         int direction;
1680         if (!*view->grep) {
1681                 if (!*opt_search)
1682                         report("No previous search");
1683                 else
1684                         search_view(view, request);
1685                 return;
1686         }
1688         switch (request) {
1689         case REQ_SEARCH:
1690         case REQ_FIND_NEXT:
1691                 direction = 1;
1692                 break;
1694         case REQ_SEARCH_BACK:
1695         case REQ_FIND_PREV:
1696                 direction = -1;
1697                 break;
1699         default:
1700                 return;
1701         }
1703         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1704                 lineno += direction;
1706         /* Note, lineno is unsigned long so will wrap around in which case it
1707          * will become bigger than view->lines. */
1708         for (; lineno < view->lines; lineno += direction) {
1709                 struct line *line = &view->line[lineno];
1711                 if (find_next_line(view, lineno, line))
1712                         return;
1713         }
1715         report("No match found for '%s'", view->grep);
1718 static void
1719 search_view(struct view *view, enum request request)
1721         int regex_err;
1723         if (view->regex) {
1724                 regfree(view->regex);
1725                 *view->grep = 0;
1726         } else {
1727                 view->regex = calloc(1, sizeof(*view->regex));
1728                 if (!view->regex)
1729                         return;
1730         }
1732         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1733         if (regex_err != 0) {
1734                 char buf[SIZEOF_STR] = "unknown error";
1736                 regerror(regex_err, view->regex, buf, sizeof(buf));
1737                 report("Search failed: %s", buf);
1738                 return;
1739         }
1741         string_copy(view->grep, opt_search);
1743         find_next(view, request);
1746 /*
1747  * Incremental updating
1748  */
1750 static void
1751 end_update(struct view *view)
1753         if (!view->pipe)
1754                 return;
1755         set_nonblocking_input(FALSE);
1756         if (view->pipe == stdin)
1757                 fclose(view->pipe);
1758         else
1759                 pclose(view->pipe);
1760         view->pipe = NULL;
1763 static bool
1764 begin_update(struct view *view)
1766         const char *id = view->id;
1768         if (view->pipe)
1769                 end_update(view);
1771         if (opt_cmd[0]) {
1772                 string_copy(view->cmd, opt_cmd);
1773                 opt_cmd[0] = 0;
1774                 /* When running random commands, the view ref could have become
1775                  * invalid so clear it. */
1776                 view->ref[0] = 0;
1778         } else if (view == VIEW(REQ_VIEW_TREE)) {
1779                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1781                 if (strcmp(view->vid, view->id))
1782                         opt_path[0] = 0;
1784                 if (!string_format(view->cmd, format, id, opt_path))
1785                         return FALSE;
1787         } else {
1788                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1790                 if (!string_format(view->cmd, format, id, id, id, id, id))
1791                         return FALSE;
1792         }
1794         /* Special case for the pager view. */
1795         if (opt_pipe) {
1796                 view->pipe = opt_pipe;
1797                 opt_pipe = NULL;
1798         } else {
1799                 view->pipe = popen(view->cmd, "r");
1800         }
1802         if (!view->pipe)
1803                 return FALSE;
1805         set_nonblocking_input(TRUE);
1807         view->offset = 0;
1808         view->lines  = 0;
1809         view->lineno = 0;
1810         string_copy(view->vid, id);
1812         if (view->line) {
1813                 int i;
1815                 for (i = 0; i < view->lines; i++)
1816                         if (view->line[i].data)
1817                                 free(view->line[i].data);
1819                 free(view->line);
1820                 view->line = NULL;
1821         }
1823         view->start_time = time(NULL);
1825         return TRUE;
1828 static struct line *
1829 realloc_lines(struct view *view, size_t line_size)
1831         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1833         if (!tmp)
1834                 return NULL;
1836         view->line = tmp;
1837         view->line_size = line_size;
1838         return view->line;
1841 static bool
1842 update_view(struct view *view)
1844         char in_buffer[BUFSIZ];
1845         char out_buffer[BUFSIZ * 2];
1846         char *line;
1847         /* The number of lines to read. If too low it will cause too much
1848          * redrawing (and possible flickering), if too high responsiveness
1849          * will suffer. */
1850         unsigned long lines = view->height;
1851         int redraw_from = -1;
1853         if (!view->pipe)
1854                 return TRUE;
1856         /* Only redraw if lines are visible. */
1857         if (view->offset + view->height >= view->lines)
1858                 redraw_from = view->lines - view->offset;
1860         /* FIXME: This is probably not perfect for backgrounded views. */
1861         if (!realloc_lines(view, view->lines + lines))
1862                 goto alloc_error;
1864         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1865                 size_t linelen = strlen(line);
1867                 if (linelen)
1868                         line[linelen - 1] = 0;
1870                 if (opt_iconv != ICONV_NONE) {
1871                         char *inbuf = line;
1872                         size_t inlen = linelen;
1874                         char *outbuf = out_buffer;
1875                         size_t outlen = sizeof(out_buffer);
1877                         size_t ret;
1879                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1880                         if (ret != (size_t) -1) {
1881                                 line = out_buffer;
1882                                 linelen = strlen(out_buffer);
1883                         }
1884                 }
1886                 if (!view->ops->read(view, line))
1887                         goto alloc_error;
1889                 if (lines-- == 1)
1890                         break;
1891         }
1893         {
1894                 int digits;
1896                 lines = view->lines;
1897                 for (digits = 0; lines; digits++)
1898                         lines /= 10;
1900                 /* Keep the displayed view in sync with line number scaling. */
1901                 if (digits != view->digits) {
1902                         view->digits = digits;
1903                         redraw_from = 0;
1904                 }
1905         }
1907         if (!view_is_displayed(view))
1908                 goto check_pipe;
1910         if (view == VIEW(REQ_VIEW_TREE)) {
1911                 /* Clear the view and redraw everything since the tree sorting
1912                  * might have rearranged things. */
1913                 redraw_view(view);
1915         } else if (redraw_from >= 0) {
1916                 /* If this is an incremental update, redraw the previous line
1917                  * since for commits some members could have changed when
1918                  * loading the main view. */
1919                 if (redraw_from > 0)
1920                         redraw_from--;
1922                 /* Incrementally draw avoids flickering. */
1923                 redraw_view_from(view, redraw_from);
1924         }
1926         /* Update the title _after_ the redraw so that if the redraw picks up a
1927          * commit reference in view->ref it'll be available here. */
1928         update_view_title(view);
1930 check_pipe:
1931         if (ferror(view->pipe)) {
1932                 report("Failed to read: %s", strerror(errno));
1933                 goto end;
1935         } else if (feof(view->pipe)) {
1936                 report("");
1937                 goto end;
1938         }
1940         return TRUE;
1942 alloc_error:
1943         report("Allocation failure");
1945 end:
1946         view->ops->read(view, NULL);
1947         end_update(view);
1948         return FALSE;
1952 /*
1953  * View opening
1954  */
1956 static void open_help_view(struct view *view)
1958         char buf[BUFSIZ];
1959         int lines = ARRAY_SIZE(req_info) + 2;
1960         int i;
1962         if (view->lines > 0)
1963                 return;
1965         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1966                 if (!req_info[i].request)
1967                         lines++;
1969         view->line = calloc(lines, sizeof(*view->line));
1970         if (!view->line) {
1971                 report("Allocation failure");
1972                 return;
1973         }
1975         view->ops->read(view, "Quick reference for tig keybindings:");
1977         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1978                 char *key;
1980                 if (!req_info[i].request) {
1981                         view->ops->read(view, "");
1982                         view->ops->read(view, req_info[i].help);
1983                         continue;
1984                 }
1986                 key = get_key(req_info[i].request);
1987                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1988                         continue;
1990                 view->ops->read(view, buf);
1991         }
1994 enum open_flags {
1995         OPEN_DEFAULT = 0,       /* Use default view switching. */
1996         OPEN_SPLIT = 1,         /* Split current view. */
1997         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1998         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1999 };
2001 static void
2002 open_view(struct view *prev, enum request request, enum open_flags flags)
2004         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2005         bool split = !!(flags & OPEN_SPLIT);
2006         bool reload = !!(flags & OPEN_RELOAD);
2007         struct view *view = VIEW(request);
2008         int nviews = displayed_views();
2009         struct view *base_view = display[0];
2011         if (view == prev && nviews == 1 && !reload) {
2012                 report("Already in %s view", view->name);
2013                 return;
2014         }
2016         if (view == VIEW(REQ_VIEW_HELP)) {
2017                 open_help_view(view);
2019         } else if ((reload || strcmp(view->vid, view->id)) &&
2020                    !begin_update(view)) {
2021                 report("Failed to load %s view", view->name);
2022                 return;
2023         }
2025         if (split) {
2026                 display[1] = view;
2027                 if (!backgrounded)
2028                         current_view = 1;
2029         } else {
2030                 /* Maximize the current view. */
2031                 memset(display, 0, sizeof(display));
2032                 current_view = 0;
2033                 display[current_view] = view;
2034         }
2036         /* Resize the view when switching between split- and full-screen,
2037          * or when switching between two different full-screen views. */
2038         if (nviews != displayed_views() ||
2039             (nviews == 1 && base_view != display[0]))
2040                 resize_display();
2042         if (split && prev->lineno - prev->offset >= prev->height) {
2043                 /* Take the title line into account. */
2044                 int lines = prev->lineno - prev->offset - prev->height + 1;
2046                 /* Scroll the view that was split if the current line is
2047                  * outside the new limited view. */
2048                 do_scroll_view(prev, lines);
2049         }
2051         if (prev && view != prev) {
2052                 if (split && !backgrounded) {
2053                         /* "Blur" the previous view. */
2054                         update_view_title(prev);
2055                 }
2057                 view->parent = prev;
2058         }
2060         if (view->pipe && view->lines == 0) {
2061                 /* Clear the old view and let the incremental updating refill
2062                  * the screen. */
2063                 wclear(view->win);
2064                 report("");
2065         } else {
2066                 redraw_view(view);
2067                 report("");
2068         }
2070         /* If the view is backgrounded the above calls to report()
2071          * won't redraw the view title. */
2072         if (backgrounded)
2073                 update_view_title(view);
2077 /*
2078  * User request switch noodle
2079  */
2081 static int
2082 view_driver(struct view *view, enum request request)
2084         int i;
2086         switch (request) {
2087         case REQ_MOVE_UP:
2088         case REQ_MOVE_DOWN:
2089         case REQ_MOVE_PAGE_UP:
2090         case REQ_MOVE_PAGE_DOWN:
2091         case REQ_MOVE_FIRST_LINE:
2092         case REQ_MOVE_LAST_LINE:
2093                 move_view(view, request);
2094                 break;
2096         case REQ_SCROLL_LINE_DOWN:
2097         case REQ_SCROLL_LINE_UP:
2098         case REQ_SCROLL_PAGE_DOWN:
2099         case REQ_SCROLL_PAGE_UP:
2100                 scroll_view(view, request);
2101                 break;
2103         case REQ_VIEW_BLOB:
2104                 if (!ref_blob[0]) {
2105                         report("No file chosen, press 't' to open tree view");
2106                         break;
2107                 }
2108                 /* Fall-through */
2109         case REQ_VIEW_MAIN:
2110         case REQ_VIEW_DIFF:
2111         case REQ_VIEW_LOG:
2112         case REQ_VIEW_TREE:
2113         case REQ_VIEW_HELP:
2114         case REQ_VIEW_PAGER:
2115                 open_view(view, request, OPEN_DEFAULT);
2116                 break;
2118         case REQ_NEXT:
2119         case REQ_PREVIOUS:
2120                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2122                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2123                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2124                    (view == VIEW(REQ_VIEW_BLOB) &&
2125                      view->parent == VIEW(REQ_VIEW_TREE))) {
2126                         view = view->parent;
2127                         move_view(view, request);
2128                         if (view_is_displayed(view))
2129                                 update_view_title(view);
2130                 } else {
2131                         move_view(view, request);
2132                         break;
2133                 }
2134                 /* Fall-through */
2136         case REQ_ENTER:
2137                 if (!view->lines) {
2138                         report("Nothing to enter");
2139                         break;
2140                 }
2141                 return view->ops->enter(view, &view->line[view->lineno]);
2143         case REQ_VIEW_NEXT:
2144         {
2145                 int nviews = displayed_views();
2146                 int next_view = (current_view + 1) % nviews;
2148                 if (next_view == current_view) {
2149                         report("Only one view is displayed");
2150                         break;
2151                 }
2153                 current_view = next_view;
2154                 /* Blur out the title of the previous view. */
2155                 update_view_title(view);
2156                 report("");
2157                 break;
2158         }
2159         case REQ_TOGGLE_LINENO:
2160                 opt_line_number = !opt_line_number;
2161                 redraw_display();
2162                 break;
2164         case REQ_TOGGLE_REV_GRAPH:
2165                 opt_rev_graph = !opt_rev_graph;
2166                 redraw_display();
2167                 break;
2169         case REQ_PROMPT:
2170                 /* Always reload^Wrerun commands from the prompt. */
2171                 open_view(view, opt_request, OPEN_RELOAD);
2172                 break;
2174         case REQ_SEARCH:
2175         case REQ_SEARCH_BACK:
2176                 search_view(view, request);
2177                 break;
2179         case REQ_FIND_NEXT:
2180         case REQ_FIND_PREV:
2181                 find_next(view, request);
2182                 break;
2184         case REQ_STOP_LOADING:
2185                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2186                         view = &views[i];
2187                         if (view->pipe)
2188                                 report("Stopped loading the %s view", view->name),
2189                         end_update(view);
2190                 }
2191                 break;
2193         case REQ_SHOW_VERSION:
2194                 report("%s (built %s)", VERSION, __DATE__);
2195                 return TRUE;
2197         case REQ_SCREEN_RESIZE:
2198                 resize_display();
2199                 /* Fall-through */
2200         case REQ_SCREEN_REDRAW:
2201                 redraw_display();
2202                 break;
2204         case REQ_NONE:
2205                 doupdate();
2206                 return TRUE;
2208         case REQ_VIEW_CLOSE:
2209                 /* XXX: Mark closed views by letting view->parent point to the
2210                  * view itself. Parents to closed view should never be
2211                  * followed. */
2212                 if (view->parent &&
2213                     view->parent->parent != view->parent) {
2214                         memset(display, 0, sizeof(display));
2215                         current_view = 0;
2216                         display[current_view] = view->parent;
2217                         view->parent = view;
2218                         resize_display();
2219                         redraw_display();
2220                         break;
2221                 }
2222                 /* Fall-through */
2223         case REQ_QUIT:
2224                 return FALSE;
2226         default:
2227                 /* An unknown key will show most commonly used commands. */
2228                 report("Unknown key, press 'h' for help");
2229                 return TRUE;
2230         }
2232         return TRUE;
2236 /*
2237  * Pager backend
2238  */
2240 static bool
2241 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2243         char *text = line->data;
2244         enum line_type type = line->type;
2245         int textlen = strlen(text);
2246         int attr;
2248         wmove(view->win, lineno, 0);
2250         if (selected) {
2251                 type = LINE_CURSOR;
2252                 wchgat(view->win, -1, 0, type, NULL);
2253         }
2255         attr = get_line_attr(type);
2256         wattrset(view->win, attr);
2258         if (opt_line_number || opt_tab_size < TABSIZE) {
2259                 static char spaces[] = "                    ";
2260                 int col_offset = 0, col = 0;
2262                 if (opt_line_number) {
2263                         unsigned long real_lineno = view->offset + lineno + 1;
2265                         if (real_lineno == 1 ||
2266                             (real_lineno % opt_num_interval) == 0) {
2267                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2269                         } else {
2270                                 waddnstr(view->win, spaces,
2271                                          MIN(view->digits, STRING_SIZE(spaces)));
2272                         }
2273                         waddstr(view->win, ": ");
2274                         col_offset = view->digits + 2;
2275                 }
2277                 while (text && col_offset + col < view->width) {
2278                         int cols_max = view->width - col_offset - col;
2279                         char *pos = text;
2280                         int cols;
2282                         if (*text == '\t') {
2283                                 text++;
2284                                 assert(sizeof(spaces) > TABSIZE);
2285                                 pos = spaces;
2286                                 cols = opt_tab_size - (col % opt_tab_size);
2288                         } else {
2289                                 text = strchr(text, '\t');
2290                                 cols = line ? text - pos : strlen(pos);
2291                         }
2293                         waddnstr(view->win, pos, MIN(cols, cols_max));
2294                         col += cols;
2295                 }
2297         } else {
2298                 int col = 0, pos = 0;
2300                 for (; pos < textlen && col < view->width; pos++, col++)
2301                         if (text[pos] == '\t')
2302                                 col += TABSIZE - (col % TABSIZE) - 1;
2304                 waddnstr(view->win, text, pos);
2305         }
2307         return TRUE;
2310 static bool
2311 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2313         char refbuf[SIZEOF_STR];
2314         char *ref = NULL;
2315         FILE *pipe;
2317         if (!string_format(refbuf, "git describe %s", commit_id))
2318                 return TRUE;
2320         pipe = popen(refbuf, "r");
2321         if (!pipe)
2322                 return TRUE;
2324         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2325                 ref = chomp_string(ref);
2326         pclose(pipe);
2328         if (!ref || !*ref)
2329                 return TRUE;
2331         /* This is the only fatal call, since it can "corrupt" the buffer. */
2332         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2333                 return FALSE;
2335         return TRUE;
2338 static void
2339 add_pager_refs(struct view *view, struct line *line)
2341         char buf[SIZEOF_STR];
2342         char *commit_id = line->data + STRING_SIZE("commit ");
2343         struct ref **refs;
2344         size_t bufpos = 0, refpos = 0;
2345         const char *sep = "Refs: ";
2346         bool is_tag = FALSE;
2348         assert(line->type == LINE_COMMIT);
2350         refs = get_refs(commit_id);
2351         if (!refs) {
2352                 if (view == VIEW(REQ_VIEW_DIFF))
2353                         goto try_add_describe_ref;
2354                 return;
2355         }
2357         do {
2358                 struct ref *ref = refs[refpos];
2359                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2361                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2362                         return;
2363                 sep = ", ";
2364                 if (ref->tag)
2365                         is_tag = TRUE;
2366         } while (refs[refpos++]->next);
2368         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2369 try_add_describe_ref:
2370                 /* Add <tag>-g<commit_id> "fake" reference. */
2371                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2372                         return;
2373         }
2375         if (bufpos == 0)
2376                 return;
2378         if (!realloc_lines(view, view->line_size + 1))
2379                 return;
2381         line = &view->line[view->lines];
2382         line->data = strdup(buf);
2383         if (!line->data)
2384                 return;
2386         line->type = LINE_PP_REFS;
2387         view->lines++;
2390 static bool
2391 pager_read(struct view *view, char *data)
2393         struct line *line = &view->line[view->lines];
2395         if (!data)
2396                 return TRUE;
2398         line->data = strdup(data);
2399         if (!line->data)
2400                 return FALSE;
2402         line->type = get_line_type(line->data);
2403         view->lines++;
2405         if (line->type == LINE_COMMIT &&
2406             (view == VIEW(REQ_VIEW_DIFF) ||
2407              view == VIEW(REQ_VIEW_LOG)))
2408                 add_pager_refs(view, line);
2410         return TRUE;
2413 static bool
2414 pager_enter(struct view *view, struct line *line)
2416         int split = 0;
2418         if (line->type == LINE_COMMIT &&
2419            (view == VIEW(REQ_VIEW_LOG) ||
2420             view == VIEW(REQ_VIEW_PAGER))) {
2421                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2422                 split = 1;
2423         }
2425         /* Always scroll the view even if it was split. That way
2426          * you can use Enter to scroll through the log view and
2427          * split open each commit diff. */
2428         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2430         /* FIXME: A minor workaround. Scrolling the view will call report("")
2431          * but if we are scrolling a non-current view this won't properly
2432          * update the view title. */
2433         if (split)
2434                 update_view_title(view);
2436         return TRUE;
2439 static bool
2440 pager_grep(struct view *view, struct line *line)
2442         regmatch_t pmatch;
2443         char *text = line->data;
2445         if (!*text)
2446                 return FALSE;
2448         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2449                 return FALSE;
2451         return TRUE;
2454 static void
2455 pager_select(struct view *view, struct line *line)
2457         if (line->type == LINE_COMMIT) {
2458                 char *text = line->data;
2460                 string_copy(view->ref, text + STRING_SIZE("commit "));
2461                 string_copy(ref_commit, view->ref);
2462         }
2465 static struct view_ops pager_ops = {
2466         "line",
2467         pager_draw,
2468         pager_read,
2469         pager_enter,
2470         pager_grep,
2471         pager_select,
2472 };
2475 /*
2476  * Tree backend
2477  */
2479 /* Parse output from git-ls-tree(1):
2480  *
2481  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2482  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2483  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2484  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2485  */
2487 #define SIZEOF_TREE_ATTR \
2488         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2490 #define TREE_UP_FORMAT "040000 tree %s\t.."
2492 static int
2493 tree_compare_entry(enum line_type type1, char *name1,
2494                    enum line_type type2, char *name2)
2496         if (type1 != type2) {
2497                 if (type1 == LINE_TREE_DIR)
2498                         return -1;
2499                 return 1;
2500         }
2502         return strcmp(name1, name2);
2505 static bool
2506 tree_read(struct view *view, char *text)
2508         size_t textlen = text ? strlen(text) : 0;
2509         char buf[SIZEOF_STR];
2510         unsigned long pos;
2511         enum line_type type;
2512         bool first_read = view->lines == 0;
2514         if (textlen <= SIZEOF_TREE_ATTR)
2515                 return FALSE;
2517         type = text[STRING_SIZE("100644 ")] == 't'
2518              ? LINE_TREE_DIR : LINE_TREE_FILE;
2520         if (first_read) {
2521                 /* Add path info line */
2522                 if (string_format(buf, "Directory path /%s", opt_path) &&
2523                     realloc_lines(view, view->line_size + 1) &&
2524                     pager_read(view, buf))
2525                         view->line[view->lines - 1].type = LINE_DEFAULT;
2526                 else
2527                         return FALSE;
2529                 /* Insert "link" to parent directory. */
2530                 if (*opt_path &&
2531                     string_format(buf, TREE_UP_FORMAT, view->ref) &&
2532                     realloc_lines(view, view->line_size + 1) &&
2533                     pager_read(view, buf))
2534                         view->line[view->lines - 1].type = LINE_TREE_DIR;
2535                 else if (*opt_path)
2536                         return FALSE;
2537         }
2539         /* Strip the path part ... */
2540         if (*opt_path) {
2541                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2542                 size_t striplen = strlen(opt_path);
2543                 char *path = text + SIZEOF_TREE_ATTR;
2545                 if (pathlen > striplen)
2546                         memmove(path, path + striplen,
2547                                 pathlen - striplen + 1);
2548         }
2550         /* Skip "Directory ..." and ".." line. */
2551         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2552                 struct line *line = &view->line[pos];
2553                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2554                 char *path2 = text + SIZEOF_TREE_ATTR;
2555                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2557                 if (cmp <= 0)
2558                         continue;
2560                 text = strdup(text);
2561                 if (!text)
2562                         return FALSE;
2564                 if (view->lines > pos)
2565                         memmove(&view->line[pos + 1], &view->line[pos],
2566                                 (view->lines - pos) * sizeof(*line));
2568                 line = &view->line[pos];
2569                 line->data = text;
2570                 line->type = type;
2571                 view->lines++;
2572                 return TRUE;
2573         }
2575         if (!pager_read(view, text))
2576                 return FALSE;
2578         /* Move the current line to the first tree entry. */
2579         if (first_read)
2580                 view->lineno++;
2582         view->line[view->lines - 1].type = type;
2583         return TRUE;
2586 static bool
2587 tree_enter(struct view *view, struct line *line)
2589         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2590         enum request request;
2592         switch (line->type) {
2593         case LINE_TREE_DIR:
2594                 /* Depending on whether it is a subdir or parent (updir?) link
2595                  * mangle the path buffer. */
2596                 if (line == &view->line[1] && *opt_path) {
2597                         size_t path_len = strlen(opt_path);
2598                         char *dirsep = opt_path + path_len - 1;
2600                         while (dirsep > opt_path && dirsep[-1] != '/')
2601                                 dirsep--;
2603                         dirsep[0] = 0;
2605                 } else {
2606                         size_t pathlen = strlen(opt_path);
2607                         size_t origlen = pathlen;
2608                         char *data = line->data;
2609                         char *basename = data + SIZEOF_TREE_ATTR;
2611                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2612                                 opt_path[origlen] = 0;
2613                                 return TRUE;
2614                         }
2615                 }
2617                 /* Trees and subtrees share the same ID, so they are not not
2618                  * unique like blobs. */
2619                 flags |= OPEN_RELOAD;
2620                 request = REQ_VIEW_TREE;
2621                 break;
2623         case LINE_TREE_FILE:
2624                 request = REQ_VIEW_BLOB;
2625                 break;
2627         default:
2628                 return TRUE;
2629         }
2631         open_view(view, request, flags);
2633         return TRUE;
2636 static void
2637 tree_select(struct view *view, struct line *line)
2639         char *text = line->data;
2641         text += STRING_SIZE("100644 blob ");
2643         if (line->type == LINE_TREE_FILE) {
2644                 string_ncopy(ref_blob, text, 40);
2645                 /* Also update the blob view's ref, since all there must always
2646                  * be in sync. */
2647                 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2649         } else if (line->type != LINE_TREE_DIR) {
2650                 return;
2651         }
2653         string_ncopy(view->ref, text, 40);
2656 static struct view_ops tree_ops = {
2657         "file",
2658         pager_draw,
2659         tree_read,
2660         tree_enter,
2661         pager_grep,
2662         tree_select,
2663 };
2665 static bool
2666 blob_read(struct view *view, char *line)
2668         bool state = pager_read(view, line);
2670         if (state == TRUE)
2671                 view->line[view->lines - 1].type = LINE_DEFAULT;
2673         return state;
2676 static struct view_ops blob_ops = {
2677         "line",
2678         pager_draw,
2679         blob_read,
2680         pager_enter,
2681         pager_grep,
2682         pager_select,
2683 };
2686 /*
2687  * Revision graph
2688  */
2690 struct commit {
2691         char id[SIZEOF_REV];            /* SHA1 ID. */
2692         char title[75];                 /* First line of the commit message. */
2693         char author[75];                /* Author of the commit. */
2694         struct tm time;                 /* Date from the author ident. */
2695         struct ref **refs;              /* Repository references. */
2696         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2697         size_t graph_size;              /* The width of the graph array. */
2698 };
2700 /* Size of rev graph with no  "padding" columns */
2701 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2703 struct rev_graph {
2704         struct rev_graph *prev, *next, *parents;
2705         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2706         size_t size;
2707         struct commit *commit;
2708         size_t pos;
2709 };
2711 /* Parents of the commit being visualized. */
2712 static struct rev_graph graph_parents[4];
2714 /* The current stack of revisions on the graph. */
2715 static struct rev_graph graph_stacks[4] = {
2716         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
2717         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
2718         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
2719         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
2720 };
2722 static inline bool
2723 graph_parent_is_merge(struct rev_graph *graph)
2725         return graph->parents->size > 1;
2728 static inline void
2729 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
2731         struct commit *commit = graph->commit;
2733         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
2734                 commit->graph[commit->graph_size++] = symbol;
2737 static void
2738 done_rev_graph(struct rev_graph *graph)
2740         if (graph_parent_is_merge(graph) &&
2741             graph->pos < graph->size - 1 &&
2742             graph->next->size == graph->size + graph->parents->size - 1) {
2743                 size_t i = graph->pos + graph->parents->size - 1;
2745                 graph->commit->graph_size = i * 2;
2746                 while (i < graph->next->size - 1) {
2747                         append_to_rev_graph(graph, ' ');
2748                         append_to_rev_graph(graph, '\\');
2749                         i++;
2750                 }
2751         }
2753         graph->size = graph->pos = 0;
2754         graph->commit = NULL;
2755         memset(graph->parents, 0, sizeof(*graph->parents));
2758 static void
2759 push_rev_graph(struct rev_graph *graph, char *parent)
2761         int i;
2763         /* "Collapse" duplicate parents lines.
2764          *
2765          * FIXME: This needs to also update update the drawn graph but
2766          * for now it just serves as a method for pruning graph lines. */
2767         for (i = 0; i < graph->size; i++)
2768                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
2769                         return;
2771         if (graph->size < SIZEOF_REVITEMS) {
2772                 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
2773         }
2776 static chtype
2777 get_rev_graph_symbol(struct rev_graph *graph)
2779         chtype symbol;
2781         if (graph->parents->size == 0)
2782                 symbol = REVGRAPH_INIT;
2783         else if (graph_parent_is_merge(graph))
2784                 symbol = REVGRAPH_MERGE;
2785         else if (graph->pos >= graph->size)
2786                 symbol = REVGRAPH_BRANCH;
2787         else
2788                 symbol = REVGRAPH_COMMIT;
2790         return symbol;
2793 static void
2794 draw_rev_graph(struct rev_graph *graph)
2796         struct rev_filler {
2797                 chtype separator, line;
2798         };
2799         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
2800         static struct rev_filler fillers[] = {
2801                 { ' ',  REVGRAPH_LINE },
2802                 { '`',  '.' },
2803                 { '\'', ' ' },
2804                 { '/',  ' ' },
2805         };
2806         chtype symbol = get_rev_graph_symbol(graph);
2807         struct rev_filler *filler;
2808         size_t i;
2810         filler = &fillers[DEFAULT];
2812         for (i = 0; i < graph->pos; i++) {
2813                 append_to_rev_graph(graph, filler->line);
2814                 if (graph_parent_is_merge(graph->prev) &&
2815                     graph->prev->pos == i)
2816                         filler = &fillers[RSHARP];
2818                 append_to_rev_graph(graph, filler->separator);
2819         }
2821         /* Place the symbol for this revision. */
2822         append_to_rev_graph(graph, symbol);
2824         if (graph->prev->size > graph->size)
2825                 filler = &fillers[RDIAG];
2826         else
2827                 filler = &fillers[DEFAULT];
2829         i++;
2831         for (; i < graph->size; i++) {
2832                 append_to_rev_graph(graph, filler->separator);
2833                 append_to_rev_graph(graph, filler->line);
2834                 if (graph_parent_is_merge(graph->prev) &&
2835                     i < graph->prev->pos + graph->parents->size)
2836                         filler = &fillers[RSHARP];
2837                 if (graph->prev->size > graph->size)
2838                         filler = &fillers[LDIAG];
2839         }
2841         if (graph->prev->size > graph->size) {
2842                 append_to_rev_graph(graph, filler->separator);
2843                 if (filler->line != ' ')
2844                         append_to_rev_graph(graph, filler->line);
2845         }
2848 /* Prepare the next rev graph */
2849 static void
2850 prepare_rev_graph(struct rev_graph *graph)
2852         size_t i;
2854         /* First, traverse all lines of revisions up to the active one. */
2855         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2856                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
2857                         break;
2859                 push_rev_graph(graph->next, graph->rev[graph->pos]);
2860         }
2862         /* Interleave the new revision parent(s). */
2863         for (i = 0; i < graph->parents->size; i++)
2864                 push_rev_graph(graph->next, graph->parents->rev[i]);
2866         /* Lastly, put any remaining revisions. */
2867         for (i = graph->pos + 1; i < graph->size; i++)
2868                 push_rev_graph(graph->next, graph->rev[i]);
2871 static void
2872 update_rev_graph(struct rev_graph *graph)
2874         /* If this is the finalizing update ... */
2875         if (graph->commit)
2876                 prepare_rev_graph(graph);
2878         /* Graph visualization needs a one rev look-ahead,
2879          * so the first update doesn't visualize anything. */
2880         if (!graph->prev->commit)
2881                 return;
2883         draw_rev_graph(graph->prev);
2884         done_rev_graph(graph->prev->prev);
2888 /*
2889  * Main view backend
2890  */
2892 static bool
2893 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2895         char buf[DATE_COLS + 1];
2896         struct commit *commit = line->data;
2897         enum line_type type;
2898         int col = 0;
2899         size_t timelen;
2900         size_t authorlen;
2901         int trimmed = 1;
2903         if (!*commit->author)
2904                 return FALSE;
2906         wmove(view->win, lineno, col);
2908         if (selected) {
2909                 type = LINE_CURSOR;
2910                 wattrset(view->win, get_line_attr(type));
2911                 wchgat(view->win, -1, 0, type, NULL);
2913         } else {
2914                 type = LINE_MAIN_COMMIT;
2915                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2916         }
2918         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2919         waddnstr(view->win, buf, timelen);
2920         waddstr(view->win, " ");
2922         col += DATE_COLS;
2923         wmove(view->win, lineno, col);
2924         if (type != LINE_CURSOR)
2925                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2927         if (opt_utf8) {
2928                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2929         } else {
2930                 authorlen = strlen(commit->author);
2931                 if (authorlen > AUTHOR_COLS - 2) {
2932                         authorlen = AUTHOR_COLS - 2;
2933                         trimmed = 1;
2934                 }
2935         }
2937         if (trimmed) {
2938                 waddnstr(view->win, commit->author, authorlen);
2939                 if (type != LINE_CURSOR)
2940                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2941                 waddch(view->win, '~');
2942         } else {
2943                 waddstr(view->win, commit->author);
2944         }
2946         col += AUTHOR_COLS;
2947         if (type != LINE_CURSOR)
2948                 wattrset(view->win, A_NORMAL);
2950         if (opt_rev_graph && commit->graph_size) {
2951                 size_t i;
2953                 wmove(view->win, lineno, col);
2954                 /* Using waddch() instead of waddnstr() ensures that
2955                  * they'll be rendered correctly for the cursor line. */
2956                 for (i = 0; i < commit->graph_size; i++)
2957                         waddch(view->win, commit->graph[i]);
2959                 waddch(view->win, ' ');
2960                 col += commit->graph_size + 1;
2961         }
2963         wmove(view->win, lineno, col);
2965         if (commit->refs) {
2966                 size_t i = 0;
2968                 do {
2969                         if (type == LINE_CURSOR)
2970                                 ;
2971                         else if (commit->refs[i]->tag)
2972                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2973                         else
2974                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2975                         waddstr(view->win, "[");
2976                         waddstr(view->win, commit->refs[i]->name);
2977                         waddstr(view->win, "]");
2978                         if (type != LINE_CURSOR)
2979                                 wattrset(view->win, A_NORMAL);
2980                         waddstr(view->win, " ");
2981                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2982                 } while (commit->refs[i++]->next);
2983         }
2985         if (type != LINE_CURSOR)
2986                 wattrset(view->win, get_line_attr(type));
2988         {
2989                 int titlelen = strlen(commit->title);
2991                 if (col + titlelen > view->width)
2992                         titlelen = view->width - col;
2994                 waddnstr(view->win, commit->title, titlelen);
2995         }
2997         return TRUE;
3000 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3001 static bool
3002 main_read(struct view *view, char *line)
3004         static struct rev_graph *graph = graph_stacks;
3005         enum line_type type;
3006         struct commit *commit = view->lines
3007                               ? view->line[view->lines - 1].data : NULL;
3009         if (!line) {
3010                 update_rev_graph(graph);
3011                 return TRUE;
3012         }
3014         type = get_line_type(line);
3016         switch (type) {
3017         case LINE_COMMIT:
3018                 commit = calloc(1, sizeof(struct commit));
3019                 if (!commit)
3020                         return FALSE;
3022                 line += STRING_SIZE("commit ");
3024                 view->line[view->lines++].data = commit;
3025                 string_copy(commit->id, line);
3026                 commit->refs = get_refs(commit->id);
3027                 graph->commit = commit;
3028                 break;
3030         case LINE_PARENT:
3031                 if (commit) {
3032                         line += STRING_SIZE("parent ");
3033                         push_rev_graph(graph->parents, line);
3034                 }
3035                 break;
3037         case LINE_AUTHOR:
3038         {
3039                 char *ident = line + STRING_SIZE("author ");
3040                 char *end = strchr(ident, '<');
3042                 if (!commit)
3043                         break;
3045                 update_rev_graph(graph);
3046                 graph = graph->next;
3048                 if (end) {
3049                         char *email = end + 1;
3051                         for (; end > ident && isspace(end[-1]); end--) ;
3053                         if (end == ident && *email) {
3054                                 ident = email;
3055                                 end = strchr(ident, '>');
3056                                 for (; end > ident && isspace(end[-1]); end--) ;
3057                         }
3058                         *end = 0;
3059                 }
3061                 /* End is NULL or ident meaning there's no author. */
3062                 if (end <= ident)
3063                         ident = "Unknown";
3065                 string_copy(commit->author, ident);
3067                 /* Parse epoch and timezone */
3068                 if (end) {
3069                         char *secs = strchr(end + 1, '>');
3070                         char *zone;
3071                         time_t time;
3073                         if (!secs || secs[1] != ' ')
3074                                 break;
3076                         secs += 2;
3077                         time = (time_t) atol(secs);
3078                         zone = strchr(secs, ' ');
3079                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3080                                 long tz;
3082                                 zone++;
3083                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3084                                 tz += ('0' - zone[2]) * 60 * 60;
3085                                 tz += ('0' - zone[3]) * 60;
3086                                 tz += ('0' - zone[4]) * 60;
3088                                 if (zone[0] == '-')
3089                                         tz = -tz;
3091                                 time -= tz;
3092                         }
3093                         gmtime_r(&time, &commit->time);
3094                 }
3095                 break;
3096         }
3097         default:
3098                 if (!commit)
3099                         break;
3101                 /* Fill in the commit title if it has not already been set. */
3102                 if (commit->title[0])
3103                         break;
3105                 /* Require titles to start with a non-space character at the
3106                  * offset used by git log. */
3107                 /* FIXME: More gracefull handling of titles; append "..." to
3108                  * shortened titles, etc. */
3109                 if (strncmp(line, "    ", 4) ||
3110                     isspace(line[4]))
3111                         break;
3113                 string_copy(commit->title, line + 4);
3114         }
3116         return TRUE;
3119 static bool
3120 main_enter(struct view *view, struct line *line)
3122         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3124         open_view(view, REQ_VIEW_DIFF, flags);
3125         return TRUE;
3128 static bool
3129 main_grep(struct view *view, struct line *line)
3131         struct commit *commit = line->data;
3132         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3133         char buf[DATE_COLS + 1];
3134         regmatch_t pmatch;
3136         for (state = S_TITLE; state < S_END; state++) {
3137                 char *text;
3139                 switch (state) {
3140                 case S_TITLE:   text = commit->title;   break;
3141                 case S_AUTHOR:  text = commit->author;  break;
3142                 case S_DATE:
3143                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3144                                 continue;
3145                         text = buf;
3146                         break;
3148                 default:
3149                         return FALSE;
3150                 }
3152                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3153                         return TRUE;
3154         }
3156         return FALSE;
3159 static void
3160 main_select(struct view *view, struct line *line)
3162         struct commit *commit = line->data;
3164         string_copy(view->ref, commit->id);
3165         string_copy(ref_commit, view->ref);
3168 static struct view_ops main_ops = {
3169         "commit",
3170         main_draw,
3171         main_read,
3172         main_enter,
3173         main_grep,
3174         main_select,
3175 };
3178 /*
3179  * Unicode / UTF-8 handling
3180  *
3181  * NOTE: Much of the following code for dealing with unicode is derived from
3182  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3183  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3184  */
3186 /* I've (over)annotated a lot of code snippets because I am not entirely
3187  * confident that the approach taken by this small UTF-8 interface is correct.
3188  * --jonas */
3190 static inline int
3191 unicode_width(unsigned long c)
3193         if (c >= 0x1100 &&
3194            (c <= 0x115f                         /* Hangul Jamo */
3195             || c == 0x2329
3196             || c == 0x232a
3197             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
3198                                                 /* CJK ... Yi */
3199             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
3200             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
3201             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
3202             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
3203             || (c >= 0xffe0  && c <= 0xffe6)
3204             || (c >= 0x20000 && c <= 0x2fffd)
3205             || (c >= 0x30000 && c <= 0x3fffd)))
3206                 return 2;
3208         return 1;
3211 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3212  * Illegal bytes are set one. */
3213 static const unsigned char utf8_bytes[256] = {
3214         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3215         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3216         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3217         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3218         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3219         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3220         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,
3221         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,
3222 };
3224 /* Decode UTF-8 multi-byte representation into a unicode character. */
3225 static inline unsigned long
3226 utf8_to_unicode(const char *string, size_t length)
3228         unsigned long unicode;
3230         switch (length) {
3231         case 1:
3232                 unicode  =   string[0];
3233                 break;
3234         case 2:
3235                 unicode  =  (string[0] & 0x1f) << 6;
3236                 unicode +=  (string[1] & 0x3f);
3237                 break;
3238         case 3:
3239                 unicode  =  (string[0] & 0x0f) << 12;
3240                 unicode += ((string[1] & 0x3f) << 6);
3241                 unicode +=  (string[2] & 0x3f);
3242                 break;
3243         case 4:
3244                 unicode  =  (string[0] & 0x0f) << 18;
3245                 unicode += ((string[1] & 0x3f) << 12);
3246                 unicode += ((string[2] & 0x3f) << 6);
3247                 unicode +=  (string[3] & 0x3f);
3248                 break;
3249         case 5:
3250                 unicode  =  (string[0] & 0x0f) << 24;
3251                 unicode += ((string[1] & 0x3f) << 18);
3252                 unicode += ((string[2] & 0x3f) << 12);
3253                 unicode += ((string[3] & 0x3f) << 6);
3254                 unicode +=  (string[4] & 0x3f);
3255                 break;
3256         case 6:
3257                 unicode  =  (string[0] & 0x01) << 30;
3258                 unicode += ((string[1] & 0x3f) << 24);
3259                 unicode += ((string[2] & 0x3f) << 18);
3260                 unicode += ((string[3] & 0x3f) << 12);
3261                 unicode += ((string[4] & 0x3f) << 6);
3262                 unicode +=  (string[5] & 0x3f);
3263                 break;
3264         default:
3265                 die("Invalid unicode length");
3266         }
3268         /* Invalid characters could return the special 0xfffd value but NUL
3269          * should be just as good. */
3270         return unicode > 0xffff ? 0 : unicode;
3273 /* Calculates how much of string can be shown within the given maximum width
3274  * and sets trimmed parameter to non-zero value if all of string could not be
3275  * shown.
3276  *
3277  * Additionally, adds to coloffset how many many columns to move to align with
3278  * the expected position. Takes into account how multi-byte and double-width
3279  * characters will effect the cursor position.
3280  *
3281  * Returns the number of bytes to output from string to satisfy max_width. */
3282 static size_t
3283 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3285         const char *start = string;
3286         const char *end = strchr(string, '\0');
3287         size_t mbwidth = 0;
3288         size_t width = 0;
3290         *trimmed = 0;
3292         while (string < end) {
3293                 int c = *(unsigned char *) string;
3294                 unsigned char bytes = utf8_bytes[c];
3295                 size_t ucwidth;
3296                 unsigned long unicode;
3298                 if (string + bytes > end)
3299                         break;
3301                 /* Change representation to figure out whether
3302                  * it is a single- or double-width character. */
3304                 unicode = utf8_to_unicode(string, bytes);
3305                 /* FIXME: Graceful handling of invalid unicode character. */
3306                 if (!unicode)
3307                         break;
3309                 ucwidth = unicode_width(unicode);
3310                 width  += ucwidth;
3311                 if (width > max_width) {
3312                         *trimmed = 1;
3313                         break;
3314                 }
3316                 /* The column offset collects the differences between the
3317                  * number of bytes encoding a character and the number of
3318                  * columns will be used for rendering said character.
3319                  *
3320                  * So if some character A is encoded in 2 bytes, but will be
3321                  * represented on the screen using only 1 byte this will and up
3322                  * adding 1 to the multi-byte column offset.
3323                  *
3324                  * Assumes that no double-width character can be encoding in
3325                  * less than two bytes. */
3326                 if (bytes > ucwidth)
3327                         mbwidth += bytes - ucwidth;
3329                 string  += bytes;
3330         }
3332         *coloffset += mbwidth;
3334         return string - start;
3338 /*
3339  * Status management
3340  */
3342 /* Whether or not the curses interface has been initialized. */
3343 static bool cursed = FALSE;
3345 /* The status window is used for polling keystrokes. */
3346 static WINDOW *status_win;
3348 /* Update status and title window. */
3349 static void
3350 report(const char *msg, ...)
3352         static bool empty = TRUE;
3353         struct view *view = display[current_view];
3355         if (!empty || *msg) {
3356                 va_list args;
3358                 va_start(args, msg);
3360                 werase(status_win);
3361                 wmove(status_win, 0, 0);
3362                 if (*msg) {
3363                         vwprintw(status_win, msg, args);
3364                         empty = FALSE;
3365                 } else {
3366                         empty = TRUE;
3367                 }
3368                 wrefresh(status_win);
3370                 va_end(args);
3371         }
3373         update_view_title(view);
3374         update_display_cursor(view);
3377 /* Controls when nodelay should be in effect when polling user input. */
3378 static void
3379 set_nonblocking_input(bool loading)
3381         static unsigned int loading_views;
3383         if ((loading == FALSE && loading_views-- == 1) ||
3384             (loading == TRUE  && loading_views++ == 0))
3385                 nodelay(status_win, loading);
3388 static void
3389 init_display(void)
3391         int x, y;
3393         /* Initialize the curses library */
3394         if (isatty(STDIN_FILENO)) {
3395                 cursed = !!initscr();
3396         } else {
3397                 /* Leave stdin and stdout alone when acting as a pager. */
3398                 FILE *io = fopen("/dev/tty", "r+");
3400                 if (!io)
3401                         die("Failed to open /dev/tty");
3402                 cursed = !!newterm(NULL, io, io);
3403         }
3405         if (!cursed)
3406                 die("Failed to initialize curses");
3408         nonl();         /* Tell curses not to do NL->CR/NL on output */
3409         cbreak();       /* Take input chars one at a time, no wait for \n */
3410         noecho();       /* Don't echo input */
3411         leaveok(stdscr, TRUE);
3413         if (has_colors())
3414                 init_colors();
3416         getmaxyx(stdscr, y, x);
3417         status_win = newwin(1, 0, y - 1, 0);
3418         if (!status_win)
3419                 die("Failed to create status window");
3421         /* Enable keyboard mapping */
3422         keypad(status_win, TRUE);
3423         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3426 static char *
3427 read_prompt(const char *prompt)
3429         enum { READING, STOP, CANCEL } status = READING;
3430         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3431         int pos = 0;
3433         while (status == READING) {
3434                 struct view *view;
3435                 int i, key;
3437                 foreach_view (view, i)
3438                         update_view(view);
3440                 report("%s%.*s", prompt, pos, buf);
3441                 /* Refresh, accept single keystroke of input */
3442                 key = wgetch(status_win);
3443                 switch (key) {
3444                 case KEY_RETURN:
3445                 case KEY_ENTER:
3446                 case '\n':
3447                         status = pos ? STOP : CANCEL;
3448                         break;
3450                 case KEY_BACKSPACE:
3451                         if (pos > 0)
3452                                 pos--;
3453                         else
3454                                 status = CANCEL;
3455                         break;
3457                 case KEY_ESC:
3458                         status = CANCEL;
3459                         break;
3461                 case ERR:
3462                         break;
3464                 default:
3465                         if (pos >= sizeof(buf)) {
3466                                 report("Input string too long");
3467                                 return NULL;
3468                         }
3470                         if (isprint(key))
3471                                 buf[pos++] = (char) key;
3472                 }
3473         }
3475         if (status == CANCEL) {
3476                 /* Clear the status window */
3477                 report("");
3478                 return NULL;
3479         }
3481         buf[pos++] = 0;
3483         return buf;
3486 /*
3487  * Repository references
3488  */
3490 static struct ref *refs;
3491 static size_t refs_size;
3493 /* Id <-> ref store */
3494 static struct ref ***id_refs;
3495 static size_t id_refs_size;
3497 static struct ref **
3498 get_refs(char *id)
3500         struct ref ***tmp_id_refs;
3501         struct ref **ref_list = NULL;
3502         size_t ref_list_size = 0;
3503         size_t i;
3505         for (i = 0; i < id_refs_size; i++)
3506                 if (!strcmp(id, id_refs[i][0]->id))
3507                         return id_refs[i];
3509         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3510         if (!tmp_id_refs)
3511                 return NULL;
3513         id_refs = tmp_id_refs;
3515         for (i = 0; i < refs_size; i++) {
3516                 struct ref **tmp;
3518                 if (strcmp(id, refs[i].id))
3519                         continue;
3521                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3522                 if (!tmp) {
3523                         if (ref_list)
3524                                 free(ref_list);
3525                         return NULL;
3526                 }
3528                 ref_list = tmp;
3529                 if (ref_list_size > 0)
3530                         ref_list[ref_list_size - 1]->next = 1;
3531                 ref_list[ref_list_size] = &refs[i];
3533                 /* XXX: The properties of the commit chains ensures that we can
3534                  * safely modify the shared ref. The repo references will
3535                  * always be similar for the same id. */
3536                 ref_list[ref_list_size]->next = 0;
3537                 ref_list_size++;
3538         }
3540         if (ref_list)
3541                 id_refs[id_refs_size++] = ref_list;
3543         return ref_list;
3546 static int
3547 read_ref(char *id, int idlen, char *name, int namelen)
3549         struct ref *ref;
3550         bool tag = FALSE;
3552         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3553                 /* Commits referenced by tags has "^{}" appended. */
3554                 if (name[namelen - 1] != '}')
3555                         return OK;
3557                 while (namelen > 0 && name[namelen] != '^')
3558                         namelen--;
3560                 tag = TRUE;
3561                 namelen -= STRING_SIZE("refs/tags/");
3562                 name    += STRING_SIZE("refs/tags/");
3564         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3565                 namelen -= STRING_SIZE("refs/heads/");
3566                 name    += STRING_SIZE("refs/heads/");
3568         } else if (!strcmp(name, "HEAD")) {
3569                 return OK;
3570         }
3572         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3573         if (!refs)
3574                 return ERR;
3576         ref = &refs[refs_size++];
3577         ref->name = malloc(namelen + 1);
3578         if (!ref->name)
3579                 return ERR;
3581         strncpy(ref->name, name, namelen);
3582         ref->name[namelen] = 0;
3583         ref->tag = tag;
3584         string_copy(ref->id, id);
3586         return OK;
3589 static int
3590 load_refs(void)
3592         const char *cmd_env = getenv("TIG_LS_REMOTE");
3593         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3595         return read_properties(popen(cmd, "r"), "\t", read_ref);
3598 static int
3599 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3601         if (!strcmp(name, "i18n.commitencoding"))
3602                 string_copy(opt_encoding, value);
3604         return OK;
3607 static int
3608 load_repo_config(void)
3610         return read_properties(popen("git repo-config --list", "r"),
3611                                "=", read_repo_config_option);
3614 static int
3615 read_properties(FILE *pipe, const char *separators,
3616                 int (*read_property)(char *, int, char *, int))
3618         char buffer[BUFSIZ];
3619         char *name;
3620         int state = OK;
3622         if (!pipe)
3623                 return ERR;
3625         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3626                 char *value;
3627                 size_t namelen;
3628                 size_t valuelen;
3630                 name = chomp_string(name);
3631                 namelen = strcspn(name, separators);
3633                 if (name[namelen]) {
3634                         name[namelen] = 0;
3635                         value = chomp_string(name + namelen + 1);
3636                         valuelen = strlen(value);
3638                 } else {
3639                         value = "";
3640                         valuelen = 0;
3641                 }
3643                 state = read_property(name, namelen, value, valuelen);
3644         }
3646         if (state != ERR && ferror(pipe))
3647                 state = ERR;
3649         pclose(pipe);
3651         return state;
3655 /*
3656  * Main
3657  */
3659 static void __NORETURN
3660 quit(int sig)
3662         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3663         if (cursed)
3664                 endwin();
3665         exit(0);
3668 static void __NORETURN
3669 die(const char *err, ...)
3671         va_list args;
3673         endwin();
3675         va_start(args, err);
3676         fputs("tig: ", stderr);
3677         vfprintf(stderr, err, args);
3678         fputs("\n", stderr);
3679         va_end(args);
3681         exit(1);
3684 int
3685 main(int argc, char *argv[])
3687         struct view *view;
3688         enum request request;
3689         size_t i;
3691         signal(SIGINT, quit);
3693         if (setlocale(LC_ALL, "")) {
3694                 string_copy(opt_codeset, nl_langinfo(CODESET));
3695         }
3697         if (load_options() == ERR)
3698                 die("Failed to load user config.");
3700         /* Load the repo config file so options can be overwritten from
3701          * the command line.  */
3702         if (load_repo_config() == ERR)
3703                 die("Failed to load repo config.");
3705         if (!parse_options(argc, argv))
3706                 return 0;
3708         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3709                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3710                 if (opt_iconv == ICONV_NONE)
3711                         die("Failed to initialize character set conversion");
3712         }
3714         if (load_refs() == ERR)
3715                 die("Failed to load refs.");
3717         /* Require a git repository unless when running in pager mode. */
3718         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3719                 die("Not a git repository");
3721         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3722                 view->cmd_env = getenv(view->cmd_env);
3724         request = opt_request;
3726         init_display();
3728         while (view_driver(display[current_view], request)) {
3729                 int key;
3730                 int i;
3732                 foreach_view (view, i)
3733                         update_view(view);
3735                 /* Refresh, accept single keystroke of input */
3736                 key = wgetch(status_win);
3738                 request = get_keybinding(display[current_view]->keymap, key);
3740                 /* Some low-level request handling. This keeps access to
3741                  * status_win restricted. */
3742                 switch (request) {
3743                 case REQ_PROMPT:
3744                 {
3745                         char *cmd = read_prompt(":");
3747                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3748                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3749                                         opt_request = REQ_VIEW_DIFF;
3750                                 } else {
3751                                         opt_request = REQ_VIEW_PAGER;
3752                                 }
3753                                 break;
3754                         }
3756                         request = REQ_NONE;
3757                         break;
3758                 }
3759                 case REQ_SEARCH:
3760                 case REQ_SEARCH_BACK:
3761                 {
3762                         const char *prompt = request == REQ_SEARCH
3763                                            ? "/" : "?";
3764                         char *search = read_prompt(prompt);
3766                         if (search)
3767                                 string_copy(opt_search, search);
3768                         else
3769                                 request = REQ_NONE;
3770                         break;
3771                 }
3772                 case REQ_SCREEN_RESIZE:
3773                 {
3774                         int height, width;
3776                         getmaxyx(stdscr, height, width);
3778                         /* Resize the status view and let the view driver take
3779                          * care of resizing the displayed views. */
3780                         wresize(status_win, 1, width);
3781                         mvwin(status_win, height - 1, 0);
3782                         wrefresh(status_win);
3783                         break;
3784                 }
3785                 default:
3786                         break;
3787                 }
3788         }
3790         quit(0);
3792         return 0;