Code

Add support for tree and blob view
[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_REVGRAPH 19      /* Size of revision ancestry graphics. */
64 /* This color name can be used to refer to the default term colors. */
65 #define COLOR_DEFAULT   (-1)
67 #define ICONV_NONE      ((iconv_t) -1)
69 /* The format and size of the date column in the main view. */
70 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
71 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
73 #define AUTHOR_COLS     20
75 /* The default interval between line numbers. */
76 #define NUMBER_INTERVAL 1
78 #define TABSIZE         8
80 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
82 #define TIG_LS_REMOTE \
83         "git ls-remote . 2>/dev/null"
85 #define TIG_DIFF_CMD \
86         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
88 #define TIG_LOG_CMD     \
89         "git log --cc --stat -n100 %s 2>/dev/null"
91 #define TIG_MAIN_CMD \
92         "git log --topo-order --pretty=raw %s 2>/dev/null"
94 #define TIG_TREE_CMD    \
95         "git ls-tree %s %s"
97 #define TIG_BLOB_CMD    \
98         "git cat-file blob %s"
100 /* XXX: Needs to be defined to the empty string. */
101 #define TIG_HELP_CMD    ""
102 #define TIG_PAGER_CMD   ""
104 /* Some ascii-shorthands fitted into the ncurses namespace. */
105 #define KEY_TAB         '\t'
106 #define KEY_RETURN      '\r'
107 #define KEY_ESC         27
110 struct ref {
111         char *name;             /* Ref name; tag or head names are shortened. */
112         char id[41];            /* Commit SHA1 ID */
113         unsigned int tag:1;     /* Is it a tag? */
114         unsigned int next:1;    /* For ref lists: are there more refs? */
115 };
117 static struct ref **get_refs(char *id);
119 struct int_map {
120         const char *name;
121         int namelen;
122         int value;
123 };
125 static int
126 set_from_int_map(struct int_map *map, size_t map_size,
127                  int *value, const char *name, int namelen)
130         int i;
132         for (i = 0; i < map_size; i++)
133                 if (namelen == map[i].namelen &&
134                     !strncasecmp(name, map[i].name, namelen)) {
135                         *value = map[i].value;
136                         return OK;
137                 }
139         return ERR;
143 /*
144  * String helpers
145  */
147 static inline void
148 string_ncopy(char *dst, const char *src, int dstlen)
150         strncpy(dst, src, dstlen - 1);
151         dst[dstlen - 1] = 0;
155 /* Shorthand for safely copying into a fixed buffer. */
156 #define string_copy(dst, src) \
157         string_ncopy(dst, src, sizeof(dst))
159 static char *
160 chomp_string(char *name)
162         int namelen;
164         while (isspace(*name))
165                 name++;
167         namelen = strlen(name) - 1;
168         while (namelen > 0 && isspace(name[namelen]))
169                 name[namelen--] = 0;
171         return name;
174 static bool
175 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
177         va_list args;
178         int pos = bufpos ? *bufpos : 0;
180         va_start(args, fmt);
181         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
182         va_end(args);
184         if (bufpos)
185                 *bufpos = pos;
187         return pos >= bufsize ? FALSE : TRUE;
190 #define string_format(buf, fmt, args...) \
191         string_nformat(buf, sizeof(buf), NULL, fmt, args)
193 #define string_format_from(buf, from, fmt, args...) \
194         string_nformat(buf, sizeof(buf), from, fmt, args)
196 static int
197 string_enum_compare(const char *str1, const char *str2, int len)
199         size_t i;
201 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
203         /* Diff-Header == DIFF_HEADER */
204         for (i = 0; i < len; i++) {
205                 if (toupper(str1[i]) == toupper(str2[i]))
206                         continue;
208                 if (string_enum_sep(str1[i]) &&
209                     string_enum_sep(str2[i]))
210                         continue;
212                 return str1[i] - str2[i];
213         }
215         return 0;
218 /* Shell quoting
219  *
220  * NOTE: The following is a slightly modified copy of the git project's shell
221  * quoting routines found in the quote.c file.
222  *
223  * Help to copy the thing properly quoted for the shell safety.  any single
224  * quote is replaced with '\'', any exclamation point is replaced with '\!',
225  * and the whole thing is enclosed in a
226  *
227  * E.g.
228  *  original     sq_quote     result
229  *  name     ==> name      ==> 'name'
230  *  a b      ==> a b       ==> 'a b'
231  *  a'b      ==> a'\''b    ==> 'a'\''b'
232  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
233  */
235 static size_t
236 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
238         char c;
240 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
242         BUFPUT('\'');
243         while ((c = *src++)) {
244                 if (c == '\'' || c == '!') {
245                         BUFPUT('\'');
246                         BUFPUT('\\');
247                         BUFPUT(c);
248                         BUFPUT('\'');
249                 } else {
250                         BUFPUT(c);
251                 }
252         }
253         BUFPUT('\'');
255         return bufsize;
259 /*
260  * User requests
261  */
263 #define REQ_INFO \
264         /* XXX: Keep the view request first and in sync with views[]. */ \
265         REQ_GROUP("View switching") \
266         REQ_(VIEW_MAIN,         "Show main view"), \
267         REQ_(VIEW_DIFF,         "Show diff view"), \
268         REQ_(VIEW_LOG,          "Show log view"), \
269         REQ_(VIEW_TREE,         "Show tree view"), \
270         REQ_(VIEW_BLOB,         "Show blob view"), \
271         REQ_(VIEW_HELP,         "Show help page"), \
272         REQ_(VIEW_PAGER,        "Show pager view"), \
273         \
274         REQ_GROUP("View manipulation") \
275         REQ_(ENTER,             "Enter current line and scroll"), \
276         REQ_(NEXT,              "Move to next"), \
277         REQ_(PREVIOUS,          "Move to previous"), \
278         REQ_(VIEW_NEXT,         "Move focus to next view"), \
279         REQ_(VIEW_CLOSE,        "Close the current view"), \
280         REQ_(QUIT,              "Close all views and quit"), \
281         \
282         REQ_GROUP("Cursor navigation") \
283         REQ_(MOVE_UP,           "Move cursor one line up"), \
284         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
285         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
286         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
287         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
288         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
289         \
290         REQ_GROUP("Scrolling") \
291         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
292         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
293         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
294         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
295         \
296         REQ_GROUP("Searching") \
297         REQ_(SEARCH,            "Search the view"), \
298         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
299         REQ_(FIND_NEXT,         "Find next search match"), \
300         REQ_(FIND_PREV,         "Find previous search match"), \
301         \
302         REQ_GROUP("Misc") \
303         REQ_(NONE,              "Do nothing"), \
304         REQ_(PROMPT,            "Bring up the prompt"), \
305         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
306         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
307         REQ_(SHOW_VERSION,      "Show version information"), \
308         REQ_(STOP_LOADING,      "Stop all loading views"), \
309         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
310         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
313 /* User action requests. */
314 enum request {
315 #define REQ_GROUP(help)
316 #define REQ_(req, help) REQ_##req
318         /* Offset all requests to avoid conflicts with ncurses getch values. */
319         REQ_OFFSET = KEY_MAX + 1,
320         REQ_INFO,
321         REQ_UNKNOWN,
323 #undef  REQ_GROUP
324 #undef  REQ_
325 };
327 struct request_info {
328         enum request request;
329         char *name;
330         int namelen;
331         char *help;
332 };
334 static struct request_info req_info[] = {
335 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
336 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
337         REQ_INFO
338 #undef  REQ_GROUP
339 #undef  REQ_
340 };
342 static enum request
343 get_request(const char *name)
345         int namelen = strlen(name);
346         int i;
348         for (i = 0; i < ARRAY_SIZE(req_info); i++)
349                 if (req_info[i].namelen == namelen &&
350                     !string_enum_compare(req_info[i].name, name, namelen))
351                         return req_info[i].request;
353         return REQ_UNKNOWN;
357 /*
358  * Options
359  */
361 static const char usage[] =
362 VERSION " (" __DATE__ ")\n"
363 "\n"
364 "Usage: tig [options]\n"
365 "   or: tig [options] [--] [git log options]\n"
366 "   or: tig [options] log  [git log options]\n"
367 "   or: tig [options] diff [git diff options]\n"
368 "   or: tig [options] show [git show options]\n"
369 "   or: tig [options] <    [git command output]\n"
370 "\n"
371 "Options:\n"
372 "  -l                          Start up in log view\n"
373 "  -d                          Start up in diff view\n"
374 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
375 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
376 "  --                          Mark end of tig options\n"
377 "  -v, --version               Show version and exit\n"
378 "  -h, --help                  Show help message and exit\n";
380 /* Option and state variables. */
381 static bool opt_line_number             = FALSE;
382 static bool opt_rev_graph               = TRUE;
383 static int opt_num_interval             = NUMBER_INTERVAL;
384 static int opt_tab_size                 = TABSIZE;
385 static enum request opt_request         = REQ_VIEW_MAIN;
386 static char opt_cmd[SIZEOF_STR]         = "";
387 static char opt_path[SIZEOF_STR]        = "";
388 static FILE *opt_pipe                   = NULL;
389 static char opt_encoding[20]            = "UTF-8";
390 static bool opt_utf8                    = TRUE;
391 static char opt_codeset[20]             = "UTF-8";
392 static iconv_t opt_iconv                = ICONV_NONE;
393 static char opt_search[SIZEOF_STR]      = "";
395 enum option_type {
396         OPT_NONE,
397         OPT_INT,
398 };
400 static bool
401 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
403         va_list args;
404         char *value = "";
405         int *number;
407         if (opt[0] != '-')
408                 return FALSE;
410         if (opt[1] == '-') {
411                 int namelen = strlen(name);
413                 opt += 2;
415                 if (strncmp(opt, name, namelen))
416                         return FALSE;
418                 if (opt[namelen] == '=')
419                         value = opt + namelen + 1;
421         } else {
422                 if (!short_name || opt[1] != short_name)
423                         return FALSE;
424                 value = opt + 2;
425         }
427         va_start(args, type);
428         if (type == OPT_INT) {
429                 number = va_arg(args, int *);
430                 if (isdigit(*value))
431                         *number = atoi(value);
432         }
433         va_end(args);
435         return TRUE;
438 /* Returns the index of log or diff command or -1 to exit. */
439 static bool
440 parse_options(int argc, char *argv[])
442         int i;
444         for (i = 1; i < argc; i++) {
445                 char *opt = argv[i];
447                 if (!strcmp(opt, "-l")) {
448                         opt_request = REQ_VIEW_LOG;
449                         continue;
450                 }
452                 if (!strcmp(opt, "-d")) {
453                         opt_request = REQ_VIEW_DIFF;
454                         continue;
455                 }
457                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
458                         opt_line_number = TRUE;
459                         continue;
460                 }
462                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
463                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
464                         continue;
465                 }
467                 if (check_option(opt, 'v', "version", OPT_NONE)) {
468                         printf("tig version %s\n", VERSION);
469                         return FALSE;
470                 }
472                 if (check_option(opt, 'h', "help", OPT_NONE)) {
473                         printf(usage);
474                         return FALSE;
475                 }
477                 if (!strcmp(opt, "--")) {
478                         i++;
479                         break;
480                 }
482                 if (!strcmp(opt, "log") ||
483                     !strcmp(opt, "diff") ||
484                     !strcmp(opt, "show")) {
485                         opt_request = opt[0] == 'l'
486                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
487                         break;
488                 }
490                 if (opt[0] && opt[0] != '-')
491                         break;
493                 die("unknown option '%s'\n\n%s", opt, usage);
494         }
496         if (!isatty(STDIN_FILENO)) {
497                 opt_request = REQ_VIEW_PAGER;
498                 opt_pipe = stdin;
500         } else if (i < argc) {
501                 size_t buf_size;
503                 if (opt_request == REQ_VIEW_MAIN)
504                         /* XXX: This is vulnerable to the user overriding
505                          * options required for the main view parser. */
506                         string_copy(opt_cmd, "git log --stat --pretty=raw");
507                 else
508                         string_copy(opt_cmd, "git");
509                 buf_size = strlen(opt_cmd);
511                 while (buf_size < sizeof(opt_cmd) && i < argc) {
512                         opt_cmd[buf_size++] = ' ';
513                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
514                 }
516                 if (buf_size >= sizeof(opt_cmd))
517                         die("command too long");
519                 opt_cmd[buf_size] = 0;
521         }
523         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
524                 opt_utf8 = FALSE;
526         return TRUE;
530 /*
531  * Line-oriented content detection.
532  */
534 #define LINE_INFO \
535 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
536 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
537 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
538 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
539 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
540 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
541 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
542 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
543 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
544 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
545 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
546 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
547 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
548 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
549 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
550 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
551 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
552 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
553 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
554 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
555 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
556 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
557 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
558 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
559 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
560 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
561 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
562 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
563 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
564 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
565 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
566 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
567 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
568 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
569 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
570 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
571 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
572 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
573 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
574 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
576 enum line_type {
577 #define LINE(type, line, fg, bg, attr) \
578         LINE_##type
579         LINE_INFO
580 #undef  LINE
581 };
583 struct line_info {
584         const char *name;       /* Option name. */
585         int namelen;            /* Size of option name. */
586         const char *line;       /* The start of line to match. */
587         int linelen;            /* Size of string to match. */
588         int fg, bg, attr;       /* Color and text attributes for the lines. */
589 };
591 static struct line_info line_info[] = {
592 #define LINE(type, line, fg, bg, attr) \
593         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
594         LINE_INFO
595 #undef  LINE
596 };
598 static enum line_type
599 get_line_type(char *line)
601         int linelen = strlen(line);
602         enum line_type type;
604         for (type = 0; type < ARRAY_SIZE(line_info); type++)
605                 /* Case insensitive search matches Signed-off-by lines better. */
606                 if (linelen >= line_info[type].linelen &&
607                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
608                         return type;
610         return LINE_DEFAULT;
613 static inline int
614 get_line_attr(enum line_type type)
616         assert(type < ARRAY_SIZE(line_info));
617         return COLOR_PAIR(type) | line_info[type].attr;
620 static struct line_info *
621 get_line_info(char *name, int namelen)
623         enum line_type type;
625         for (type = 0; type < ARRAY_SIZE(line_info); type++)
626                 if (namelen == line_info[type].namelen &&
627                     !string_enum_compare(line_info[type].name, name, namelen))
628                         return &line_info[type];
630         return NULL;
633 static void
634 init_colors(void)
636         int default_bg = COLOR_BLACK;
637         int default_fg = COLOR_WHITE;
638         enum line_type type;
640         start_color();
642         if (use_default_colors() != ERR) {
643                 default_bg = -1;
644                 default_fg = -1;
645         }
647         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
648                 struct line_info *info = &line_info[type];
649                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
650                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
652                 init_pair(type, fg, bg);
653         }
656 struct line {
657         enum line_type type;
658         void *data;             /* User data */
659 };
662 /*
663  * Keys
664  */
666 struct keybinding {
667         int alias;
668         enum request request;
669         struct keybinding *next;
670 };
672 static struct keybinding default_keybindings[] = {
673         /* View switching */
674         { 'm',          REQ_VIEW_MAIN },
675         { 'd',          REQ_VIEW_DIFF },
676         { 'l',          REQ_VIEW_LOG },
677         { 't',          REQ_VIEW_TREE },
678         { 'b',          REQ_VIEW_BLOB },
679         { 'p',          REQ_VIEW_PAGER },
680         { 'h',          REQ_VIEW_HELP },
682         /* View manipulation */
683         { 'q',          REQ_VIEW_CLOSE },
684         { KEY_TAB,      REQ_VIEW_NEXT },
685         { KEY_RETURN,   REQ_ENTER },
686         { KEY_UP,       REQ_PREVIOUS },
687         { KEY_DOWN,     REQ_NEXT },
689         /* Cursor navigation */
690         { 'k',          REQ_MOVE_UP },
691         { 'j',          REQ_MOVE_DOWN },
692         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
693         { KEY_END,      REQ_MOVE_LAST_LINE },
694         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
695         { ' ',          REQ_MOVE_PAGE_DOWN },
696         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
697         { 'b',          REQ_MOVE_PAGE_UP },
698         { '-',          REQ_MOVE_PAGE_UP },
700         /* Scrolling */
701         { KEY_IC,       REQ_SCROLL_LINE_UP },
702         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
703         { 'w',          REQ_SCROLL_PAGE_UP },
704         { 's',          REQ_SCROLL_PAGE_DOWN },
706         /* Searching */
707         { '/',          REQ_SEARCH },
708         { '?',          REQ_SEARCH_BACK },
709         { 'n',          REQ_FIND_NEXT },
710         { 'N',          REQ_FIND_PREV },
712         /* Misc */
713         { 'Q',          REQ_QUIT },
714         { 'z',          REQ_STOP_LOADING },
715         { 'v',          REQ_SHOW_VERSION },
716         { 'r',          REQ_SCREEN_REDRAW },
717         { 'n',          REQ_TOGGLE_LINENO },
718         { 'g',          REQ_TOGGLE_REV_GRAPH },
719         { ':',          REQ_PROMPT },
721         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
722         { ERR,          REQ_NONE },
724         /* Using the ncurses SIGWINCH handler. */
725         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
726 };
728 #define KEYMAP_INFO \
729         KEYMAP_(GENERIC), \
730         KEYMAP_(MAIN), \
731         KEYMAP_(DIFF), \
732         KEYMAP_(LOG), \
733         KEYMAP_(TREE), \
734         KEYMAP_(BLOB), \
735         KEYMAP_(PAGER), \
736         KEYMAP_(HELP) \
738 enum keymap {
739 #define KEYMAP_(name) KEYMAP_##name
740         KEYMAP_INFO
741 #undef  KEYMAP_
742 };
744 static struct int_map keymap_table[] = {
745 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
746         KEYMAP_INFO
747 #undef  KEYMAP_
748 };
750 #define set_keymap(map, name) \
751         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
753 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
755 static void
756 add_keybinding(enum keymap keymap, enum request request, int key)
758         struct keybinding *keybinding;
760         keybinding = calloc(1, sizeof(*keybinding));
761         if (!keybinding)
762                 die("Failed to allocate keybinding");
764         keybinding->alias = key;
765         keybinding->request = request;
766         keybinding->next = keybindings[keymap];
767         keybindings[keymap] = keybinding;
770 /* Looks for a key binding first in the given map, then in the generic map, and
771  * lastly in the default keybindings. */
772 static enum request
773 get_keybinding(enum keymap keymap, int key)
775         struct keybinding *kbd;
776         int i;
778         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
779                 if (kbd->alias == key)
780                         return kbd->request;
782         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
783                 if (kbd->alias == key)
784                         return kbd->request;
786         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
787                 if (default_keybindings[i].alias == key)
788                         return default_keybindings[i].request;
790         return (enum request) key;
794 struct key {
795         char *name;
796         int value;
797 };
799 static struct key key_table[] = {
800         { "Enter",      KEY_RETURN },
801         { "Space",      ' ' },
802         { "Backspace",  KEY_BACKSPACE },
803         { "Tab",        KEY_TAB },
804         { "Escape",     KEY_ESC },
805         { "Left",       KEY_LEFT },
806         { "Right",      KEY_RIGHT },
807         { "Up",         KEY_UP },
808         { "Down",       KEY_DOWN },
809         { "Insert",     KEY_IC },
810         { "Delete",     KEY_DC },
811         { "Hash",       '#' },
812         { "Home",       KEY_HOME },
813         { "End",        KEY_END },
814         { "PageUp",     KEY_PPAGE },
815         { "PageDown",   KEY_NPAGE },
816         { "F1",         KEY_F(1) },
817         { "F2",         KEY_F(2) },
818         { "F3",         KEY_F(3) },
819         { "F4",         KEY_F(4) },
820         { "F5",         KEY_F(5) },
821         { "F6",         KEY_F(6) },
822         { "F7",         KEY_F(7) },
823         { "F8",         KEY_F(8) },
824         { "F9",         KEY_F(9) },
825         { "F10",        KEY_F(10) },
826         { "F11",        KEY_F(11) },
827         { "F12",        KEY_F(12) },
828 };
830 static int
831 get_key_value(const char *name)
833         int i;
835         for (i = 0; i < ARRAY_SIZE(key_table); i++)
836                 if (!strcasecmp(key_table[i].name, name))
837                         return key_table[i].value;
839         if (strlen(name) == 1 && isprint(*name))
840                 return (int) *name;
842         return ERR;
845 static char *
846 get_key(enum request request)
848         static char buf[BUFSIZ];
849         static char key_char[] = "'X'";
850         int pos = 0;
851         char *sep = "    ";
852         int i;
854         buf[pos] = 0;
856         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
857                 struct keybinding *keybinding = &default_keybindings[i];
858                 char *seq = NULL;
859                 int key;
861                 if (keybinding->request != request)
862                         continue;
864                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
865                         if (key_table[key].value == keybinding->alias)
866                                 seq = key_table[key].name;
868                 if (seq == NULL &&
869                     keybinding->alias < 127 &&
870                     isprint(keybinding->alias)) {
871                         key_char[1] = (char) keybinding->alias;
872                         seq = key_char;
873                 }
875                 if (!seq)
876                         seq = "'?'";
878                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
879                         return "Too many keybindings!";
880                 sep = ", ";
881         }
883         return buf;
887 /*
888  * User config file handling.
889  */
891 static struct int_map color_map[] = {
892 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
893         COLOR_MAP(DEFAULT),
894         COLOR_MAP(BLACK),
895         COLOR_MAP(BLUE),
896         COLOR_MAP(CYAN),
897         COLOR_MAP(GREEN),
898         COLOR_MAP(MAGENTA),
899         COLOR_MAP(RED),
900         COLOR_MAP(WHITE),
901         COLOR_MAP(YELLOW),
902 };
904 #define set_color(color, name) \
905         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
907 static struct int_map attr_map[] = {
908 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
909         ATTR_MAP(NORMAL),
910         ATTR_MAP(BLINK),
911         ATTR_MAP(BOLD),
912         ATTR_MAP(DIM),
913         ATTR_MAP(REVERSE),
914         ATTR_MAP(STANDOUT),
915         ATTR_MAP(UNDERLINE),
916 };
918 #define set_attribute(attr, name) \
919         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
921 static int   config_lineno;
922 static bool  config_errors;
923 static char *config_msg;
925 /* Wants: object fgcolor bgcolor [attr] */
926 static int
927 option_color_command(int argc, char *argv[])
929         struct line_info *info;
931         if (argc != 3 && argc != 4) {
932                 config_msg = "Wrong number of arguments given to color command";
933                 return ERR;
934         }
936         info = get_line_info(argv[0], strlen(argv[0]));
937         if (!info) {
938                 config_msg = "Unknown color name";
939                 return ERR;
940         }
942         if (set_color(&info->fg, argv[1]) == ERR ||
943             set_color(&info->bg, argv[2]) == ERR) {
944                 config_msg = "Unknown color";
945                 return ERR;
946         }
948         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
949                 config_msg = "Unknown attribute";
950                 return ERR;
951         }
953         return OK;
956 /* Wants: name = value */
957 static int
958 option_set_command(int argc, char *argv[])
960         if (argc != 3) {
961                 config_msg = "Wrong number of arguments given to set command";
962                 return ERR;
963         }
965         if (strcmp(argv[1], "=")) {
966                 config_msg = "No value assigned";
967                 return ERR;
968         }
970         if (!strcmp(argv[0], "show-rev-graph")) {
971                 opt_rev_graph = (!strcmp(argv[2], "1") ||
972                                  !strcmp(argv[2], "true") ||
973                                  !strcmp(argv[2], "yes"));
974                 return OK;
975         }
977         if (!strcmp(argv[0], "line-number-interval")) {
978                 opt_num_interval = atoi(argv[2]);
979                 return OK;
980         }
982         if (!strcmp(argv[0], "tab-size")) {
983                 opt_tab_size = atoi(argv[2]);
984                 return OK;
985         }
987         if (!strcmp(argv[0], "commit-encoding")) {
988                 char *arg = argv[2];
989                 int delimiter = *arg;
990                 int i;
992                 switch (delimiter) {
993                 case '"':
994                 case '\'':
995                         for (arg++, i = 0; arg[i]; i++)
996                                 if (arg[i] == delimiter) {
997                                         arg[i] = 0;
998                                         break;
999                                 }
1000                 default:
1001                         string_copy(opt_encoding, arg);
1002                         return OK;
1003                 }
1004         }
1006         config_msg = "Unknown variable name";
1007         return ERR;
1010 /* Wants: mode request key */
1011 static int
1012 option_bind_command(int argc, char *argv[])
1014         enum request request;
1015         int keymap;
1016         int key;
1018         if (argc != 3) {
1019                 config_msg = "Wrong number of arguments given to bind command";
1020                 return ERR;
1021         }
1023         if (set_keymap(&keymap, argv[0]) == ERR) {
1024                 config_msg = "Unknown key map";
1025                 return ERR;
1026         }
1028         key = get_key_value(argv[1]);
1029         if (key == ERR) {
1030                 config_msg = "Unknown key";
1031                 return ERR;
1032         }
1034         request = get_request(argv[2]);
1035         if (request == REQ_UNKNOWN) {
1036                 config_msg = "Unknown request name";
1037                 return ERR;
1038         }
1040         add_keybinding(keymap, request, key);
1042         return OK;
1045 static int
1046 set_option(char *opt, char *value)
1048         char *argv[16];
1049         int valuelen;
1050         int argc = 0;
1052         /* Tokenize */
1053         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1054                 argv[argc++] = value;
1056                 value += valuelen;
1057                 if (!*value)
1058                         break;
1060                 *value++ = 0;
1061                 while (isspace(*value))
1062                         value++;
1063         }
1065         if (!strcmp(opt, "color"))
1066                 return option_color_command(argc, argv);
1068         if (!strcmp(opt, "set"))
1069                 return option_set_command(argc, argv);
1071         if (!strcmp(opt, "bind"))
1072                 return option_bind_command(argc, argv);
1074         config_msg = "Unknown option command";
1075         return ERR;
1078 static int
1079 read_option(char *opt, int optlen, char *value, int valuelen)
1081         int status = OK;
1083         config_lineno++;
1084         config_msg = "Internal error";
1086         /* Check for comment markers, since read_properties() will
1087          * only ensure opt and value are split at first " \t". */
1088         optlen = strcspn(opt, "#");
1089         if (optlen == 0)
1090                 return OK;
1092         if (opt[optlen] != 0) {
1093                 config_msg = "No option value";
1094                 status = ERR;
1096         }  else {
1097                 /* Look for comment endings in the value. */
1098                 int len = strcspn(value, "#");
1100                 if (len < valuelen) {
1101                         valuelen = len;
1102                         value[valuelen] = 0;
1103                 }
1105                 status = set_option(opt, value);
1106         }
1108         if (status == ERR) {
1109                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1110                         config_lineno, optlen, opt, config_msg);
1111                 config_errors = TRUE;
1112         }
1114         /* Always keep going if errors are encountered. */
1115         return OK;
1118 static int
1119 load_options(void)
1121         char *home = getenv("HOME");
1122         char buf[SIZEOF_STR];
1123         FILE *file;
1125         config_lineno = 0;
1126         config_errors = FALSE;
1128         if (!home || !string_format(buf, "%s/.tigrc", home))
1129                 return ERR;
1131         /* It's ok that the file doesn't exist. */
1132         file = fopen(buf, "r");
1133         if (!file)
1134                 return OK;
1136         if (read_properties(file, " \t", read_option) == ERR ||
1137             config_errors == TRUE)
1138                 fprintf(stderr, "Errors while loading %s.\n", buf);
1140         return OK;
1144 /*
1145  * The viewer
1146  */
1148 struct view;
1149 struct view_ops;
1151 /* The display array of active views and the index of the current view. */
1152 static struct view *display[2];
1153 static unsigned int current_view;
1155 #define foreach_view(view, i) \
1156         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1158 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1160 /* Current head and commit ID */
1161 static char ref_blob[SIZEOF_REF]        = "";
1162 static char ref_commit[SIZEOF_REF]      = "HEAD";
1163 static char ref_head[SIZEOF_REF]        = "HEAD";
1165 struct view {
1166         const char *name;       /* View name */
1167         const char *cmd_fmt;    /* Default command line format */
1168         const char *cmd_env;    /* Command line set via environment */
1169         const char *id;         /* Points to either of ref_{head,commit,blob} */
1171         struct view_ops *ops;   /* View operations */
1173         enum keymap keymap;     /* What keymap does this view have */
1175         char cmd[SIZEOF_STR];   /* Command buffer */
1176         char ref[SIZEOF_REF];   /* Hovered commit reference */
1177         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1179         int height, width;      /* The width and height of the main window */
1180         WINDOW *win;            /* The main window */
1181         WINDOW *title;          /* The title window living below the main window */
1183         /* Navigation */
1184         unsigned long offset;   /* Offset of the window top */
1185         unsigned long lineno;   /* Current line number */
1187         /* Searching */
1188         char grep[SIZEOF_STR];  /* Search string */
1189         regex_t regex;          /* Pre-compiled regex */
1191         /* If non-NULL, points to the view that opened this view. If this view
1192          * is closed tig will switch back to the parent view. */
1193         struct view *parent;
1195         /* Buffering */
1196         unsigned long lines;    /* Total number of lines */
1197         struct line *line;      /* Line index */
1198         unsigned long line_size;/* Total number of allocated lines */
1199         unsigned int digits;    /* Number of digits in the lines member. */
1201         /* Loading */
1202         FILE *pipe;
1203         time_t start_time;
1204 };
1206 struct view_ops {
1207         /* What type of content being displayed. Used in the title bar. */
1208         const char *type;
1209         /* Draw one line; @lineno must be < view->height. */
1210         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1211         /* Read one line; updates view->line. */
1212         bool (*read)(struct view *view, char *data);
1213         /* Depending on view, change display based on current line. */
1214         bool (*enter)(struct view *view, struct line *line);
1215         /* Search for regex in a line. */
1216         bool (*grep)(struct view *view, struct line *line);
1217 };
1219 static struct view_ops pager_ops;
1220 static struct view_ops main_ops;
1221 static struct view_ops tree_ops;
1222 static struct view_ops blob_ops;
1224 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1225         { name, cmd, #env, ref, ops, map}
1227 #define VIEW_(id, name, ops, ref) \
1228         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1231 static struct view views[] = {
1232         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1233         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1234         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1235         VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
1236         VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
1237         VIEW_(HELP,  "help",  &pager_ops, "static"),
1238         VIEW_(PAGER, "pager", &pager_ops, "static"),
1239 };
1241 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1244 static bool
1245 draw_view_line(struct view *view, unsigned int lineno)
1247         if (view->offset + lineno >= view->lines)
1248                 return FALSE;
1250         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1253 static void
1254 redraw_view_from(struct view *view, int lineno)
1256         assert(0 <= lineno && lineno < view->height);
1258         for (; lineno < view->height; lineno++) {
1259                 if (!draw_view_line(view, lineno))
1260                         break;
1261         }
1263         redrawwin(view->win);
1264         wrefresh(view->win);
1267 static void
1268 redraw_view(struct view *view)
1270         wclear(view->win);
1271         redraw_view_from(view, 0);
1275 static void
1276 update_view_title(struct view *view)
1278         if (view == display[current_view])
1279                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1280         else
1281                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1283         werase(view->title);
1284         wmove(view->title, 0, 0);
1286         if (*view->ref)
1287                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1288         else
1289                 wprintw(view->title, "[%s]", view->name);
1291         if (view->lines || view->pipe) {
1292                 unsigned int view_lines = view->offset + view->height;
1293                 unsigned int lines = view->lines
1294                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1295                                    : 0;
1297                 wprintw(view->title, " - %s %d of %d (%d%%)",
1298                         view->ops->type,
1299                         view->lineno + 1,
1300                         view->lines,
1301                         lines);
1302         }
1304         if (view->pipe) {
1305                 time_t secs = time(NULL) - view->start_time;
1307                 /* Three git seconds are a long time ... */
1308                 if (secs > 2)
1309                         wprintw(view->title, " %lds", secs);
1310         }
1312         wmove(view->title, 0, view->width - 1);
1313         wrefresh(view->title);
1316 static void
1317 resize_display(void)
1319         int offset, i;
1320         struct view *base = display[0];
1321         struct view *view = display[1] ? display[1] : display[0];
1323         /* Setup window dimensions */
1325         getmaxyx(stdscr, base->height, base->width);
1327         /* Make room for the status window. */
1328         base->height -= 1;
1330         if (view != base) {
1331                 /* Horizontal split. */
1332                 view->width   = base->width;
1333                 view->height  = SCALE_SPLIT_VIEW(base->height);
1334                 base->height -= view->height;
1336                 /* Make room for the title bar. */
1337                 view->height -= 1;
1338         }
1340         /* Make room for the title bar. */
1341         base->height -= 1;
1343         offset = 0;
1345         foreach_view (view, i) {
1346                 if (!view->win) {
1347                         view->win = newwin(view->height, 0, offset, 0);
1348                         if (!view->win)
1349                                 die("Failed to create %s view", view->name);
1351                         scrollok(view->win, TRUE);
1353                         view->title = newwin(1, 0, offset + view->height, 0);
1354                         if (!view->title)
1355                                 die("Failed to create title window");
1357                 } else {
1358                         wresize(view->win, view->height, view->width);
1359                         mvwin(view->win,   offset, 0);
1360                         mvwin(view->title, offset + view->height, 0);
1361                 }
1363                 offset += view->height + 1;
1364         }
1367 static void
1368 redraw_display(void)
1370         struct view *view;
1371         int i;
1373         foreach_view (view, i) {
1374                 redraw_view(view);
1375                 update_view_title(view);
1376         }
1379 static void
1380 update_display_cursor(void)
1382         struct view *view = display[current_view];
1384         /* Move the cursor to the right-most column of the cursor line.
1385          *
1386          * XXX: This could turn out to be a bit expensive, but it ensures that
1387          * the cursor does not jump around. */
1388         if (view->lines) {
1389                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1390                 wrefresh(view->win);
1391         }
1394 /*
1395  * Navigation
1396  */
1398 /* Scrolling backend */
1399 static void
1400 do_scroll_view(struct view *view, int lines, bool redraw)
1402         /* The rendering expects the new offset. */
1403         view->offset += lines;
1405         assert(0 <= view->offset && view->offset < view->lines);
1406         assert(lines);
1408         /* Redraw the whole screen if scrolling is pointless. */
1409         if (view->height < ABS(lines)) {
1410                 redraw_view(view);
1412         } else {
1413                 int line = lines > 0 ? view->height - lines : 0;
1414                 int end = line + ABS(lines);
1416                 wscrl(view->win, lines);
1418                 for (; line < end; line++) {
1419                         if (!draw_view_line(view, line))
1420                                 break;
1421                 }
1422         }
1424         /* Move current line into the view. */
1425         if (view->lineno < view->offset) {
1426                 view->lineno = view->offset;
1427                 draw_view_line(view, 0);
1429         } else if (view->lineno >= view->offset + view->height) {
1430                 if (view->lineno == view->offset + view->height) {
1431                         /* Clear the hidden line so it doesn't show if the view
1432                          * is scrolled up. */
1433                         wmove(view->win, view->height, 0);
1434                         wclrtoeol(view->win);
1435                 }
1436                 view->lineno = view->offset + view->height - 1;
1437                 draw_view_line(view, view->lineno - view->offset);
1438         }
1440         assert(view->offset <= view->lineno && view->lineno < view->lines);
1442         if (!redraw)
1443                 return;
1445         redrawwin(view->win);
1446         wrefresh(view->win);
1447         report("");
1450 /* Scroll frontend */
1451 static void
1452 scroll_view(struct view *view, enum request request)
1454         int lines = 1;
1456         switch (request) {
1457         case REQ_SCROLL_PAGE_DOWN:
1458                 lines = view->height;
1459         case REQ_SCROLL_LINE_DOWN:
1460                 if (view->offset + lines > view->lines)
1461                         lines = view->lines - view->offset;
1463                 if (lines == 0 || view->offset + view->height >= view->lines) {
1464                         report("Cannot scroll beyond the last line");
1465                         return;
1466                 }
1467                 break;
1469         case REQ_SCROLL_PAGE_UP:
1470                 lines = view->height;
1471         case REQ_SCROLL_LINE_UP:
1472                 if (lines > view->offset)
1473                         lines = view->offset;
1475                 if (lines == 0) {
1476                         report("Cannot scroll beyond the first line");
1477                         return;
1478                 }
1480                 lines = -lines;
1481                 break;
1483         default:
1484                 die("request %d not handled in switch", request);
1485         }
1487         do_scroll_view(view, lines, TRUE);
1490 /* Cursor moving */
1491 static void
1492 move_view(struct view *view, enum request request, bool redraw)
1494         int steps;
1496         switch (request) {
1497         case REQ_MOVE_FIRST_LINE:
1498                 steps = -view->lineno;
1499                 break;
1501         case REQ_MOVE_LAST_LINE:
1502                 steps = view->lines - view->lineno - 1;
1503                 break;
1505         case REQ_MOVE_PAGE_UP:
1506                 steps = view->height > view->lineno
1507                       ? -view->lineno : -view->height;
1508                 break;
1510         case REQ_MOVE_PAGE_DOWN:
1511                 steps = view->lineno + view->height >= view->lines
1512                       ? view->lines - view->lineno - 1 : view->height;
1513                 break;
1515         case REQ_MOVE_UP:
1516                 steps = -1;
1517                 break;
1519         case REQ_MOVE_DOWN:
1520                 steps = 1;
1521                 break;
1523         default:
1524                 die("request %d not handled in switch", request);
1525         }
1527         if (steps <= 0 && view->lineno == 0) {
1528                 report("Cannot move beyond the first line");
1529                 return;
1531         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1532                 report("Cannot move beyond the last line");
1533                 return;
1534         }
1536         /* Move the current line */
1537         view->lineno += steps;
1538         assert(0 <= view->lineno && view->lineno < view->lines);
1540         /* Repaint the old "current" line if we be scrolling */
1541         if (ABS(steps) < view->height) {
1542                 int prev_lineno = view->lineno - steps - view->offset;
1544                 wmove(view->win, prev_lineno, 0);
1545                 wclrtoeol(view->win);
1546                 draw_view_line(view,  prev_lineno);
1547         }
1549         /* Check whether the view needs to be scrolled */
1550         if (view->lineno < view->offset ||
1551             view->lineno >= view->offset + view->height) {
1552                 if (steps < 0 && -steps > view->offset) {
1553                         steps = -view->offset;
1555                 } else if (steps > 0) {
1556                         if (view->lineno == view->lines - 1 &&
1557                             view->lines > view->height) {
1558                                 steps = view->lines - view->offset - 1;
1559                                 if (steps >= view->height)
1560                                         steps -= view->height - 1;
1561                         }
1562                 }
1564                 do_scroll_view(view, steps, redraw);
1565                 return;
1566         }
1568         /* Draw the current line */
1569         draw_view_line(view, view->lineno - view->offset);
1571         if (!redraw)
1572                 return;
1574         redrawwin(view->win);
1575         wrefresh(view->win);
1576         report("");
1580 /*
1581  * Searching
1582  */
1584 static void search_view(struct view *view, enum request request, const char *search);
1586 static bool
1587 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1589         if (!view->ops->grep(view, line))
1590                 return FALSE;
1592         if (lineno - view->offset >= view->height) {
1593                 view->offset = lineno;
1594                 view->lineno = lineno;
1595                 redraw_view(view);
1597         } else {
1598                 unsigned long old_lineno = view->lineno - view->offset;
1600                 view->lineno = lineno;
1602                 wmove(view->win, old_lineno, 0);
1603                 wclrtoeol(view->win);
1604                 draw_view_line(view, old_lineno);
1606                 draw_view_line(view, view->lineno - view->offset);
1607                 redrawwin(view->win);
1608                 wrefresh(view->win);
1609         }
1611         report("Line %ld matches '%s'", lineno + 1, view->grep);
1612         return TRUE;
1615 static void
1616 find_next(struct view *view, enum request request)
1618         unsigned long lineno = view->lineno;
1619         int direction;
1621         if (!*view->grep) {
1622                 if (!*opt_search)
1623                         report("No previous search");
1624                 else
1625                         search_view(view, request, opt_search);
1626                 return;
1627         }
1629         switch (request) {
1630         case REQ_SEARCH:
1631         case REQ_FIND_NEXT:
1632                 direction = 1;
1633                 break;
1635         case REQ_SEARCH_BACK:
1636         case REQ_FIND_PREV:
1637                 direction = -1;
1638                 break;
1640         default:
1641                 return;
1642         }
1644         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1645                 lineno += direction;
1647         /* Note, lineno is unsigned long so will wrap around in which case it
1648          * will become bigger than view->lines. */
1649         for (; lineno < view->lines; lineno += direction) {
1650                 struct line *line = &view->line[lineno];
1652                 if (find_next_line(view, lineno, line))
1653                         return;
1654         }
1656         report("No match found for '%s'", view->grep);
1659 static void
1660 search_view(struct view *view, enum request request, const char *search)
1662         int regex_err;
1664         if (*view->grep) {
1665                 regfree(&view->regex);
1666                 *view->grep = 0;
1667         }
1669         regex_err = regcomp(&view->regex, search, REG_EXTENDED);
1670         if (regex_err != 0) {
1671                 char buf[SIZEOF_STR] = "unknown error";
1673                 regerror(regex_err, &view->regex, buf, sizeof(buf));
1674                 report("Search failed: %s", buf);;
1675                 return;
1676         }
1678         string_copy(view->grep, search);
1680         find_next(view, request);
1683 /*
1684  * Incremental updating
1685  */
1687 static void
1688 end_update(struct view *view)
1690         if (!view->pipe)
1691                 return;
1692         set_nonblocking_input(FALSE);
1693         if (view->pipe == stdin)
1694                 fclose(view->pipe);
1695         else
1696                 pclose(view->pipe);
1697         view->pipe = NULL;
1700 static bool
1701 begin_update(struct view *view)
1703         const char *id = view->id;
1705         if (view->pipe)
1706                 end_update(view);
1708         if (opt_cmd[0]) {
1709                 string_copy(view->cmd, opt_cmd);
1710                 opt_cmd[0] = 0;
1711                 /* When running random commands, the view ref could have become
1712                  * invalid so clear it. */
1713                 view->ref[0] = 0;
1715         } else if (view == VIEW(REQ_VIEW_TREE)) {
1716                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1718                 if (strcmp(view->vid, view->id))
1719                         opt_path[0] = 0;
1721                 if (snprintf(view->cmd, sizeof(view->cmd), format, id, opt_path)
1722                     >= sizeof(view->cmd))
1723                         return FALSE;
1725         } else {
1726                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1728                 if (!string_format(view->cmd, format, id, id, id, id, id))
1729                         return FALSE;
1730         }
1732         /* Special case for the pager view. */
1733         if (opt_pipe) {
1734                 view->pipe = opt_pipe;
1735                 opt_pipe = NULL;
1736         } else {
1737                 view->pipe = popen(view->cmd, "r");
1738         }
1740         if (!view->pipe)
1741                 return FALSE;
1743         set_nonblocking_input(TRUE);
1745         view->offset = 0;
1746         view->lines  = 0;
1747         view->lineno = 0;
1748         string_copy(view->vid, id);
1750         if (view->line) {
1751                 int i;
1753                 for (i = 0; i < view->lines; i++)
1754                         if (view->line[i].data)
1755                                 free(view->line[i].data);
1757                 free(view->line);
1758                 view->line = NULL;
1759         }
1761         view->start_time = time(NULL);
1763         return TRUE;
1766 static struct line *
1767 realloc_lines(struct view *view, size_t line_size)
1769         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1771         if (!tmp)
1772                 return NULL;
1774         view->line = tmp;
1775         view->line_size = line_size;
1776         return view->line;
1779 static bool
1780 update_view(struct view *view)
1782         char in_buffer[BUFSIZ];
1783         char out_buffer[BUFSIZ * 2];
1784         char *line;
1785         /* The number of lines to read. If too low it will cause too much
1786          * redrawing (and possible flickering), if too high responsiveness
1787          * will suffer. */
1788         unsigned long lines = view->height;
1789         int redraw_from = -1;
1791         if (!view->pipe)
1792                 return TRUE;
1794         /* Only redraw if lines are visible. */
1795         if (view->offset + view->height >= view->lines)
1796                 redraw_from = view->lines - view->offset;
1798         if (!realloc_lines(view, view->lines + lines))
1799                 goto alloc_error;
1801         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1802                 size_t linelen = strlen(line);
1804                 if (linelen)
1805                         line[linelen - 1] = 0;
1807                 if (opt_iconv != ICONV_NONE) {
1808                         char *inbuf = line;
1809                         size_t inlen = linelen;
1811                         char *outbuf = out_buffer;
1812                         size_t outlen = sizeof(out_buffer);
1814                         size_t ret;
1816                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1817                         if (ret != (size_t) -1) {
1818                                 line = out_buffer;
1819                                 linelen = strlen(out_buffer);
1820                         }
1821                 }
1823                 if (!view->ops->read(view, line))
1824                         goto alloc_error;
1826                 if (lines-- == 1)
1827                         break;
1828         }
1830         {
1831                 int digits;
1833                 lines = view->lines;
1834                 for (digits = 0; lines; digits++)
1835                         lines /= 10;
1837                 /* Keep the displayed view in sync with line number scaling. */
1838                 if (digits != view->digits) {
1839                         view->digits = digits;
1840                         redraw_from = 0;
1841                 }
1842         }
1844         if (view == VIEW(REQ_VIEW_TREE)) {
1845                 /* Clear the view and redraw everything since the tree sorting
1846                  * might have rearranged things. */
1847                 redraw_view(view);
1849         } else if (redraw_from >= 0) {
1850                 /* If this is an incremental update, redraw the previous line
1851                  * since for commits some members could have changed when
1852                  * loading the main view. */
1853                 if (redraw_from > 0)
1854                         redraw_from--;
1856                 /* Incrementally draw avoids flickering. */
1857                 redraw_view_from(view, redraw_from);
1858         }
1860         /* Update the title _after_ the redraw so that if the redraw picks up a
1861          * commit reference in view->ref it'll be available here. */
1862         update_view_title(view);
1864         if (ferror(view->pipe)) {
1865                 report("Failed to read: %s", strerror(errno));
1866                 goto end;
1868         } else if (feof(view->pipe)) {
1869                 report("");
1870                 goto end;
1871         }
1873         return TRUE;
1875 alloc_error:
1876         report("Allocation failure");
1878 end:
1879         end_update(view);
1880         return FALSE;
1884 /*
1885  * View opening
1886  */
1888 static void open_help_view(struct view *view)
1890         char buf[BUFSIZ];
1891         int lines = ARRAY_SIZE(req_info) + 2;
1892         int i;
1894         if (view->lines > 0)
1895                 return;
1897         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1898                 if (!req_info[i].request)
1899                         lines++;
1901         view->line = calloc(lines, sizeof(*view->line));
1902         if (!view->line) {
1903                 report("Allocation failure");
1904                 return;
1905         }
1907         view->ops->read(view, "Quick reference for tig keybindings:");
1909         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1910                 char *key;
1912                 if (!req_info[i].request) {
1913                         view->ops->read(view, "");
1914                         view->ops->read(view, req_info[i].help);
1915                         continue;
1916                 }
1918                 key = get_key(req_info[i].request);
1919                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1920                         continue;
1922                 view->ops->read(view, buf);
1923         }
1926 enum open_flags {
1927         OPEN_DEFAULT = 0,       /* Use default view switching. */
1928         OPEN_SPLIT = 1,         /* Split current view. */
1929         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1930         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1931 };
1933 static void
1934 open_view(struct view *prev, enum request request, enum open_flags flags)
1936         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1937         bool split = !!(flags & OPEN_SPLIT);
1938         bool reload = !!(flags & OPEN_RELOAD);
1939         struct view *view = VIEW(request);
1940         int nviews = displayed_views();
1941         struct view *base_view = display[0];
1943         if (view == prev && nviews == 1 && !reload) {
1944                 report("Already in %s view", view->name);
1945                 return;
1946         }
1948         if (view == VIEW(REQ_VIEW_HELP)) {
1949                 open_help_view(view);
1951         } else if ((reload || strcmp(view->vid, view->id)) &&
1952                    !begin_update(view)) {
1953                 report("Failed to load %s view", view->name);
1954                 return;
1955         }
1957         if (split) {
1958                 display[1] = view;
1959                 if (!backgrounded)
1960                         current_view = 1;
1961         } else {
1962                 /* Maximize the current view. */
1963                 memset(display, 0, sizeof(display));
1964                 current_view = 0;
1965                 display[current_view] = view;
1966         }
1968         /* Resize the view when switching between split- and full-screen,
1969          * or when switching between two different full-screen views. */
1970         if (nviews != displayed_views() ||
1971             (nviews == 1 && base_view != display[0]))
1972                 resize_display();
1974         if (split && prev->lineno - prev->offset >= prev->height) {
1975                 /* Take the title line into account. */
1976                 int lines = prev->lineno - prev->offset - prev->height + 1;
1978                 /* Scroll the view that was split if the current line is
1979                  * outside the new limited view. */
1980                 do_scroll_view(prev, lines, TRUE);
1981         }
1983         if (prev && view != prev) {
1984                 if (split && !backgrounded) {
1985                         /* "Blur" the previous view. */
1986                         update_view_title(prev);
1987                 }
1989                 view->parent = prev;
1990         }
1992         if (view->pipe && view->lines == 0) {
1993                 /* Clear the old view and let the incremental updating refill
1994                  * the screen. */
1995                 wclear(view->win);
1996                 report("");
1997         } else {
1998                 redraw_view(view);
1999                 report("");
2000         }
2002         /* If the view is backgrounded the above calls to report()
2003          * won't redraw the view title. */
2004         if (backgrounded)
2005                 update_view_title(view);
2009 /*
2010  * User request switch noodle
2011  */
2013 static int
2014 view_driver(struct view *view, enum request request)
2016         int i;
2018         switch (request) {
2019         case REQ_MOVE_UP:
2020         case REQ_MOVE_DOWN:
2021         case REQ_MOVE_PAGE_UP:
2022         case REQ_MOVE_PAGE_DOWN:
2023         case REQ_MOVE_FIRST_LINE:
2024         case REQ_MOVE_LAST_LINE:
2025                 move_view(view, request, TRUE);
2026                 break;
2028         case REQ_SCROLL_LINE_DOWN:
2029         case REQ_SCROLL_LINE_UP:
2030         case REQ_SCROLL_PAGE_DOWN:
2031         case REQ_SCROLL_PAGE_UP:
2032                 scroll_view(view, request);
2033                 break;
2035         case REQ_VIEW_BLOB:
2036                 if (!ref_blob[0]) {
2037                         report("No file chosen, press 't' to open tree view");
2038                         break;
2039                 }
2040                 /* Fall-through */
2041         case REQ_VIEW_MAIN:
2042         case REQ_VIEW_DIFF:
2043         case REQ_VIEW_LOG:
2044         case REQ_VIEW_TREE:
2045         case REQ_VIEW_HELP:
2046         case REQ_VIEW_PAGER:
2047                 open_view(view, request, OPEN_DEFAULT);
2048                 break;
2050         case REQ_NEXT:
2051         case REQ_PREVIOUS:
2052                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2054                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2055                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2056                    (view == VIEW(REQ_VIEW_BLOB) &&
2057                      view->parent == VIEW(REQ_VIEW_TREE))) {
2058                         bool redraw = display[1] == view;
2060                         view = view->parent;
2061                         move_view(view, request, redraw);
2062                         if (redraw)
2063                                 update_view_title(view);
2064                 } else {
2065                         move_view(view, request, TRUE);
2066                         break;
2067                 }
2068                 /* Fall-through */
2070         case REQ_ENTER:
2071                 if (!view->lines) {
2072                         report("Nothing to enter");
2073                         break;
2074                 }
2075                 return view->ops->enter(view, &view->line[view->lineno]);
2077         case REQ_VIEW_NEXT:
2078         {
2079                 int nviews = displayed_views();
2080                 int next_view = (current_view + 1) % nviews;
2082                 if (next_view == current_view) {
2083                         report("Only one view is displayed");
2084                         break;
2085                 }
2087                 current_view = next_view;
2088                 /* Blur out the title of the previous view. */
2089                 update_view_title(view);
2090                 report("");
2091                 break;
2092         }
2093         case REQ_TOGGLE_LINENO:
2094                 opt_line_number = !opt_line_number;
2095                 redraw_display();
2096                 break;
2098         case REQ_TOGGLE_REV_GRAPH:
2099                 opt_rev_graph = !opt_rev_graph;
2100                 redraw_display();
2101                 break;
2103         case REQ_PROMPT:
2104                 /* Always reload^Wrerun commands from the prompt. */
2105                 open_view(view, opt_request, OPEN_RELOAD);
2106                 break;
2108         case REQ_SEARCH:
2109         case REQ_SEARCH_BACK:
2110                 search_view(view, request, opt_search);
2111                 break;
2113         case REQ_FIND_NEXT:
2114         case REQ_FIND_PREV:
2115                 find_next(view, request);
2116                 break;
2118         case REQ_STOP_LOADING:
2119                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2120                         view = &views[i];
2121                         if (view->pipe)
2122                                 report("Stopped loading the %s view", view->name),
2123                         end_update(view);
2124                 }
2125                 break;
2127         case REQ_SHOW_VERSION:
2128                 report("%s (built %s)", VERSION, __DATE__);
2129                 return TRUE;
2131         case REQ_SCREEN_RESIZE:
2132                 resize_display();
2133                 /* Fall-through */
2134         case REQ_SCREEN_REDRAW:
2135                 redraw_display();
2136                 break;
2138         case REQ_NONE:
2139                 doupdate();
2140                 return TRUE;
2142         case REQ_VIEW_CLOSE:
2143                 /* XXX: Mark closed views by letting view->parent point to the
2144                  * view itself. Parents to closed view should never be
2145                  * followed. */
2146                 if (view->parent &&
2147                     view->parent->parent != view->parent) {
2148                         memset(display, 0, sizeof(display));
2149                         current_view = 0;
2150                         display[current_view] = view->parent;
2151                         view->parent = view;
2152                         resize_display();
2153                         redraw_display();
2154                         break;
2155                 }
2156                 /* Fall-through */
2157         case REQ_QUIT:
2158                 return FALSE;
2160         default:
2161                 /* An unknown key will show most commonly used commands. */
2162                 report("Unknown key, press 'h' for help");
2163                 return TRUE;
2164         }
2166         return TRUE;
2170 /*
2171  * Pager backend
2172  */
2174 static bool
2175 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2177         char *text = line->data;
2178         enum line_type type = line->type;
2179         int textlen = strlen(text);
2180         int attr;
2182         wmove(view->win, lineno, 0);
2184         if (view->offset + lineno == view->lineno) {
2185                 if (type == LINE_COMMIT) {
2186                         string_copy(view->ref, text + 7);
2187                         string_copy(ref_commit, view->ref);
2189                 } else if (type == LINE_TREE_DIR || type == LINE_TREE_FILE) {
2190                         strncpy(view->ref, text + STRING_SIZE("100644 blob "), 41);
2191                         view->ref[40] = 0;
2192                         string_copy(ref_blob, view->ref);
2193                 }
2195                 type = LINE_CURSOR;
2196                 wchgat(view->win, -1, 0, type, NULL);
2197         }
2199         attr = get_line_attr(type);
2200         wattrset(view->win, attr);
2202         if (opt_line_number || opt_tab_size < TABSIZE) {
2203                 static char spaces[] = "                    ";
2204                 int col_offset = 0, col = 0;
2206                 if (opt_line_number) {
2207                         unsigned long real_lineno = view->offset + lineno + 1;
2209                         if (real_lineno == 1 ||
2210                             (real_lineno % opt_num_interval) == 0) {
2211                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2213                         } else {
2214                                 waddnstr(view->win, spaces,
2215                                          MIN(view->digits, STRING_SIZE(spaces)));
2216                         }
2217                         waddstr(view->win, ": ");
2218                         col_offset = view->digits + 2;
2219                 }
2221                 while (text && col_offset + col < view->width) {
2222                         int cols_max = view->width - col_offset - col;
2223                         char *pos = text;
2224                         int cols;
2226                         if (*text == '\t') {
2227                                 text++;
2228                                 assert(sizeof(spaces) > TABSIZE);
2229                                 pos = spaces;
2230                                 cols = opt_tab_size - (col % opt_tab_size);
2232                         } else {
2233                                 text = strchr(text, '\t');
2234                                 cols = line ? text - pos : strlen(pos);
2235                         }
2237                         waddnstr(view->win, pos, MIN(cols, cols_max));
2238                         col += cols;
2239                 }
2241         } else {
2242                 int col = 0, pos = 0;
2244                 for (; pos < textlen && col < view->width; pos++, col++)
2245                         if (text[pos] == '\t')
2246                                 col += TABSIZE - (col % TABSIZE) - 1;
2248                 waddnstr(view->win, text, pos);
2249         }
2251         return TRUE;
2254 static bool
2255 add_describe_ref(char *buf, int *bufpos, char *commit_id, const char *sep)
2257         char refbuf[SIZEOF_STR];
2258         char *ref = NULL;
2259         FILE *pipe;
2261         if (!string_format(refbuf, "git describe %s", commit_id))
2262                 return TRUE;
2264         pipe = popen(refbuf, "r");
2265         if (!pipe)
2266                 return TRUE;
2268         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2269                 ref = chomp_string(ref);
2270         pclose(pipe);
2272         if (!ref || !*ref)
2273                 return TRUE;
2275         /* This is the only fatal call, since it can "corrupt" the buffer. */
2276         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2277                 return FALSE;
2279         return TRUE;
2282 static void
2283 add_pager_refs(struct view *view, struct line *line)
2285         char buf[SIZEOF_STR];
2286         char *commit_id = line->data + STRING_SIZE("commit ");
2287         struct ref **refs;
2288         int bufpos = 0, refpos = 0;
2289         const char *sep = "Refs: ";
2290         bool is_tag = FALSE;
2292         assert(line->type == LINE_COMMIT);
2294         refs = get_refs(commit_id);
2295         if (!refs) {
2296                 if (view == VIEW(REQ_VIEW_DIFF))
2297                         goto try_add_describe_ref;
2298                 return;
2299         }
2301         do {
2302                 struct ref *ref = refs[refpos];
2303                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2305                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2306                         return;
2307                 sep = ", ";
2308                 if (ref->tag)
2309                         is_tag = TRUE;
2310         } while (refs[refpos++]->next);
2312         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2313 try_add_describe_ref:
2314                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2315                         return;
2316         }
2318         if (!realloc_lines(view, view->line_size + 1))
2319                 return;
2321         line = &view->line[view->lines];
2322         line->data = strdup(buf);
2323         if (!line->data)
2324                 return;
2326         line->type = LINE_PP_REFS;
2327         view->lines++;
2330 static bool
2331 pager_read(struct view *view, char *data)
2333         struct line *line = &view->line[view->lines];
2335         line->data = strdup(data);
2336         if (!line->data)
2337                 return FALSE;
2339         line->type = get_line_type(line->data);
2340         view->lines++;
2342         if (line->type == LINE_COMMIT &&
2343             (view == VIEW(REQ_VIEW_DIFF) ||
2344              view == VIEW(REQ_VIEW_LOG)))
2345                 add_pager_refs(view, line);
2347         return TRUE;
2350 static bool
2351 pager_enter(struct view *view, struct line *line)
2353         int split = 0;
2355         if (line->type == LINE_COMMIT &&
2356            (view == VIEW(REQ_VIEW_LOG) ||
2357             view == VIEW(REQ_VIEW_PAGER))) {
2358                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2359                 split = 1;
2360         }
2362         /* Always scroll the view even if it was split. That way
2363          * you can use Enter to scroll through the log view and
2364          * split open each commit diff. */
2365         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2367         /* FIXME: A minor workaround. Scrolling the view will call report("")
2368          * but if we are scrolling a non-current view this won't properly
2369          * update the view title. */
2370         if (split)
2371                 update_view_title(view);
2373         return TRUE;
2376 static bool
2377 pager_grep(struct view *view, struct line *line)
2379         regmatch_t pmatch;
2380         char *text = line->data;
2382         if (!*text)
2383                 return FALSE;
2385         if (regexec(&view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2386                 return FALSE;
2388         return TRUE;
2391 static struct view_ops pager_ops = {
2392         "line",
2393         pager_draw,
2394         pager_read,
2395         pager_enter,
2396         pager_grep,
2397 };
2400 /*
2401  * Tree backend
2402  */
2404 /* Parse output from git ls-tree:
2405  *
2406  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2407  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2408  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2409  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2410  */
2412 #define SIZEOF_TREE_ATTR \
2413         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2415 #define TREE_UP_FORMAT "040000 tree %s\t.."
2417 static int
2418 tree_compare_entry(enum line_type type1, char *name1,
2419                    enum line_type type2, char *name2)
2421         if (type1 != type2) {
2422                 if (type1 == LINE_TREE_DIR)
2423                         return -1;
2424                 return 1;
2425         }
2427         return strcmp(name1, name2);
2430 static bool
2431 tree_read(struct view *view, char *text)
2433         size_t textlen = strlen(text);
2434         char buf[SIZEOF_STR];
2435         unsigned long pos;
2436         enum line_type type;
2438         if (textlen <= SIZEOF_TREE_ATTR)
2439                 return FALSE;
2441         type = text[STRING_SIZE("100644 ")] == 't'
2442              ? LINE_TREE_DIR : LINE_TREE_FILE;
2444         /* The first time around ... */
2445         if (!view->lines) {
2446                 /* Add path info line */
2447                 if (snprintf(buf, sizeof(buf), "Directory path /%s", opt_path) < sizeof(buf) &&
2448                     realloc_lines(view, view->line_size + 1) &&
2449                     pager_read(view, buf))
2450                         view->line[view->lines - 1].type = LINE_DEFAULT;
2451                 else
2452                         return FALSE;
2454                 /* Insert "link" to parent directory. */
2455                 if (*opt_path &&
2456                     snprintf(buf, sizeof(buf), TREE_UP_FORMAT, view->ref) < sizeof(buf) &&
2457                     realloc_lines(view, view->line_size + 1) &&
2458                     pager_read(view, buf))
2459                         view->line[view->lines - 1].type = LINE_TREE_DIR;
2460                 else if (*opt_path)
2461                         return FALSE;
2462         }
2464         /* Strip the path part ... */
2465         if (*opt_path) {
2466                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2467                 size_t striplen = strlen(opt_path);
2468                 char *path = text + SIZEOF_TREE_ATTR;
2470                 if (pathlen > striplen)
2471                         memmove(path, path + striplen,
2472                                 pathlen - striplen + 1);
2473         }
2475         /* Skip "Directory ..." and ".." line. */
2476         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2477                 struct line *line = &view->line[pos];
2478                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2479                 char *path2 = text + SIZEOF_TREE_ATTR;
2480                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2482                 if (cmp <= 0)
2483                         continue;
2485                 text = strdup(text);
2486                 if (!text)
2487                         return FALSE;
2489                 if (view->lines > pos)
2490                         memmove(&view->line[pos + 1], &view->line[pos],
2491                                 (view->lines - pos) * sizeof(*line));
2493                 line = &view->line[pos];
2494                 line->data = text;
2495                 line->type = type;
2496                 view->lines++;
2497                 return TRUE;
2498         }
2500         if (!pager_read(view, text))
2501                 return FALSE;
2503         view->line[view->lines - 1].type = type;
2504         return TRUE;
2507 static bool
2508 tree_enter(struct view *view, struct line *line)
2510         enum open_flags flags = OPEN_DEFAULT;
2511         char *data = line->data;
2512         enum request request;
2514         switch (line->type) {
2515         case LINE_TREE_DIR:
2516                 /* Depending on whether it is a subdir or parent (updir?) link
2517                  * mangle the path buffer. */
2518                 if (line == &view->line[1] && *opt_path) {
2519                         size_t path_len = strlen(opt_path);
2520                         char *dirsep = opt_path + path_len - 1;
2522                         while (dirsep > opt_path && dirsep[-1] != '/')
2523                                 dirsep--;
2525                         dirsep[0] = 0;
2527                 } else {
2528                         int pathlen = strlen(opt_path);
2529                         char *basename = data + SIZEOF_TREE_ATTR;
2531                         string_format_from(opt_path, &pathlen, "%s/", basename);
2532                 }
2534                 /* Trees and subtrees share the same ID, so they are not not
2535                  * unique like blobs. */
2536                 flags |= OPEN_RELOAD;
2537                 request = REQ_VIEW_TREE;
2538                 break;
2540         case LINE_TREE_FILE:
2541                 /* This causes the blob view to become split, and not having it
2542                  * in the tree dir case will make the blob view automatically
2543                  * disappear when moving to a different directory. */
2544                 flags |= OPEN_SPLIT;
2545                 request = REQ_VIEW_BLOB;
2546                 break;
2548         default:
2549                 return TRUE;
2550         }
2552         open_view(view, request, flags);
2554         if (!VIEW(request)->pipe)
2555                 return TRUE;
2557         /* For tree views insert the path to the parent as the first line. */
2558         if (request == REQ_VIEW_BLOB) {
2559                 /* Mirror what is showed in the title bar. */
2560                 string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40);
2561                 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2562                 return TRUE;
2563         }
2565         return TRUE;
2568 static struct view_ops tree_ops = {
2569         "file",
2570         pager_draw,
2571         tree_read,
2572         tree_enter,
2573         pager_grep,
2574 };
2576 static bool
2577 blob_read(struct view *view, char *line)
2579         bool state = pager_read(view, line);
2581         if (state == TRUE)
2582                 view->line[view->lines - 1].type = LINE_DEFAULT;
2584         return state;
2587 static struct view_ops blob_ops = {
2588         "line",
2589         pager_draw,
2590         blob_read,
2591         pager_enter,
2592         pager_grep,
2593 };
2596 /*
2597  * Main view backend
2598  */
2600 struct commit {
2601         char id[41];                    /* SHA1 ID. */
2602         char title[75];                 /* First line of the commit message. */
2603         char author[75];                /* Author of the commit. */
2604         struct tm time;                 /* Date from the author ident. */
2605         struct ref **refs;              /* Repository references. */
2606         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2607         size_t graph_size;              /* The width of the graph array. */
2608 };
2610 static bool
2611 main_draw(struct view *view, struct line *line, unsigned int lineno)
2613         char buf[DATE_COLS + 1];
2614         struct commit *commit = line->data;
2615         enum line_type type;
2616         int col = 0;
2617         size_t timelen;
2618         size_t authorlen;
2619         int trimmed = 1;
2621         if (!*commit->author)
2622                 return FALSE;
2624         wmove(view->win, lineno, col);
2626         if (view->offset + lineno == view->lineno) {
2627                 string_copy(view->ref, commit->id);
2628                 string_copy(ref_commit, view->ref);
2629                 type = LINE_CURSOR;
2630                 wattrset(view->win, get_line_attr(type));
2631                 wchgat(view->win, -1, 0, type, NULL);
2633         } else {
2634                 type = LINE_MAIN_COMMIT;
2635                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2636         }
2638         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2639         waddnstr(view->win, buf, timelen);
2640         waddstr(view->win, " ");
2642         col += DATE_COLS;
2643         wmove(view->win, lineno, col);
2644         if (type != LINE_CURSOR)
2645                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2647         if (opt_utf8) {
2648                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2649         } else {
2650                 authorlen = strlen(commit->author);
2651                 if (authorlen > AUTHOR_COLS - 2) {
2652                         authorlen = AUTHOR_COLS - 2;
2653                         trimmed = 1;
2654                 }
2655         }
2657         if (trimmed) {
2658                 waddnstr(view->win, commit->author, authorlen);
2659                 if (type != LINE_CURSOR)
2660                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2661                 waddch(view->win, '~');
2662         } else {
2663                 waddstr(view->win, commit->author);
2664         }
2666         col += AUTHOR_COLS;
2667         if (type != LINE_CURSOR)
2668                 wattrset(view->win, A_NORMAL);
2670         if (opt_rev_graph && commit->graph_size) {
2671                 size_t i;
2673                 wmove(view->win, lineno, col);
2674                 /* Using waddch() instead of waddnstr() ensures that
2675                  * they'll be rendered correctly for the cursor line. */
2676                 for (i = 0; i < commit->graph_size; i++)
2677                         waddch(view->win, commit->graph[i]);
2679                 col += commit->graph_size + 1;
2680         }
2682         wmove(view->win, lineno, col);
2684         if (commit->refs) {
2685                 size_t i = 0;
2687                 do {
2688                         if (type == LINE_CURSOR)
2689                                 ;
2690                         else if (commit->refs[i]->tag)
2691                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2692                         else
2693                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2694                         waddstr(view->win, "[");
2695                         waddstr(view->win, commit->refs[i]->name);
2696                         waddstr(view->win, "]");
2697                         if (type != LINE_CURSOR)
2698                                 wattrset(view->win, A_NORMAL);
2699                         waddstr(view->win, " ");
2700                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2701                 } while (commit->refs[i++]->next);
2702         }
2704         if (type != LINE_CURSOR)
2705                 wattrset(view->win, get_line_attr(type));
2707         {
2708                 int titlelen = strlen(commit->title);
2710                 if (col + titlelen > view->width)
2711                         titlelen = view->width - col;
2713                 waddnstr(view->win, commit->title, titlelen);
2714         }
2716         return TRUE;
2719 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2720 static bool
2721 main_read(struct view *view, char *line)
2723         enum line_type type = get_line_type(line);
2724         struct commit *commit = view->lines
2725                               ? view->line[view->lines - 1].data : NULL;
2727         switch (type) {
2728         case LINE_COMMIT:
2729                 commit = calloc(1, sizeof(struct commit));
2730                 if (!commit)
2731                         return FALSE;
2733                 line += STRING_SIZE("commit ");
2735                 view->line[view->lines++].data = commit;
2736                 string_copy(commit->id, line);
2737                 commit->refs = get_refs(commit->id);
2738                 commit->graph[commit->graph_size++] = ACS_LTEE;
2739                 break;
2741         case LINE_AUTHOR:
2742         {
2743                 char *ident = line + STRING_SIZE("author ");
2744                 char *end = strchr(ident, '<');
2746                 if (!commit)
2747                         break;
2749                 if (end) {
2750                         char *email = end + 1;
2752                         for (; end > ident && isspace(end[-1]); end--) ;
2754                         if (end == ident && *email) {
2755                                 ident = email;
2756                                 end = strchr(ident, '>');
2757                                 for (; end > ident && isspace(end[-1]); end--) ;
2758                         }
2759                         *end = 0;
2760                 }
2762                 /* End is NULL or ident meaning there's no author. */
2763                 if (end <= ident)
2764                         ident = "Unknown";
2766                 string_copy(commit->author, ident);
2768                 /* Parse epoch and timezone */
2769                 if (end) {
2770                         char *secs = strchr(end + 1, '>');
2771                         char *zone;
2772                         time_t time;
2774                         if (!secs || secs[1] != ' ')
2775                                 break;
2777                         secs += 2;
2778                         time = (time_t) atol(secs);
2779                         zone = strchr(secs, ' ');
2780                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2781                                 long tz;
2783                                 zone++;
2784                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2785                                 tz += ('0' - zone[2]) * 60 * 60;
2786                                 tz += ('0' - zone[3]) * 60;
2787                                 tz += ('0' - zone[4]) * 60;
2789                                 if (zone[0] == '-')
2790                                         tz = -tz;
2792                                 time -= tz;
2793                         }
2794                         gmtime_r(&time, &commit->time);
2795                 }
2796                 break;
2797         }
2798         default:
2799                 if (!commit)
2800                         break;
2802                 /* Fill in the commit title if it has not already been set. */
2803                 if (commit->title[0])
2804                         break;
2806                 /* Require titles to start with a non-space character at the
2807                  * offset used by git log. */
2808                 /* FIXME: More gracefull handling of titles; append "..." to
2809                  * shortened titles, etc. */
2810                 if (strncmp(line, "    ", 4) ||
2811                     isspace(line[4]))
2812                         break;
2814                 string_copy(commit->title, line + 4);
2815         }
2817         return TRUE;
2820 static bool
2821 main_enter(struct view *view, struct line *line)
2823         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2825         open_view(view, REQ_VIEW_DIFF, flags);
2826         return TRUE;
2829 static bool
2830 main_grep(struct view *view, struct line *line)
2832         struct commit *commit = line->data;
2833         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
2834         char buf[DATE_COLS + 1];
2835         regmatch_t pmatch;
2837         for (state = S_TITLE; state < S_END; state++) {
2838                 char *text;
2840                 switch (state) {
2841                 case S_TITLE:   text = commit->title;   break;
2842                 case S_AUTHOR:  text = commit->author;  break;
2843                 case S_DATE:
2844                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
2845                                 continue;
2846                         text = buf;
2847                         break;
2849                 default:
2850                         return FALSE;
2851                 }
2853                 if (regexec(&view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
2854                         return TRUE;
2855         }
2857         return FALSE;
2860 static struct view_ops main_ops = {
2861         "commit",
2862         main_draw,
2863         main_read,
2864         main_enter,
2865         main_grep,
2866 };
2869 /*
2870  * Unicode / UTF-8 handling
2871  *
2872  * NOTE: Much of the following code for dealing with unicode is derived from
2873  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2874  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2875  */
2877 /* I've (over)annotated a lot of code snippets because I am not entirely
2878  * confident that the approach taken by this small UTF-8 interface is correct.
2879  * --jonas */
2881 static inline int
2882 unicode_width(unsigned long c)
2884         if (c >= 0x1100 &&
2885            (c <= 0x115f                         /* Hangul Jamo */
2886             || c == 0x2329
2887             || c == 0x232a
2888             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2889                                                 /* CJK ... Yi */
2890             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2891             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2892             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2893             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2894             || (c >= 0xffe0  && c <= 0xffe6)
2895             || (c >= 0x20000 && c <= 0x2fffd)
2896             || (c >= 0x30000 && c <= 0x3fffd)))
2897                 return 2;
2899         return 1;
2902 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2903  * Illegal bytes are set one. */
2904 static const unsigned char utf8_bytes[256] = {
2905         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2906         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2907         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2908         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2909         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2910         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
2911         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,
2912         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,
2913 };
2915 /* Decode UTF-8 multi-byte representation into a unicode character. */
2916 static inline unsigned long
2917 utf8_to_unicode(const char *string, size_t length)
2919         unsigned long unicode;
2921         switch (length) {
2922         case 1:
2923                 unicode  =   string[0];
2924                 break;
2925         case 2:
2926                 unicode  =  (string[0] & 0x1f) << 6;
2927                 unicode +=  (string[1] & 0x3f);
2928                 break;
2929         case 3:
2930                 unicode  =  (string[0] & 0x0f) << 12;
2931                 unicode += ((string[1] & 0x3f) << 6);
2932                 unicode +=  (string[2] & 0x3f);
2933                 break;
2934         case 4:
2935                 unicode  =  (string[0] & 0x0f) << 18;
2936                 unicode += ((string[1] & 0x3f) << 12);
2937                 unicode += ((string[2] & 0x3f) << 6);
2938                 unicode +=  (string[3] & 0x3f);
2939                 break;
2940         case 5:
2941                 unicode  =  (string[0] & 0x0f) << 24;
2942                 unicode += ((string[1] & 0x3f) << 18);
2943                 unicode += ((string[2] & 0x3f) << 12);
2944                 unicode += ((string[3] & 0x3f) << 6);
2945                 unicode +=  (string[4] & 0x3f);
2946                 break;
2947         case 6:
2948                 unicode  =  (string[0] & 0x01) << 30;
2949                 unicode += ((string[1] & 0x3f) << 24);
2950                 unicode += ((string[2] & 0x3f) << 18);
2951                 unicode += ((string[3] & 0x3f) << 12);
2952                 unicode += ((string[4] & 0x3f) << 6);
2953                 unicode +=  (string[5] & 0x3f);
2954                 break;
2955         default:
2956                 die("Invalid unicode length");
2957         }
2959         /* Invalid characters could return the special 0xfffd value but NUL
2960          * should be just as good. */
2961         return unicode > 0xffff ? 0 : unicode;
2964 /* Calculates how much of string can be shown within the given maximum width
2965  * and sets trimmed parameter to non-zero value if all of string could not be
2966  * shown.
2967  *
2968  * Additionally, adds to coloffset how many many columns to move to align with
2969  * the expected position. Takes into account how multi-byte and double-width
2970  * characters will effect the cursor position.
2971  *
2972  * Returns the number of bytes to output from string to satisfy max_width. */
2973 static size_t
2974 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2976         const char *start = string;
2977         const char *end = strchr(string, '\0');
2978         size_t mbwidth = 0;
2979         size_t width = 0;
2981         *trimmed = 0;
2983         while (string < end) {
2984                 int c = *(unsigned char *) string;
2985                 unsigned char bytes = utf8_bytes[c];
2986                 size_t ucwidth;
2987                 unsigned long unicode;
2989                 if (string + bytes > end)
2990                         break;
2992                 /* Change representation to figure out whether
2993                  * it is a single- or double-width character. */
2995                 unicode = utf8_to_unicode(string, bytes);
2996                 /* FIXME: Graceful handling of invalid unicode character. */
2997                 if (!unicode)
2998                         break;
3000                 ucwidth = unicode_width(unicode);
3001                 width  += ucwidth;
3002                 if (width > max_width) {
3003                         *trimmed = 1;
3004                         break;
3005                 }
3007                 /* The column offset collects the differences between the
3008                  * number of bytes encoding a character and the number of
3009                  * columns will be used for rendering said character.
3010                  *
3011                  * So if some character A is encoded in 2 bytes, but will be
3012                  * represented on the screen using only 1 byte this will and up
3013                  * adding 1 to the multi-byte column offset.
3014                  *
3015                  * Assumes that no double-width character can be encoding in
3016                  * less than two bytes. */
3017                 if (bytes > ucwidth)
3018                         mbwidth += bytes - ucwidth;
3020                 string  += bytes;
3021         }
3023         *coloffset += mbwidth;
3025         return string - start;
3029 /*
3030  * Status management
3031  */
3033 /* Whether or not the curses interface has been initialized. */
3034 static bool cursed = FALSE;
3036 /* The status window is used for polling keystrokes. */
3037 static WINDOW *status_win;
3039 /* Update status and title window. */
3040 static void
3041 report(const char *msg, ...)
3043         static bool empty = TRUE;
3044         struct view *view = display[current_view];
3046         if (!empty || *msg) {
3047                 va_list args;
3049                 va_start(args, msg);
3051                 werase(status_win);
3052                 wmove(status_win, 0, 0);
3053                 if (*msg) {
3054                         vwprintw(status_win, msg, args);
3055                         empty = FALSE;
3056                 } else {
3057                         empty = TRUE;
3058                 }
3059                 wrefresh(status_win);
3061                 va_end(args);
3062         }
3064         update_view_title(view);
3065         update_display_cursor();
3068 /* Controls when nodelay should be in effect when polling user input. */
3069 static void
3070 set_nonblocking_input(bool loading)
3072         static unsigned int loading_views;
3074         if ((loading == FALSE && loading_views-- == 1) ||
3075             (loading == TRUE  && loading_views++ == 0))
3076                 nodelay(status_win, loading);
3079 static void
3080 init_display(void)
3082         int x, y;
3084         /* Initialize the curses library */
3085         if (isatty(STDIN_FILENO)) {
3086                 cursed = !!initscr();
3087         } else {
3088                 /* Leave stdin and stdout alone when acting as a pager. */
3089                 FILE *io = fopen("/dev/tty", "r+");
3091                 if (!io)
3092                         die("Failed to open /dev/tty");
3093                 cursed = !!newterm(NULL, io, io);
3094         }
3096         if (!cursed)
3097                 die("Failed to initialize curses");
3099         nonl();         /* Tell curses not to do NL->CR/NL on output */
3100         cbreak();       /* Take input chars one at a time, no wait for \n */
3101         noecho();       /* Don't echo input */
3102         leaveok(stdscr, TRUE);
3104         if (has_colors())
3105                 init_colors();
3107         getmaxyx(stdscr, y, x);
3108         status_win = newwin(1, 0, y - 1, 0);
3109         if (!status_win)
3110                 die("Failed to create status window");
3112         /* Enable keyboard mapping */
3113         keypad(status_win, TRUE);
3114         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3117 static char *
3118 read_prompt(const char *prompt)
3120         enum { READING, STOP, CANCEL } status = READING;
3121         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3122         int pos = 0;
3124         while (status == READING) {
3125                 struct view *view;
3126                 int i, key;
3128                 foreach_view (view, i)
3129                         update_view(view);
3131                 report("%s%.*s", prompt, pos, buf);
3132                 /* Refresh, accept single keystroke of input */
3133                 key = wgetch(status_win);
3134                 switch (key) {
3135                 case KEY_RETURN:
3136                 case KEY_ENTER:
3137                 case '\n':
3138                         status = pos ? STOP : CANCEL;
3139                         break;
3141                 case KEY_BACKSPACE:
3142                         if (pos > 0)
3143                                 pos--;
3144                         else
3145                                 status = CANCEL;
3146                         break;
3148                 case KEY_ESC:
3149                         status = CANCEL;
3150                         break;
3152                 case ERR:
3153                         break;
3155                 default:
3156                         if (pos >= sizeof(buf)) {
3157                                 report("Input string too long");
3158                                 return NULL;
3159                         }
3161                         if (isprint(key))
3162                                 buf[pos++] = (char) key;
3163                 }
3164         }
3166         if (status == CANCEL) {
3167                 /* Clear the status window */
3168                 report("");
3169                 return NULL;
3170         }
3172         buf[pos++] = 0;
3174         return buf;
3177 /*
3178  * Repository references
3179  */
3181 static struct ref *refs;
3182 static size_t refs_size;
3184 /* Id <-> ref store */
3185 static struct ref ***id_refs;
3186 static size_t id_refs_size;
3188 static struct ref **
3189 get_refs(char *id)
3191         struct ref ***tmp_id_refs;
3192         struct ref **ref_list = NULL;
3193         size_t ref_list_size = 0;
3194         size_t i;
3196         for (i = 0; i < id_refs_size; i++)
3197                 if (!strcmp(id, id_refs[i][0]->id))
3198                         return id_refs[i];
3200         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3201         if (!tmp_id_refs)
3202                 return NULL;
3204         id_refs = tmp_id_refs;
3206         for (i = 0; i < refs_size; i++) {
3207                 struct ref **tmp;
3209                 if (strcmp(id, refs[i].id))
3210                         continue;
3212                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3213                 if (!tmp) {
3214                         if (ref_list)
3215                                 free(ref_list);
3216                         return NULL;
3217                 }
3219                 ref_list = tmp;
3220                 if (ref_list_size > 0)
3221                         ref_list[ref_list_size - 1]->next = 1;
3222                 ref_list[ref_list_size] = &refs[i];
3224                 /* XXX: The properties of the commit chains ensures that we can
3225                  * safely modify the shared ref. The repo references will
3226                  * always be similar for the same id. */
3227                 ref_list[ref_list_size]->next = 0;
3228                 ref_list_size++;
3229         }
3231         if (ref_list)
3232                 id_refs[id_refs_size++] = ref_list;
3234         return ref_list;
3237 static int
3238 read_ref(char *id, int idlen, char *name, int namelen)
3240         struct ref *ref;
3241         bool tag = FALSE;
3243         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3244                 /* Commits referenced by tags has "^{}" appended. */
3245                 if (name[namelen - 1] != '}')
3246                         return OK;
3248                 while (namelen > 0 && name[namelen] != '^')
3249                         namelen--;
3251                 tag = TRUE;
3252                 namelen -= STRING_SIZE("refs/tags/");
3253                 name    += STRING_SIZE("refs/tags/");
3255         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3256                 namelen -= STRING_SIZE("refs/heads/");
3257                 name    += STRING_SIZE("refs/heads/");
3259         } else if (!strcmp(name, "HEAD")) {
3260                 return OK;
3261         }
3263         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3264         if (!refs)
3265                 return ERR;
3267         ref = &refs[refs_size++];
3268         ref->name = malloc(namelen + 1);
3269         if (!ref->name)
3270                 return ERR;
3272         strncpy(ref->name, name, namelen);
3273         ref->name[namelen] = 0;
3274         ref->tag = tag;
3275         string_copy(ref->id, id);
3277         return OK;
3280 static int
3281 load_refs(void)
3283         const char *cmd_env = getenv("TIG_LS_REMOTE");
3284         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3286         return read_properties(popen(cmd, "r"), "\t", read_ref);
3289 static int
3290 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3292         if (!strcmp(name, "i18n.commitencoding"))
3293                 string_copy(opt_encoding, value);
3295         return OK;
3298 static int
3299 load_repo_config(void)
3301         return read_properties(popen("git repo-config --list", "r"),
3302                                "=", read_repo_config_option);
3305 static int
3306 read_properties(FILE *pipe, const char *separators,
3307                 int (*read_property)(char *, int, char *, int))
3309         char buffer[BUFSIZ];
3310         char *name;
3311         int state = OK;
3313         if (!pipe)
3314                 return ERR;
3316         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3317                 char *value;
3318                 size_t namelen;
3319                 size_t valuelen;
3321                 name = chomp_string(name);
3322                 namelen = strcspn(name, separators);
3324                 if (name[namelen]) {
3325                         name[namelen] = 0;
3326                         value = chomp_string(name + namelen + 1);
3327                         valuelen = strlen(value);
3329                 } else {
3330                         value = "";
3331                         valuelen = 0;
3332                 }
3334                 state = read_property(name, namelen, value, valuelen);
3335         }
3337         if (state != ERR && ferror(pipe))
3338                 state = ERR;
3340         pclose(pipe);
3342         return state;
3346 /*
3347  * Main
3348  */
3350 static void __NORETURN
3351 quit(int sig)
3353         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3354         if (cursed)
3355                 endwin();
3356         exit(0);
3359 static void __NORETURN
3360 die(const char *err, ...)
3362         va_list args;
3364         endwin();
3366         va_start(args, err);
3367         fputs("tig: ", stderr);
3368         vfprintf(stderr, err, args);
3369         fputs("\n", stderr);
3370         va_end(args);
3372         exit(1);
3375 int
3376 main(int argc, char *argv[])
3378         struct view *view;
3379         enum request request;
3380         size_t i;
3382         signal(SIGINT, quit);
3384         if (setlocale(LC_ALL, "")) {
3385                 string_copy(opt_codeset, nl_langinfo(CODESET));
3386         }
3388         if (load_options() == ERR)
3389                 die("Failed to load user config.");
3391         /* Load the repo config file so options can be overwritten from
3392          * the command line.  */
3393         if (load_repo_config() == ERR)
3394                 die("Failed to load repo config.");
3396         if (!parse_options(argc, argv))
3397                 return 0;
3399         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3400                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3401                 if (opt_iconv == (iconv_t) -1)
3402                         die("Failed to initialize character set conversion");
3403         }
3405         if (load_refs() == ERR)
3406                 die("Failed to load refs.");
3408         /* Require a git repository unless when running in pager mode. */
3409         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3410                 die("Not a git repository");
3412         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3413                 view->cmd_env = getenv(view->cmd_env);
3415         request = opt_request;
3417         init_display();
3419         while (view_driver(display[current_view], request)) {
3420                 int key;
3421                 int i;
3423                 foreach_view (view, i)
3424                         update_view(view);
3426                 /* Refresh, accept single keystroke of input */
3427                 key = wgetch(status_win);
3429                 request = get_keybinding(display[current_view]->keymap, key);
3431                 /* Some low-level request handling. This keeps access to
3432                  * status_win restricted. */
3433                 switch (request) {
3434                 case REQ_PROMPT:
3435                 {
3436                         char *cmd = read_prompt(":");
3438                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3439                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3440                                         opt_request = REQ_VIEW_DIFF;
3441                                 } else {
3442                                         opt_request = REQ_VIEW_PAGER;
3443                                 }
3444                                 break;
3445                         }
3447                         request = REQ_NONE;
3448                         break;
3449                 }
3450                 case REQ_SEARCH:
3451                 case REQ_SEARCH_BACK:
3452                 {
3453                         const char *prompt = request == REQ_SEARCH
3454                                            ? "/" : "?";
3455                         char *search = read_prompt(prompt);
3457                         if (search)
3458                                 string_copy(opt_search, search);
3459                         else
3460                                 request = REQ_NONE;
3461                         break;
3462                 }
3463                 case REQ_SCREEN_RESIZE:
3464                 {
3465                         int height, width;
3467                         getmaxyx(stdscr, height, width);
3469                         /* Resize the status view and let the view driver take
3470                          * care of resizing the displayed views. */
3471                         wresize(status_win, 1, width);
3472                         mvwin(status_win, height - 1, 0);
3473                         wrefresh(status_win);
3474                         break;
3475                 }
3476                 default:
3477                         break;
3478                 }
3479         }
3481         quit(0);
3483         return 0;