Code

main_read: cleanup and simplify
[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.6.git"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <sys/types.h>
34 #include <regex.h>
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
40 #include <curses.h>
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
55 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
57 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x)  (sizeof(x) - 1)
60 #define SIZEOF_STR      1024    /* Default string size. */
61 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
64 /* Revision graph */
66 #define REVGRAPH_INIT   'I'
67 #define REVGRAPH_MERGE  'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE   '|'
72 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
74 /* This color name can be used to refer to the default term colors. */
75 #define COLOR_DEFAULT   (-1)
77 #define ICONV_NONE      ((iconv_t) -1)
79 /* The format and size of the date column in the main view. */
80 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
81 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
83 #define AUTHOR_COLS     20
85 /* The default interval between line numbers. */
86 #define NUMBER_INTERVAL 1
88 #define TABSIZE         8
90 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
92 #define TIG_LS_REMOTE \
93         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
95 #define TIG_DIFF_CMD \
96         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
98 #define TIG_LOG_CMD     \
99         "git log --cc --stat -n100 %s 2>/dev/null"
101 #define TIG_MAIN_CMD \
102         "git log --topo-order --pretty=raw %s 2>/dev/null"
104 #define TIG_TREE_CMD    \
105         "git ls-tree %s %s"
107 #define TIG_BLOB_CMD    \
108         "git cat-file blob %s"
110 /* XXX: Needs to be defined to the empty string. */
111 #define TIG_HELP_CMD    ""
112 #define TIG_PAGER_CMD   ""
114 /* Some ascii-shorthands fitted into the ncurses namespace. */
115 #define KEY_TAB         '\t'
116 #define KEY_RETURN      '\r'
117 #define KEY_ESC         27
120 struct ref {
121         char *name;             /* Ref name; tag or head names are shortened. */
122         char id[SIZEOF_REV];    /* Commit SHA1 ID */
123         unsigned int tag:1;     /* Is it a tag? */
124         unsigned int remote:1;  /* Is it a remote ref? */
125         unsigned int next:1;    /* For ref lists: are there more refs? */
126 };
128 static struct ref **get_refs(char *id);
130 struct int_map {
131         const char *name;
132         int namelen;
133         int value;
134 };
136 static int
137 set_from_int_map(struct int_map *map, size_t map_size,
138                  int *value, const char *name, int namelen)
141         int i;
143         for (i = 0; i < map_size; i++)
144                 if (namelen == map[i].namelen &&
145                     !strncasecmp(name, map[i].name, namelen)) {
146                         *value = map[i].value;
147                         return OK;
148                 }
150         return ERR;
154 /*
155  * String helpers
156  */
158 static inline void
159 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
161         if (srclen > dstlen - 1)
162                 srclen = dstlen - 1;
164         strncpy(dst, src, srclen);
165         dst[srclen] = 0;
168 /* Shorthands for safely copying into a fixed buffer. */
170 #define string_copy(dst, src) \
171         string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
173 #define string_ncopy(dst, src, srclen) \
174         string_ncopy_do(dst, sizeof(dst), src, srclen)
176 #define string_copy_rev(dst, src) \
177         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
179 static char *
180 chomp_string(char *name)
182         int namelen;
184         while (isspace(*name))
185                 name++;
187         namelen = strlen(name) - 1;
188         while (namelen > 0 && isspace(name[namelen]))
189                 name[namelen--] = 0;
191         return name;
194 static bool
195 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
197         va_list args;
198         size_t pos = bufpos ? *bufpos : 0;
200         va_start(args, fmt);
201         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
202         va_end(args);
204         if (bufpos)
205                 *bufpos = pos;
207         return pos >= bufsize ? FALSE : TRUE;
210 #define string_format(buf, fmt, args...) \
211         string_nformat(buf, sizeof(buf), NULL, fmt, args)
213 #define string_format_from(buf, from, fmt, args...) \
214         string_nformat(buf, sizeof(buf), from, fmt, args)
216 static int
217 string_enum_compare(const char *str1, const char *str2, int len)
219         size_t i;
221 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
223         /* Diff-Header == DIFF_HEADER */
224         for (i = 0; i < len; i++) {
225                 if (toupper(str1[i]) == toupper(str2[i]))
226                         continue;
228                 if (string_enum_sep(str1[i]) &&
229                     string_enum_sep(str2[i]))
230                         continue;
232                 return str1[i] - str2[i];
233         }
235         return 0;
238 /* Shell quoting
239  *
240  * NOTE: The following is a slightly modified copy of the git project's shell
241  * quoting routines found in the quote.c file.
242  *
243  * Help to copy the thing properly quoted for the shell safety.  any single
244  * quote is replaced with '\'', any exclamation point is replaced with '\!',
245  * and the whole thing is enclosed in a
246  *
247  * E.g.
248  *  original     sq_quote     result
249  *  name     ==> name      ==> 'name'
250  *  a b      ==> a b       ==> 'a b'
251  *  a'b      ==> a'\''b    ==> 'a'\''b'
252  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
253  */
255 static size_t
256 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
258         char c;
260 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
262         BUFPUT('\'');
263         while ((c = *src++)) {
264                 if (c == '\'' || c == '!') {
265                         BUFPUT('\'');
266                         BUFPUT('\\');
267                         BUFPUT(c);
268                         BUFPUT('\'');
269                 } else {
270                         BUFPUT(c);
271                 }
272         }
273         BUFPUT('\'');
275         if (bufsize < SIZEOF_STR)
276                 buf[bufsize] = 0;
278         return bufsize;
282 /*
283  * User requests
284  */
286 #define REQ_INFO \
287         /* XXX: Keep the view request first and in sync with views[]. */ \
288         REQ_GROUP("View switching") \
289         REQ_(VIEW_MAIN,         "Show main view"), \
290         REQ_(VIEW_DIFF,         "Show diff view"), \
291         REQ_(VIEW_LOG,          "Show log view"), \
292         REQ_(VIEW_TREE,         "Show tree view"), \
293         REQ_(VIEW_BLOB,         "Show blob view"), \
294         REQ_(VIEW_HELP,         "Show help page"), \
295         REQ_(VIEW_PAGER,        "Show pager view"), \
296         \
297         REQ_GROUP("View manipulation") \
298         REQ_(ENTER,             "Enter current line and scroll"), \
299         REQ_(NEXT,              "Move to next"), \
300         REQ_(PREVIOUS,          "Move to previous"), \
301         REQ_(VIEW_NEXT,         "Move focus to next view"), \
302         REQ_(VIEW_CLOSE,        "Close the current view"), \
303         REQ_(QUIT,              "Close all views and quit"), \
304         \
305         REQ_GROUP("Cursor navigation") \
306         REQ_(MOVE_UP,           "Move cursor one line up"), \
307         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
308         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
309         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
310         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
311         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
312         \
313         REQ_GROUP("Scrolling") \
314         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
315         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
316         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
317         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
318         \
319         REQ_GROUP("Searching") \
320         REQ_(SEARCH,            "Search the view"), \
321         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
322         REQ_(FIND_NEXT,         "Find next search match"), \
323         REQ_(FIND_PREV,         "Find previous search match"), \
324         \
325         REQ_GROUP("Misc") \
326         REQ_(NONE,              "Do nothing"), \
327         REQ_(PROMPT,            "Bring up the prompt"), \
328         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
329         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
330         REQ_(SHOW_VERSION,      "Show version information"), \
331         REQ_(STOP_LOADING,      "Stop all loading views"), \
332         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
333         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
336 /* User action requests. */
337 enum request {
338 #define REQ_GROUP(help)
339 #define REQ_(req, help) REQ_##req
341         /* Offset all requests to avoid conflicts with ncurses getch values. */
342         REQ_OFFSET = KEY_MAX + 1,
343         REQ_INFO,
344         REQ_UNKNOWN,
346 #undef  REQ_GROUP
347 #undef  REQ_
348 };
350 struct request_info {
351         enum request request;
352         char *name;
353         int namelen;
354         char *help;
355 };
357 static struct request_info req_info[] = {
358 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
359 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
360         REQ_INFO
361 #undef  REQ_GROUP
362 #undef  REQ_
363 };
365 static enum request
366 get_request(const char *name)
368         int namelen = strlen(name);
369         int i;
371         for (i = 0; i < ARRAY_SIZE(req_info); i++)
372                 if (req_info[i].namelen == namelen &&
373                     !string_enum_compare(req_info[i].name, name, namelen))
374                         return req_info[i].request;
376         return REQ_UNKNOWN;
380 /*
381  * Options
382  */
384 static const char usage[] =
385 VERSION " (" __DATE__ ")\n"
386 "\n"
387 "Usage: tig [options]\n"
388 "   or: tig [options] [--] [git log options]\n"
389 "   or: tig [options] log  [git log options]\n"
390 "   or: tig [options] diff [git diff options]\n"
391 "   or: tig [options] show [git show options]\n"
392 "   or: tig [options] <    [git command output]\n"
393 "\n"
394 "Options:\n"
395 "  -l                          Start up in log view\n"
396 "  -d                          Start up in diff view\n"
397 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
398 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
399 "  --                          Mark end of tig options\n"
400 "  -v, --version               Show version and exit\n"
401 "  -h, --help                  Show help message and exit\n";
403 /* Option and state variables. */
404 static bool opt_line_number             = FALSE;
405 static bool opt_rev_graph               = FALSE;
406 static int opt_num_interval             = NUMBER_INTERVAL;
407 static int opt_tab_size                 = TABSIZE;
408 static enum request opt_request         = REQ_VIEW_MAIN;
409 static char opt_cmd[SIZEOF_STR]         = "";
410 static char opt_path[SIZEOF_STR]        = "";
411 static FILE *opt_pipe                   = NULL;
412 static char opt_encoding[20]            = "UTF-8";
413 static bool opt_utf8                    = TRUE;
414 static char opt_codeset[20]             = "UTF-8";
415 static iconv_t opt_iconv                = ICONV_NONE;
416 static char opt_search[SIZEOF_STR]      = "";
418 enum option_type {
419         OPT_NONE,
420         OPT_INT,
421 };
423 static bool
424 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
426         va_list args;
427         char *value = "";
428         int *number;
430         if (opt[0] != '-')
431                 return FALSE;
433         if (opt[1] == '-') {
434                 int namelen = strlen(name);
436                 opt += 2;
438                 if (strncmp(opt, name, namelen))
439                         return FALSE;
441                 if (opt[namelen] == '=')
442                         value = opt + namelen + 1;
444         } else {
445                 if (!short_name || opt[1] != short_name)
446                         return FALSE;
447                 value = opt + 2;
448         }
450         va_start(args, type);
451         if (type == OPT_INT) {
452                 number = va_arg(args, int *);
453                 if (isdigit(*value))
454                         *number = atoi(value);
455         }
456         va_end(args);
458         return TRUE;
461 /* Returns the index of log or diff command or -1 to exit. */
462 static bool
463 parse_options(int argc, char *argv[])
465         int i;
467         for (i = 1; i < argc; i++) {
468                 char *opt = argv[i];
470                 if (!strcmp(opt, "log") ||
471                     !strcmp(opt, "diff") ||
472                     !strcmp(opt, "show")) {
473                         opt_request = opt[0] == 'l'
474                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
475                         break;
476                 }
478                 if (opt[0] && opt[0] != '-')
479                         break;
481                 if (!strcmp(opt, "-l")) {
482                         opt_request = REQ_VIEW_LOG;
483                         continue;
484                 }
486                 if (!strcmp(opt, "-d")) {
487                         opt_request = REQ_VIEW_DIFF;
488                         continue;
489                 }
491                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
492                         opt_line_number = TRUE;
493                         continue;
494                 }
496                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
497                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
498                         continue;
499                 }
501                 if (check_option(opt, 'v', "version", OPT_NONE)) {
502                         printf("tig version %s\n", VERSION);
503                         return FALSE;
504                 }
506                 if (check_option(opt, 'h', "help", OPT_NONE)) {
507                         printf(usage);
508                         return FALSE;
509                 }
511                 if (!strcmp(opt, "--")) {
512                         i++;
513                         break;
514                 }
516                 die("unknown option '%s'\n\n%s", opt, usage);
517         }
519         if (!isatty(STDIN_FILENO)) {
520                 opt_request = REQ_VIEW_PAGER;
521                 opt_pipe = stdin;
523         } else if (i < argc) {
524                 size_t buf_size;
526                 if (opt_request == REQ_VIEW_MAIN)
527                         /* XXX: This is vulnerable to the user overriding
528                          * options required for the main view parser. */
529                         string_copy(opt_cmd, "git log --pretty=raw");
530                 else
531                         string_copy(opt_cmd, "git");
532                 buf_size = strlen(opt_cmd);
534                 while (buf_size < sizeof(opt_cmd) && i < argc) {
535                         opt_cmd[buf_size++] = ' ';
536                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
537                 }
539                 if (buf_size >= sizeof(opt_cmd))
540                         die("command too long");
542                 opt_cmd[buf_size] = 0;
543         }
545         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
546                 opt_utf8 = FALSE;
548         return TRUE;
552 /*
553  * Line-oriented content detection.
554  */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
558 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
559 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
560 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
561 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
562 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
568 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
570 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
572 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
573 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
579 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
580 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
581 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
582 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
586 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
587 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
589 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
590 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
591 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
593 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
594 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
595 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
597 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
598 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
600 enum line_type {
601 #define LINE(type, line, fg, bg, attr) \
602         LINE_##type
603         LINE_INFO
604 #undef  LINE
605 };
607 struct line_info {
608         const char *name;       /* Option name. */
609         int namelen;            /* Size of option name. */
610         const char *line;       /* The start of line to match. */
611         int linelen;            /* Size of string to match. */
612         int fg, bg, attr;       /* Color and text attributes for the lines. */
613 };
615 static struct line_info line_info[] = {
616 #define LINE(type, line, fg, bg, attr) \
617         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
618         LINE_INFO
619 #undef  LINE
620 };
622 static enum line_type
623 get_line_type(char *line)
625         int linelen = strlen(line);
626         enum line_type type;
628         for (type = 0; type < ARRAY_SIZE(line_info); type++)
629                 /* Case insensitive search matches Signed-off-by lines better. */
630                 if (linelen >= line_info[type].linelen &&
631                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
632                         return type;
634         return LINE_DEFAULT;
637 static inline int
638 get_line_attr(enum line_type type)
640         assert(type < ARRAY_SIZE(line_info));
641         return COLOR_PAIR(type) | line_info[type].attr;
644 static struct line_info *
645 get_line_info(char *name, int namelen)
647         enum line_type type;
649         for (type = 0; type < ARRAY_SIZE(line_info); type++)
650                 if (namelen == line_info[type].namelen &&
651                     !string_enum_compare(line_info[type].name, name, namelen))
652                         return &line_info[type];
654         return NULL;
657 static void
658 init_colors(void)
660         int default_bg = COLOR_BLACK;
661         int default_fg = COLOR_WHITE;
662         enum line_type type;
664         start_color();
666         if (use_default_colors() != ERR) {
667                 default_bg = -1;
668                 default_fg = -1;
669         }
671         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
672                 struct line_info *info = &line_info[type];
673                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
674                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
676                 init_pair(type, fg, bg);
677         }
680 struct line {
681         enum line_type type;
683         /* State flags */
684         unsigned int selected:1;
686         void *data;             /* User data */
687 };
690 /*
691  * Keys
692  */
694 struct keybinding {
695         int alias;
696         enum request request;
697         struct keybinding *next;
698 };
700 static struct keybinding default_keybindings[] = {
701         /* View switching */
702         { 'm',          REQ_VIEW_MAIN },
703         { 'd',          REQ_VIEW_DIFF },
704         { 'l',          REQ_VIEW_LOG },
705         { 't',          REQ_VIEW_TREE },
706         { 'f',          REQ_VIEW_BLOB },
707         { 'p',          REQ_VIEW_PAGER },
708         { 'h',          REQ_VIEW_HELP },
710         /* View manipulation */
711         { 'q',          REQ_VIEW_CLOSE },
712         { KEY_TAB,      REQ_VIEW_NEXT },
713         { KEY_RETURN,   REQ_ENTER },
714         { KEY_UP,       REQ_PREVIOUS },
715         { KEY_DOWN,     REQ_NEXT },
717         /* Cursor navigation */
718         { 'k',          REQ_MOVE_UP },
719         { 'j',          REQ_MOVE_DOWN },
720         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
721         { KEY_END,      REQ_MOVE_LAST_LINE },
722         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
723         { ' ',          REQ_MOVE_PAGE_DOWN },
724         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
725         { 'b',          REQ_MOVE_PAGE_UP },
726         { '-',          REQ_MOVE_PAGE_UP },
728         /* Scrolling */
729         { KEY_IC,       REQ_SCROLL_LINE_UP },
730         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
731         { 'w',          REQ_SCROLL_PAGE_UP },
732         { 's',          REQ_SCROLL_PAGE_DOWN },
734         /* Searching */
735         { '/',          REQ_SEARCH },
736         { '?',          REQ_SEARCH_BACK },
737         { 'n',          REQ_FIND_NEXT },
738         { 'N',          REQ_FIND_PREV },
740         /* Misc */
741         { 'Q',          REQ_QUIT },
742         { 'z',          REQ_STOP_LOADING },
743         { 'v',          REQ_SHOW_VERSION },
744         { 'r',          REQ_SCREEN_REDRAW },
745         { '.',          REQ_TOGGLE_LINENO },
746         { 'g',          REQ_TOGGLE_REV_GRAPH },
747         { ':',          REQ_PROMPT },
749         /* Using the ncurses SIGWINCH handler. */
750         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
751 };
753 #define KEYMAP_INFO \
754         KEYMAP_(GENERIC), \
755         KEYMAP_(MAIN), \
756         KEYMAP_(DIFF), \
757         KEYMAP_(LOG), \
758         KEYMAP_(TREE), \
759         KEYMAP_(BLOB), \
760         KEYMAP_(PAGER), \
761         KEYMAP_(HELP) \
763 enum keymap {
764 #define KEYMAP_(name) KEYMAP_##name
765         KEYMAP_INFO
766 #undef  KEYMAP_
767 };
769 static struct int_map keymap_table[] = {
770 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
771         KEYMAP_INFO
772 #undef  KEYMAP_
773 };
775 #define set_keymap(map, name) \
776         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
778 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
780 static void
781 add_keybinding(enum keymap keymap, enum request request, int key)
783         struct keybinding *keybinding;
785         keybinding = calloc(1, sizeof(*keybinding));
786         if (!keybinding)
787                 die("Failed to allocate keybinding");
789         keybinding->alias = key;
790         keybinding->request = request;
791         keybinding->next = keybindings[keymap];
792         keybindings[keymap] = keybinding;
795 /* Looks for a key binding first in the given map, then in the generic map, and
796  * lastly in the default keybindings. */
797 static enum request
798 get_keybinding(enum keymap keymap, int key)
800         struct keybinding *kbd;
801         int i;
803         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
804                 if (kbd->alias == key)
805                         return kbd->request;
807         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
808                 if (kbd->alias == key)
809                         return kbd->request;
811         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
812                 if (default_keybindings[i].alias == key)
813                         return default_keybindings[i].request;
815         return (enum request) key;
819 struct key {
820         char *name;
821         int value;
822 };
824 static struct key key_table[] = {
825         { "Enter",      KEY_RETURN },
826         { "Space",      ' ' },
827         { "Backspace",  KEY_BACKSPACE },
828         { "Tab",        KEY_TAB },
829         { "Escape",     KEY_ESC },
830         { "Left",       KEY_LEFT },
831         { "Right",      KEY_RIGHT },
832         { "Up",         KEY_UP },
833         { "Down",       KEY_DOWN },
834         { "Insert",     KEY_IC },
835         { "Delete",     KEY_DC },
836         { "Hash",       '#' },
837         { "Home",       KEY_HOME },
838         { "End",        KEY_END },
839         { "PageUp",     KEY_PPAGE },
840         { "PageDown",   KEY_NPAGE },
841         { "F1",         KEY_F(1) },
842         { "F2",         KEY_F(2) },
843         { "F3",         KEY_F(3) },
844         { "F4",         KEY_F(4) },
845         { "F5",         KEY_F(5) },
846         { "F6",         KEY_F(6) },
847         { "F7",         KEY_F(7) },
848         { "F8",         KEY_F(8) },
849         { "F9",         KEY_F(9) },
850         { "F10",        KEY_F(10) },
851         { "F11",        KEY_F(11) },
852         { "F12",        KEY_F(12) },
853 };
855 static int
856 get_key_value(const char *name)
858         int i;
860         for (i = 0; i < ARRAY_SIZE(key_table); i++)
861                 if (!strcasecmp(key_table[i].name, name))
862                         return key_table[i].value;
864         if (strlen(name) == 1 && isprint(*name))
865                 return (int) *name;
867         return ERR;
870 static char *
871 get_key(enum request request)
873         static char buf[BUFSIZ];
874         static char key_char[] = "'X'";
875         size_t pos = 0;
876         char *sep = "";
877         int i;
879         buf[pos] = 0;
881         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
882                 struct keybinding *keybinding = &default_keybindings[i];
883                 char *seq = NULL;
884                 int key;
886                 if (keybinding->request != request)
887                         continue;
889                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
890                         if (key_table[key].value == keybinding->alias)
891                                 seq = key_table[key].name;
893                 if (seq == NULL &&
894                     keybinding->alias < 127 &&
895                     isprint(keybinding->alias)) {
896                         key_char[1] = (char) keybinding->alias;
897                         seq = key_char;
898                 }
900                 if (!seq)
901                         seq = "'?'";
903                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
904                         return "Too many keybindings!";
905                 sep = ", ";
906         }
908         return buf;
912 /*
913  * User config file handling.
914  */
916 static struct int_map color_map[] = {
917 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
918         COLOR_MAP(DEFAULT),
919         COLOR_MAP(BLACK),
920         COLOR_MAP(BLUE),
921         COLOR_MAP(CYAN),
922         COLOR_MAP(GREEN),
923         COLOR_MAP(MAGENTA),
924         COLOR_MAP(RED),
925         COLOR_MAP(WHITE),
926         COLOR_MAP(YELLOW),
927 };
929 #define set_color(color, name) \
930         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
932 static struct int_map attr_map[] = {
933 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
934         ATTR_MAP(NORMAL),
935         ATTR_MAP(BLINK),
936         ATTR_MAP(BOLD),
937         ATTR_MAP(DIM),
938         ATTR_MAP(REVERSE),
939         ATTR_MAP(STANDOUT),
940         ATTR_MAP(UNDERLINE),
941 };
943 #define set_attribute(attr, name) \
944         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
946 static int   config_lineno;
947 static bool  config_errors;
948 static char *config_msg;
950 /* Wants: object fgcolor bgcolor [attr] */
951 static int
952 option_color_command(int argc, char *argv[])
954         struct line_info *info;
956         if (argc != 3 && argc != 4) {
957                 config_msg = "Wrong number of arguments given to color command";
958                 return ERR;
959         }
961         info = get_line_info(argv[0], strlen(argv[0]));
962         if (!info) {
963                 config_msg = "Unknown color name";
964                 return ERR;
965         }
967         if (set_color(&info->fg, argv[1]) == ERR ||
968             set_color(&info->bg, argv[2]) == ERR) {
969                 config_msg = "Unknown color";
970                 return ERR;
971         }
973         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
974                 config_msg = "Unknown attribute";
975                 return ERR;
976         }
978         return OK;
981 /* Wants: name = value */
982 static int
983 option_set_command(int argc, char *argv[])
985         if (argc != 3) {
986                 config_msg = "Wrong number of arguments given to set command";
987                 return ERR;
988         }
990         if (strcmp(argv[1], "=")) {
991                 config_msg = "No value assigned";
992                 return ERR;
993         }
995         if (!strcmp(argv[0], "show-rev-graph")) {
996                 opt_rev_graph = (!strcmp(argv[2], "1") ||
997                                  !strcmp(argv[2], "true") ||
998                                  !strcmp(argv[2], "yes"));
999                 return OK;
1000         }
1002         if (!strcmp(argv[0], "line-number-interval")) {
1003                 opt_num_interval = atoi(argv[2]);
1004                 return OK;
1005         }
1007         if (!strcmp(argv[0], "tab-size")) {
1008                 opt_tab_size = atoi(argv[2]);
1009                 return OK;
1010         }
1012         if (!strcmp(argv[0], "commit-encoding")) {
1013                 char *arg = argv[2];
1014                 int delimiter = *arg;
1015                 int i;
1017                 switch (delimiter) {
1018                 case '"':
1019                 case '\'':
1020                         for (arg++, i = 0; arg[i]; i++)
1021                                 if (arg[i] == delimiter) {
1022                                         arg[i] = 0;
1023                                         break;
1024                                 }
1025                 default:
1026                         string_copy(opt_encoding, arg);
1027                         return OK;
1028                 }
1029         }
1031         config_msg = "Unknown variable name";
1032         return ERR;
1035 /* Wants: mode request key */
1036 static int
1037 option_bind_command(int argc, char *argv[])
1039         enum request request;
1040         int keymap;
1041         int key;
1043         if (argc != 3) {
1044                 config_msg = "Wrong number of arguments given to bind command";
1045                 return ERR;
1046         }
1048         if (set_keymap(&keymap, argv[0]) == ERR) {
1049                 config_msg = "Unknown key map";
1050                 return ERR;
1051         }
1053         key = get_key_value(argv[1]);
1054         if (key == ERR) {
1055                 config_msg = "Unknown key";
1056                 return ERR;
1057         }
1059         request = get_request(argv[2]);
1060         if (request == REQ_UNKNOWN) {
1061                 config_msg = "Unknown request name";
1062                 return ERR;
1063         }
1065         add_keybinding(keymap, request, key);
1067         return OK;
1070 static int
1071 set_option(char *opt, char *value)
1073         char *argv[16];
1074         int valuelen;
1075         int argc = 0;
1077         /* Tokenize */
1078         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1079                 argv[argc++] = value;
1081                 value += valuelen;
1082                 if (!*value)
1083                         break;
1085                 *value++ = 0;
1086                 while (isspace(*value))
1087                         value++;
1088         }
1090         if (!strcmp(opt, "color"))
1091                 return option_color_command(argc, argv);
1093         if (!strcmp(opt, "set"))
1094                 return option_set_command(argc, argv);
1096         if (!strcmp(opt, "bind"))
1097                 return option_bind_command(argc, argv);
1099         config_msg = "Unknown option command";
1100         return ERR;
1103 static int
1104 read_option(char *opt, int optlen, char *value, int valuelen)
1106         int status = OK;
1108         config_lineno++;
1109         config_msg = "Internal error";
1111         /* Check for comment markers, since read_properties() will
1112          * only ensure opt and value are split at first " \t". */
1113         optlen = strcspn(opt, "#");
1114         if (optlen == 0)
1115                 return OK;
1117         if (opt[optlen] != 0) {
1118                 config_msg = "No option value";
1119                 status = ERR;
1121         }  else {
1122                 /* Look for comment endings in the value. */
1123                 int len = strcspn(value, "#");
1125                 if (len < valuelen) {
1126                         valuelen = len;
1127                         value[valuelen] = 0;
1128                 }
1130                 status = set_option(opt, value);
1131         }
1133         if (status == ERR) {
1134                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1135                         config_lineno, optlen, opt, config_msg);
1136                 config_errors = TRUE;
1137         }
1139         /* Always keep going if errors are encountered. */
1140         return OK;
1143 static int
1144 load_options(void)
1146         char *home = getenv("HOME");
1147         char buf[SIZEOF_STR];
1148         FILE *file;
1150         config_lineno = 0;
1151         config_errors = FALSE;
1153         if (!home || !string_format(buf, "%s/.tigrc", home))
1154                 return ERR;
1156         /* It's ok that the file doesn't exist. */
1157         file = fopen(buf, "r");
1158         if (!file)
1159                 return OK;
1161         if (read_properties(file, " \t", read_option) == ERR ||
1162             config_errors == TRUE)
1163                 fprintf(stderr, "Errors while loading %s.\n", buf);
1165         return OK;
1169 /*
1170  * The viewer
1171  */
1173 struct view;
1174 struct view_ops;
1176 /* The display array of active views and the index of the current view. */
1177 static struct view *display[2];
1178 static unsigned int current_view;
1180 /* Reading from the prompt? */
1181 static bool input_mode = FALSE;
1183 #define foreach_displayed_view(view, i) \
1184         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1186 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1188 /* Current head and commit ID */
1189 static char ref_blob[SIZEOF_REF]        = "";
1190 static char ref_commit[SIZEOF_REF]      = "HEAD";
1191 static char ref_head[SIZEOF_REF]        = "HEAD";
1193 struct view {
1194         const char *name;       /* View name */
1195         const char *cmd_fmt;    /* Default command line format */
1196         const char *cmd_env;    /* Command line set via environment */
1197         const char *id;         /* Points to either of ref_{head,commit,blob} */
1199         struct view_ops *ops;   /* View operations */
1201         enum keymap keymap;     /* What keymap does this view have */
1203         char cmd[SIZEOF_STR];   /* Command buffer */
1204         char ref[SIZEOF_REF];   /* Hovered commit reference */
1205         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1207         int height, width;      /* The width and height of the main window */
1208         WINDOW *win;            /* The main window */
1209         WINDOW *title;          /* The title window living below the main window */
1211         /* Navigation */
1212         unsigned long offset;   /* Offset of the window top */
1213         unsigned long lineno;   /* Current line number */
1215         /* Searching */
1216         char grep[SIZEOF_STR];  /* Search string */
1217         regex_t *regex;         /* Pre-compiled regex */
1219         /* If non-NULL, points to the view that opened this view. If this view
1220          * is closed tig will switch back to the parent view. */
1221         struct view *parent;
1223         /* Buffering */
1224         unsigned long lines;    /* Total number of lines */
1225         struct line *line;      /* Line index */
1226         unsigned long line_size;/* Total number of allocated lines */
1227         unsigned int digits;    /* Number of digits in the lines member. */
1229         /* Loading */
1230         FILE *pipe;
1231         time_t start_time;
1232 };
1234 struct view_ops {
1235         /* What type of content being displayed. Used in the title bar. */
1236         const char *type;
1237         /* Open and reads in all view content. */
1238         bool (*open)(struct view *view);
1239         /* Read one line; updates view->line. */
1240         bool (*read)(struct view *view, char *data);
1241         /* Draw one line; @lineno must be < view->height. */
1242         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1243         /* Depending on view, change display based on current line. */
1244         bool (*enter)(struct view *view, struct line *line);
1245         /* Search for regex in a line. */
1246         bool (*grep)(struct view *view, struct line *line);
1247         /* Select line */
1248         void (*select)(struct view *view, struct line *line);
1249 };
1251 static struct view_ops pager_ops;
1252 static struct view_ops main_ops;
1253 static struct view_ops tree_ops;
1254 static struct view_ops blob_ops;
1255 static struct view_ops help_ops;
1257 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1258         { name, cmd, #env, ref, ops, map}
1260 #define VIEW_(id, name, ops, ref) \
1261         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1264 static struct view views[] = {
1265         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1266         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1267         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1268         VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
1269         VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
1270         VIEW_(HELP,  "help",  &help_ops,  ""),
1271         VIEW_(PAGER, "pager", &pager_ops, ""),
1272 };
1274 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1276 #define foreach_view(view, i) \
1277         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1279 #define view_is_displayed(view) \
1280         (view == display[0] || view == display[1])
1282 static bool
1283 draw_view_line(struct view *view, unsigned int lineno)
1285         struct line *line;
1286         bool selected = (view->offset + lineno == view->lineno);
1287         bool draw_ok;
1289         assert(view_is_displayed(view));
1291         if (view->offset + lineno >= view->lines)
1292                 return FALSE;
1294         line = &view->line[view->offset + lineno];
1296         if (selected) {
1297                 line->selected = TRUE;
1298                 view->ops->select(view, line);
1299         } else if (line->selected) {
1300                 line->selected = FALSE;
1301                 wmove(view->win, lineno, 0);
1302                 wclrtoeol(view->win);
1303         }
1305         scrollok(view->win, FALSE);
1306         draw_ok = view->ops->draw(view, line, lineno, selected);
1307         scrollok(view->win, TRUE);
1309         return draw_ok;
1312 static void
1313 redraw_view_from(struct view *view, int lineno)
1315         assert(0 <= lineno && lineno < view->height);
1317         for (; lineno < view->height; lineno++) {
1318                 if (!draw_view_line(view, lineno))
1319                         break;
1320         }
1322         redrawwin(view->win);
1323         if (input_mode)
1324                 wnoutrefresh(view->win);
1325         else
1326                 wrefresh(view->win);
1329 static void
1330 redraw_view(struct view *view)
1332         wclear(view->win);
1333         redraw_view_from(view, 0);
1337 static void
1338 update_view_title(struct view *view)
1340         char buf[SIZEOF_STR];
1341         char state[SIZEOF_STR];
1342         size_t bufpos = 0, statelen = 0;
1344         assert(view_is_displayed(view));
1346         if (view->lines || view->pipe) {
1347                 unsigned int view_lines = view->offset + view->height;
1348                 unsigned int lines = view->lines
1349                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1350                                    : 0;
1352                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1353                                    view->ops->type,
1354                                    view->lineno + 1,
1355                                    view->lines,
1356                                    lines);
1358                 if (view->pipe) {
1359                         time_t secs = time(NULL) - view->start_time;
1361                         /* Three git seconds are a long time ... */
1362                         if (secs > 2)
1363                                 string_format_from(state, &statelen, " %lds", secs);
1364                 }
1365         }
1367         string_format_from(buf, &bufpos, "[%s]", view->name);
1368         if (*view->ref && bufpos < view->width) {
1369                 size_t refsize = strlen(view->ref);
1370                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1372                 if (minsize < view->width)
1373                         refsize = view->width - minsize + 7;
1374                 string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
1375         }
1377         if (statelen && bufpos < view->width) {
1378                 string_format_from(buf, &bufpos, " %s", state);
1379         }
1381         if (view == display[current_view])
1382                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1383         else
1384                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1386         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1387         wclrtoeol(view->title);
1388         wmove(view->title, 0, view->width - 1);
1390         if (input_mode)
1391                 wnoutrefresh(view->title);
1392         else
1393                 wrefresh(view->title);
1396 static void
1397 resize_display(void)
1399         int offset, i;
1400         struct view *base = display[0];
1401         struct view *view = display[1] ? display[1] : display[0];
1403         /* Setup window dimensions */
1405         getmaxyx(stdscr, base->height, base->width);
1407         /* Make room for the status window. */
1408         base->height -= 1;
1410         if (view != base) {
1411                 /* Horizontal split. */
1412                 view->width   = base->width;
1413                 view->height  = SCALE_SPLIT_VIEW(base->height);
1414                 base->height -= view->height;
1416                 /* Make room for the title bar. */
1417                 view->height -= 1;
1418         }
1420         /* Make room for the title bar. */
1421         base->height -= 1;
1423         offset = 0;
1425         foreach_displayed_view (view, i) {
1426                 if (!view->win) {
1427                         view->win = newwin(view->height, 0, offset, 0);
1428                         if (!view->win)
1429                                 die("Failed to create %s view", view->name);
1431                         scrollok(view->win, TRUE);
1433                         view->title = newwin(1, 0, offset + view->height, 0);
1434                         if (!view->title)
1435                                 die("Failed to create title window");
1437                 } else {
1438                         wresize(view->win, view->height, view->width);
1439                         mvwin(view->win,   offset, 0);
1440                         mvwin(view->title, offset + view->height, 0);
1441                 }
1443                 offset += view->height + 1;
1444         }
1447 static void
1448 redraw_display(void)
1450         struct view *view;
1451         int i;
1453         foreach_displayed_view (view, i) {
1454                 redraw_view(view);
1455                 update_view_title(view);
1456         }
1459 static void
1460 update_display_cursor(struct view *view)
1462         /* Move the cursor to the right-most column of the cursor line.
1463          *
1464          * XXX: This could turn out to be a bit expensive, but it ensures that
1465          * the cursor does not jump around. */
1466         if (view->lines) {
1467                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1468                 wrefresh(view->win);
1469         }
1472 /*
1473  * Navigation
1474  */
1476 /* Scrolling backend */
1477 static void
1478 do_scroll_view(struct view *view, int lines)
1480         bool redraw_current_line = FALSE;
1482         /* The rendering expects the new offset. */
1483         view->offset += lines;
1485         assert(0 <= view->offset && view->offset < view->lines);
1486         assert(lines);
1488         /* Move current line into the view. */
1489         if (view->lineno < view->offset) {
1490                 view->lineno = view->offset;
1491                 redraw_current_line = TRUE;
1492         } else if (view->lineno >= view->offset + view->height) {
1493                 view->lineno = view->offset + view->height - 1;
1494                 redraw_current_line = TRUE;
1495         }
1497         assert(view->offset <= view->lineno && view->lineno < view->lines);
1499         /* Redraw the whole screen if scrolling is pointless. */
1500         if (view->height < ABS(lines)) {
1501                 redraw_view(view);
1503         } else {
1504                 int line = lines > 0 ? view->height - lines : 0;
1505                 int end = line + ABS(lines);
1507                 wscrl(view->win, lines);
1509                 for (; line < end; line++) {
1510                         if (!draw_view_line(view, line))
1511                                 break;
1512                 }
1514                 if (redraw_current_line)
1515                         draw_view_line(view, view->lineno - view->offset);
1516         }
1518         redrawwin(view->win);
1519         wrefresh(view->win);
1520         report("");
1523 /* Scroll frontend */
1524 static void
1525 scroll_view(struct view *view, enum request request)
1527         int lines = 1;
1529         assert(view_is_displayed(view));
1531         switch (request) {
1532         case REQ_SCROLL_PAGE_DOWN:
1533                 lines = view->height;
1534         case REQ_SCROLL_LINE_DOWN:
1535                 if (view->offset + lines > view->lines)
1536                         lines = view->lines - view->offset;
1538                 if (lines == 0 || view->offset + view->height >= view->lines) {
1539                         report("Cannot scroll beyond the last line");
1540                         return;
1541                 }
1542                 break;
1544         case REQ_SCROLL_PAGE_UP:
1545                 lines = view->height;
1546         case REQ_SCROLL_LINE_UP:
1547                 if (lines > view->offset)
1548                         lines = view->offset;
1550                 if (lines == 0) {
1551                         report("Cannot scroll beyond the first line");
1552                         return;
1553                 }
1555                 lines = -lines;
1556                 break;
1558         default:
1559                 die("request %d not handled in switch", request);
1560         }
1562         do_scroll_view(view, lines);
1565 /* Cursor moving */
1566 static void
1567 move_view(struct view *view, enum request request)
1569         int scroll_steps = 0;
1570         int steps;
1572         switch (request) {
1573         case REQ_MOVE_FIRST_LINE:
1574                 steps = -view->lineno;
1575                 break;
1577         case REQ_MOVE_LAST_LINE:
1578                 steps = view->lines - view->lineno - 1;
1579                 break;
1581         case REQ_MOVE_PAGE_UP:
1582                 steps = view->height > view->lineno
1583                       ? -view->lineno : -view->height;
1584                 break;
1586         case REQ_MOVE_PAGE_DOWN:
1587                 steps = view->lineno + view->height >= view->lines
1588                       ? view->lines - view->lineno - 1 : view->height;
1589                 break;
1591         case REQ_MOVE_UP:
1592                 steps = -1;
1593                 break;
1595         case REQ_MOVE_DOWN:
1596                 steps = 1;
1597                 break;
1599         default:
1600                 die("request %d not handled in switch", request);
1601         }
1603         if (steps <= 0 && view->lineno == 0) {
1604                 report("Cannot move beyond the first line");
1605                 return;
1607         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1608                 report("Cannot move beyond the last line");
1609                 return;
1610         }
1612         /* Move the current line */
1613         view->lineno += steps;
1614         assert(0 <= view->lineno && view->lineno < view->lines);
1616         /* Check whether the view needs to be scrolled */
1617         if (view->lineno < view->offset ||
1618             view->lineno >= view->offset + view->height) {
1619                 scroll_steps = steps;
1620                 if (steps < 0 && -steps > view->offset) {
1621                         scroll_steps = -view->offset;
1623                 } else if (steps > 0) {
1624                         if (view->lineno == view->lines - 1 &&
1625                             view->lines > view->height) {
1626                                 scroll_steps = view->lines - view->offset - 1;
1627                                 if (scroll_steps >= view->height)
1628                                         scroll_steps -= view->height - 1;
1629                         }
1630                 }
1631         }
1633         if (!view_is_displayed(view)) {
1634                 view->offset += scroll_steps;
1635                 assert(0 <= view->offset && view->offset < view->lines);
1636                 view->ops->select(view, &view->line[view->lineno]);
1637                 return;
1638         }
1640         /* Repaint the old "current" line if we be scrolling */
1641         if (ABS(steps) < view->height)
1642                 draw_view_line(view, view->lineno - steps - view->offset);
1644         if (scroll_steps) {
1645                 do_scroll_view(view, scroll_steps);
1646                 return;
1647         }
1649         /* Draw the current line */
1650         draw_view_line(view, view->lineno - view->offset);
1652         redrawwin(view->win);
1653         wrefresh(view->win);
1654         report("");
1658 /*
1659  * Searching
1660  */
1662 static void search_view(struct view *view, enum request request);
1664 static bool
1665 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1667         assert(view_is_displayed(view));
1669         if (!view->ops->grep(view, line))
1670                 return FALSE;
1672         if (lineno - view->offset >= view->height) {
1673                 view->offset = lineno;
1674                 view->lineno = lineno;
1675                 redraw_view(view);
1677         } else {
1678                 unsigned long old_lineno = view->lineno - view->offset;
1680                 view->lineno = lineno;
1681                 draw_view_line(view, old_lineno);
1683                 draw_view_line(view, view->lineno - view->offset);
1684                 redrawwin(view->win);
1685                 wrefresh(view->win);
1686         }
1688         report("Line %ld matches '%s'", lineno + 1, view->grep);
1689         return TRUE;
1692 static void
1693 find_next(struct view *view, enum request request)
1695         unsigned long lineno = view->lineno;
1696         int direction;
1698         if (!*view->grep) {
1699                 if (!*opt_search)
1700                         report("No previous search");
1701                 else
1702                         search_view(view, request);
1703                 return;
1704         }
1706         switch (request) {
1707         case REQ_SEARCH:
1708         case REQ_FIND_NEXT:
1709                 direction = 1;
1710                 break;
1712         case REQ_SEARCH_BACK:
1713         case REQ_FIND_PREV:
1714                 direction = -1;
1715                 break;
1717         default:
1718                 return;
1719         }
1721         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1722                 lineno += direction;
1724         /* Note, lineno is unsigned long so will wrap around in which case it
1725          * will become bigger than view->lines. */
1726         for (; lineno < view->lines; lineno += direction) {
1727                 struct line *line = &view->line[lineno];
1729                 if (find_next_line(view, lineno, line))
1730                         return;
1731         }
1733         report("No match found for '%s'", view->grep);
1736 static void
1737 search_view(struct view *view, enum request request)
1739         int regex_err;
1741         if (view->regex) {
1742                 regfree(view->regex);
1743                 *view->grep = 0;
1744         } else {
1745                 view->regex = calloc(1, sizeof(*view->regex));
1746                 if (!view->regex)
1747                         return;
1748         }
1750         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1751         if (regex_err != 0) {
1752                 char buf[SIZEOF_STR] = "unknown error";
1754                 regerror(regex_err, view->regex, buf, sizeof(buf));
1755                 report("Search failed: %s", buf);
1756                 return;
1757         }
1759         string_copy(view->grep, opt_search);
1761         find_next(view, request);
1764 /*
1765  * Incremental updating
1766  */
1768 static void
1769 end_update(struct view *view)
1771         if (!view->pipe)
1772                 return;
1773         set_nonblocking_input(FALSE);
1774         if (view->pipe == stdin)
1775                 fclose(view->pipe);
1776         else
1777                 pclose(view->pipe);
1778         view->pipe = NULL;
1781 static bool
1782 begin_update(struct view *view)
1784         const char *id = view->id;
1786         if (view->pipe)
1787                 end_update(view);
1789         if (opt_cmd[0]) {
1790                 string_copy(view->cmd, opt_cmd);
1791                 opt_cmd[0] = 0;
1792                 /* When running random commands, initially show the
1793                  * command in the title. However, it maybe later be
1794                  * overwritten if a commit line is selected. */
1795                 string_copy(view->ref, view->cmd);
1797         } else if (view == VIEW(REQ_VIEW_TREE)) {
1798                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1799                 char path[SIZEOF_STR];
1801                 if (strcmp(view->vid, view->id))
1802                         opt_path[0] = path[0] = 0;
1803                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1804                         return FALSE;
1806                 if (!string_format(view->cmd, format, id, path))
1807                         return FALSE;
1809         } else {
1810                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1812                 if (!string_format(view->cmd, format, id, id, id, id, id))
1813                         return FALSE;
1815                 /* Put the current ref_* value to the view title ref
1816                  * member. This is needed by the blob view. Most other
1817                  * views sets it automatically after loading because the
1818                  * first line is a commit line. */
1819                 string_copy(view->ref, id);
1820         }
1822         /* Special case for the pager view. */
1823         if (opt_pipe) {
1824                 view->pipe = opt_pipe;
1825                 opt_pipe = NULL;
1826         } else {
1827                 view->pipe = popen(view->cmd, "r");
1828         }
1830         if (!view->pipe)
1831                 return FALSE;
1833         set_nonblocking_input(TRUE);
1835         view->offset = 0;
1836         view->lines  = 0;
1837         view->lineno = 0;
1838         string_copy_rev(view->vid, id);
1840         if (view->line) {
1841                 int i;
1843                 for (i = 0; i < view->lines; i++)
1844                         if (view->line[i].data)
1845                                 free(view->line[i].data);
1847                 free(view->line);
1848                 view->line = NULL;
1849         }
1851         view->start_time = time(NULL);
1853         return TRUE;
1856 static struct line *
1857 realloc_lines(struct view *view, size_t line_size)
1859         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1861         if (!tmp)
1862                 return NULL;
1864         view->line = tmp;
1865         view->line_size = line_size;
1866         return view->line;
1869 static bool
1870 update_view(struct view *view)
1872         char in_buffer[BUFSIZ];
1873         char out_buffer[BUFSIZ * 2];
1874         char *line;
1875         /* The number of lines to read. If too low it will cause too much
1876          * redrawing (and possible flickering), if too high responsiveness
1877          * will suffer. */
1878         unsigned long lines = view->height;
1879         int redraw_from = -1;
1881         if (!view->pipe)
1882                 return TRUE;
1884         /* Only redraw if lines are visible. */
1885         if (view->offset + view->height >= view->lines)
1886                 redraw_from = view->lines - view->offset;
1888         /* FIXME: This is probably not perfect for backgrounded views. */
1889         if (!realloc_lines(view, view->lines + lines))
1890                 goto alloc_error;
1892         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1893                 size_t linelen = strlen(line);
1895                 if (linelen)
1896                         line[linelen - 1] = 0;
1898                 if (opt_iconv != ICONV_NONE) {
1899                         char *inbuf = line;
1900                         size_t inlen = linelen;
1902                         char *outbuf = out_buffer;
1903                         size_t outlen = sizeof(out_buffer);
1905                         size_t ret;
1907                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1908                         if (ret != (size_t) -1) {
1909                                 line = out_buffer;
1910                                 linelen = strlen(out_buffer);
1911                         }
1912                 }
1914                 if (!view->ops->read(view, line))
1915                         goto alloc_error;
1917                 if (lines-- == 1)
1918                         break;
1919         }
1921         {
1922                 int digits;
1924                 lines = view->lines;
1925                 for (digits = 0; lines; digits++)
1926                         lines /= 10;
1928                 /* Keep the displayed view in sync with line number scaling. */
1929                 if (digits != view->digits) {
1930                         view->digits = digits;
1931                         redraw_from = 0;
1932                 }
1933         }
1935         if (!view_is_displayed(view))
1936                 goto check_pipe;
1938         if (view == VIEW(REQ_VIEW_TREE)) {
1939                 /* Clear the view and redraw everything since the tree sorting
1940                  * might have rearranged things. */
1941                 redraw_view(view);
1943         } else if (redraw_from >= 0) {
1944                 /* If this is an incremental update, redraw the previous line
1945                  * since for commits some members could have changed when
1946                  * loading the main view. */
1947                 if (redraw_from > 0)
1948                         redraw_from--;
1950                 /* Since revision graph visualization requires knowledge
1951                  * about the parent commit, it causes a further one-off
1952                  * needed to be redrawn for incremental updates. */
1953                 if (redraw_from > 0 && opt_rev_graph)
1954                         redraw_from--;
1956                 /* Incrementally draw avoids flickering. */
1957                 redraw_view_from(view, redraw_from);
1958         }
1960         /* Update the title _after_ the redraw so that if the redraw picks up a
1961          * commit reference in view->ref it'll be available here. */
1962         update_view_title(view);
1964 check_pipe:
1965         if (ferror(view->pipe)) {
1966                 report("Failed to read: %s", strerror(errno));
1967                 goto end;
1969         } else if (feof(view->pipe)) {
1970                 report("");
1971                 goto end;
1972         }
1974         return TRUE;
1976 alloc_error:
1977         report("Allocation failure");
1979 end:
1980         view->ops->read(view, NULL);
1981         end_update(view);
1982         return FALSE;
1985 static struct line *
1986 add_line_data(struct view *view, void *data, enum line_type type)
1988         struct line *line = &view->line[view->lines++];
1990         memset(line, 0, sizeof(*line));
1991         line->type = type;
1992         line->data = data;
1994         return line;
1997 static struct line *
1998 add_line_text(struct view *view, char *data, enum line_type type)
2000         if (data)
2001                 data = strdup(data);
2003         return data ? add_line_data(view, data, type) : NULL;
2007 /*
2008  * View opening
2009  */
2011 enum open_flags {
2012         OPEN_DEFAULT = 0,       /* Use default view switching. */
2013         OPEN_SPLIT = 1,         /* Split current view. */
2014         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2015         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2016 };
2018 static void
2019 open_view(struct view *prev, enum request request, enum open_flags flags)
2021         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2022         bool split = !!(flags & OPEN_SPLIT);
2023         bool reload = !!(flags & OPEN_RELOAD);
2024         struct view *view = VIEW(request);
2025         int nviews = displayed_views();
2026         struct view *base_view = display[0];
2028         if (view == prev && nviews == 1 && !reload) {
2029                 report("Already in %s view", view->name);
2030                 return;
2031         }
2033         if (view->ops->open) {
2034                 if (!view->ops->open(view)) {
2035                         report("Failed to load %s view", view->name);
2036                         return;
2037                 }
2039         } else if ((reload || strcmp(view->vid, view->id)) &&
2040                    !begin_update(view)) {
2041                 report("Failed to load %s view", view->name);
2042                 return;
2043         }
2045         if (split) {
2046                 display[1] = view;
2047                 if (!backgrounded)
2048                         current_view = 1;
2049         } else {
2050                 /* Maximize the current view. */
2051                 memset(display, 0, sizeof(display));
2052                 current_view = 0;
2053                 display[current_view] = view;
2054         }
2056         /* Resize the view when switching between split- and full-screen,
2057          * or when switching between two different full-screen views. */
2058         if (nviews != displayed_views() ||
2059             (nviews == 1 && base_view != display[0]))
2060                 resize_display();
2062         if (split && prev->lineno - prev->offset >= prev->height) {
2063                 /* Take the title line into account. */
2064                 int lines = prev->lineno - prev->offset - prev->height + 1;
2066                 /* Scroll the view that was split if the current line is
2067                  * outside the new limited view. */
2068                 do_scroll_view(prev, lines);
2069         }
2071         if (prev && view != prev) {
2072                 if (split && !backgrounded) {
2073                         /* "Blur" the previous view. */
2074                         update_view_title(prev);
2075                 }
2077                 view->parent = prev;
2078         }
2080         if (view->pipe && view->lines == 0) {
2081                 /* Clear the old view and let the incremental updating refill
2082                  * the screen. */
2083                 wclear(view->win);
2084                 report("");
2085         } else {
2086                 redraw_view(view);
2087                 report("");
2088         }
2090         /* If the view is backgrounded the above calls to report()
2091          * won't redraw the view title. */
2092         if (backgrounded)
2093                 update_view_title(view);
2097 /*
2098  * User request switch noodle
2099  */
2101 static int
2102 view_driver(struct view *view, enum request request)
2104         int i;
2106         switch (request) {
2107         case REQ_MOVE_UP:
2108         case REQ_MOVE_DOWN:
2109         case REQ_MOVE_PAGE_UP:
2110         case REQ_MOVE_PAGE_DOWN:
2111         case REQ_MOVE_FIRST_LINE:
2112         case REQ_MOVE_LAST_LINE:
2113                 move_view(view, request);
2114                 break;
2116         case REQ_SCROLL_LINE_DOWN:
2117         case REQ_SCROLL_LINE_UP:
2118         case REQ_SCROLL_PAGE_DOWN:
2119         case REQ_SCROLL_PAGE_UP:
2120                 scroll_view(view, request);
2121                 break;
2123         case REQ_VIEW_BLOB:
2124                 if (!ref_blob[0]) {
2125                         report("No file chosen, press %s to open tree view",
2126                                get_key(REQ_VIEW_TREE));
2127                         break;
2128                 }
2129                 open_view(view, request, OPEN_DEFAULT);
2130                 break;
2132         case REQ_VIEW_PAGER:
2133                 if (!VIEW(REQ_VIEW_PAGER)->lines) {
2134                         report("No pager content, press %s to run command from prompt",
2135                                get_key(REQ_PROMPT));
2136                         break;
2137                 }
2138                 open_view(view, request, OPEN_DEFAULT);
2139                 break;
2141         case REQ_VIEW_MAIN:
2142         case REQ_VIEW_DIFF:
2143         case REQ_VIEW_LOG:
2144         case REQ_VIEW_TREE:
2145         case REQ_VIEW_HELP:
2146                 open_view(view, request, OPEN_DEFAULT);
2147                 break;
2149         case REQ_NEXT:
2150         case REQ_PREVIOUS:
2151                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2153                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2154                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2155                    (view == VIEW(REQ_VIEW_BLOB) &&
2156                      view->parent == VIEW(REQ_VIEW_TREE))) {
2157                         view = view->parent;
2158                         move_view(view, request);
2159                         if (view_is_displayed(view))
2160                                 update_view_title(view);
2161                 } else {
2162                         move_view(view, request);
2163                         break;
2164                 }
2165                 /* Fall-through */
2167         case REQ_ENTER:
2168                 if (!view->lines) {
2169                         report("Nothing to enter");
2170                         break;
2171                 }
2172                 return view->ops->enter(view, &view->line[view->lineno]);
2174         case REQ_VIEW_NEXT:
2175         {
2176                 int nviews = displayed_views();
2177                 int next_view = (current_view + 1) % nviews;
2179                 if (next_view == current_view) {
2180                         report("Only one view is displayed");
2181                         break;
2182                 }
2184                 current_view = next_view;
2185                 /* Blur out the title of the previous view. */
2186                 update_view_title(view);
2187                 report("");
2188                 break;
2189         }
2190         case REQ_TOGGLE_LINENO:
2191                 opt_line_number = !opt_line_number;
2192                 redraw_display();
2193                 break;
2195         case REQ_TOGGLE_REV_GRAPH:
2196                 opt_rev_graph = !opt_rev_graph;
2197                 redraw_display();
2198                 break;
2200         case REQ_PROMPT:
2201                 /* Always reload^Wrerun commands from the prompt. */
2202                 open_view(view, opt_request, OPEN_RELOAD);
2203                 break;
2205         case REQ_SEARCH:
2206         case REQ_SEARCH_BACK:
2207                 search_view(view, request);
2208                 break;
2210         case REQ_FIND_NEXT:
2211         case REQ_FIND_PREV:
2212                 find_next(view, request);
2213                 break;
2215         case REQ_STOP_LOADING:
2216                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2217                         view = &views[i];
2218                         if (view->pipe)
2219                                 report("Stopped loading the %s view", view->name),
2220                         end_update(view);
2221                 }
2222                 break;
2224         case REQ_SHOW_VERSION:
2225                 report("%s (built %s)", VERSION, __DATE__);
2226                 return TRUE;
2228         case REQ_SCREEN_RESIZE:
2229                 resize_display();
2230                 /* Fall-through */
2231         case REQ_SCREEN_REDRAW:
2232                 redraw_display();
2233                 break;
2235         case REQ_NONE:
2236                 doupdate();
2237                 return TRUE;
2239         case REQ_VIEW_CLOSE:
2240                 /* XXX: Mark closed views by letting view->parent point to the
2241                  * view itself. Parents to closed view should never be
2242                  * followed. */
2243                 if (view->parent &&
2244                     view->parent->parent != view->parent) {
2245                         memset(display, 0, sizeof(display));
2246                         current_view = 0;
2247                         display[current_view] = view->parent;
2248                         view->parent = view;
2249                         resize_display();
2250                         redraw_display();
2251                         break;
2252                 }
2253                 /* Fall-through */
2254         case REQ_QUIT:
2255                 return FALSE;
2257         default:
2258                 /* An unknown key will show most commonly used commands. */
2259                 report("Unknown key, press 'h' for help");
2260                 return TRUE;
2261         }
2263         return TRUE;
2267 /*
2268  * Pager backend
2269  */
2271 static bool
2272 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2274         char *text = line->data;
2275         enum line_type type = line->type;
2276         int textlen = strlen(text);
2277         int attr;
2279         wmove(view->win, lineno, 0);
2281         if (selected) {
2282                 type = LINE_CURSOR;
2283                 wchgat(view->win, -1, 0, type, NULL);
2284         }
2286         attr = get_line_attr(type);
2287         wattrset(view->win, attr);
2289         if (opt_line_number || opt_tab_size < TABSIZE) {
2290                 static char spaces[] = "                    ";
2291                 int col_offset = 0, col = 0;
2293                 if (opt_line_number) {
2294                         unsigned long real_lineno = view->offset + lineno + 1;
2296                         if (real_lineno == 1 ||
2297                             (real_lineno % opt_num_interval) == 0) {
2298                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2300                         } else {
2301                                 waddnstr(view->win, spaces,
2302                                          MIN(view->digits, STRING_SIZE(spaces)));
2303                         }
2304                         waddstr(view->win, ": ");
2305                         col_offset = view->digits + 2;
2306                 }
2308                 while (text && col_offset + col < view->width) {
2309                         int cols_max = view->width - col_offset - col;
2310                         char *pos = text;
2311                         int cols;
2313                         if (*text == '\t') {
2314                                 text++;
2315                                 assert(sizeof(spaces) > TABSIZE);
2316                                 pos = spaces;
2317                                 cols = opt_tab_size - (col % opt_tab_size);
2319                         } else {
2320                                 text = strchr(text, '\t');
2321                                 cols = line ? text - pos : strlen(pos);
2322                         }
2324                         waddnstr(view->win, pos, MIN(cols, cols_max));
2325                         col += cols;
2326                 }
2328         } else {
2329                 int col = 0, pos = 0;
2331                 for (; pos < textlen && col < view->width; pos++, col++)
2332                         if (text[pos] == '\t')
2333                                 col += TABSIZE - (col % TABSIZE) - 1;
2335                 waddnstr(view->win, text, pos);
2336         }
2338         return TRUE;
2341 static bool
2342 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2344         char refbuf[SIZEOF_STR];
2345         char *ref = NULL;
2346         FILE *pipe;
2348         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2349                 return TRUE;
2351         pipe = popen(refbuf, "r");
2352         if (!pipe)
2353                 return TRUE;
2355         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2356                 ref = chomp_string(ref);
2357         pclose(pipe);
2359         if (!ref || !*ref)
2360                 return TRUE;
2362         /* This is the only fatal call, since it can "corrupt" the buffer. */
2363         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2364                 return FALSE;
2366         return TRUE;
2369 static void
2370 add_pager_refs(struct view *view, struct line *line)
2372         char buf[SIZEOF_STR];
2373         char *commit_id = line->data + STRING_SIZE("commit ");
2374         struct ref **refs;
2375         size_t bufpos = 0, refpos = 0;
2376         const char *sep = "Refs: ";
2377         bool is_tag = FALSE;
2379         assert(line->type == LINE_COMMIT);
2381         refs = get_refs(commit_id);
2382         if (!refs) {
2383                 if (view == VIEW(REQ_VIEW_DIFF))
2384                         goto try_add_describe_ref;
2385                 return;
2386         }
2388         do {
2389                 struct ref *ref = refs[refpos];
2390                 char *fmt = ref->tag    ? "%s[%s]" :
2391                             ref->remote ? "%s<%s>" : "%s%s";
2393                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2394                         return;
2395                 sep = ", ";
2396                 if (ref->tag)
2397                         is_tag = TRUE;
2398         } while (refs[refpos++]->next);
2400         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2401 try_add_describe_ref:
2402                 /* Add <tag>-g<commit_id> "fake" reference. */
2403                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2404                         return;
2405         }
2407         if (bufpos == 0)
2408                 return;
2410         if (!realloc_lines(view, view->line_size + 1))
2411                 return;
2413         add_line_text(view, buf, LINE_PP_REFS);
2416 static bool
2417 pager_read(struct view *view, char *data)
2419         struct line *line;
2421         if (!data)
2422                 return TRUE;
2424         line = add_line_text(view, data, get_line_type(data));
2425         if (!line)
2426                 return FALSE;
2428         if (line->type == LINE_COMMIT &&
2429             (view == VIEW(REQ_VIEW_DIFF) ||
2430              view == VIEW(REQ_VIEW_LOG)))
2431                 add_pager_refs(view, line);
2433         return TRUE;
2436 static bool
2437 pager_enter(struct view *view, struct line *line)
2439         int split = 0;
2441         if (line->type == LINE_COMMIT &&
2442            (view == VIEW(REQ_VIEW_LOG) ||
2443             view == VIEW(REQ_VIEW_PAGER))) {
2444                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2445                 split = 1;
2446         }
2448         /* Always scroll the view even if it was split. That way
2449          * you can use Enter to scroll through the log view and
2450          * split open each commit diff. */
2451         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2453         /* FIXME: A minor workaround. Scrolling the view will call report("")
2454          * but if we are scrolling a non-current view this won't properly
2455          * update the view title. */
2456         if (split)
2457                 update_view_title(view);
2459         return TRUE;
2462 static bool
2463 pager_grep(struct view *view, struct line *line)
2465         regmatch_t pmatch;
2466         char *text = line->data;
2468         if (!*text)
2469                 return FALSE;
2471         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2472                 return FALSE;
2474         return TRUE;
2477 static void
2478 pager_select(struct view *view, struct line *line)
2480         if (line->type == LINE_COMMIT) {
2481                 char *text = line->data + STRING_SIZE("commit ");
2483                 if (view != VIEW(REQ_VIEW_PAGER))
2484                         string_copy_rev(view->ref, text);
2485                 string_copy_rev(ref_commit, text);
2486         }
2489 static struct view_ops pager_ops = {
2490         "line",
2491         NULL,
2492         pager_read,
2493         pager_draw,
2494         pager_enter,
2495         pager_grep,
2496         pager_select,
2497 };
2500 /*
2501  * Help backend
2502  */
2504 static bool
2505 help_open(struct view *view)
2507         char buf[BUFSIZ];
2508         int lines = ARRAY_SIZE(req_info) + 2;
2509         int i;
2511         if (view->lines > 0)
2512                 return TRUE;
2514         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2515                 if (!req_info[i].request)
2516                         lines++;
2518         view->line = calloc(lines, sizeof(*view->line));
2519         if (!view->line)
2520                 return FALSE;
2522         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2524         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2525                 char *key;
2527                 if (!req_info[i].request) {
2528                         add_line_text(view, "", LINE_DEFAULT);
2529                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2530                         continue;
2531                 }
2533                 key = get_key(req_info[i].request);
2534                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2535                         continue;
2537                 add_line_text(view, buf, LINE_DEFAULT);
2538         }
2540         return TRUE;
2543 static struct view_ops help_ops = {
2544         "line",
2545         help_open,
2546         NULL,
2547         pager_draw,
2548         pager_enter,
2549         pager_grep,
2550         pager_select,
2551 };
2554 /*
2555  * Tree backend
2556  */
2558 /* Parse output from git-ls-tree(1):
2559  *
2560  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2561  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2562  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2563  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2564  */
2566 #define SIZEOF_TREE_ATTR \
2567         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2569 #define TREE_UP_FORMAT "040000 tree %s\t.."
2571 static int
2572 tree_compare_entry(enum line_type type1, char *name1,
2573                    enum line_type type2, char *name2)
2575         if (type1 != type2) {
2576                 if (type1 == LINE_TREE_DIR)
2577                         return -1;
2578                 return 1;
2579         }
2581         return strcmp(name1, name2);
2584 static bool
2585 tree_read(struct view *view, char *text)
2587         size_t textlen = text ? strlen(text) : 0;
2588         char buf[SIZEOF_STR];
2589         unsigned long pos;
2590         enum line_type type;
2591         bool first_read = view->lines == 0;
2593         if (textlen <= SIZEOF_TREE_ATTR)
2594                 return FALSE;
2596         type = text[STRING_SIZE("100644 ")] == 't'
2597              ? LINE_TREE_DIR : LINE_TREE_FILE;
2599         if (first_read) {
2600                 /* Add path info line */
2601                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2602                     !realloc_lines(view, view->line_size + 1) ||
2603                     !add_line_text(view, buf, LINE_DEFAULT))
2604                         return FALSE;
2606                 /* Insert "link" to parent directory. */
2607                 if (*opt_path) {
2608                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2609                             !realloc_lines(view, view->line_size + 1) ||
2610                             !add_line_text(view, buf, LINE_TREE_DIR))
2611                                 return FALSE;
2612                 }
2613         }
2615         /* Strip the path part ... */
2616         if (*opt_path) {
2617                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2618                 size_t striplen = strlen(opt_path);
2619                 char *path = text + SIZEOF_TREE_ATTR;
2621                 if (pathlen > striplen)
2622                         memmove(path, path + striplen,
2623                                 pathlen - striplen + 1);
2624         }
2626         /* Skip "Directory ..." and ".." line. */
2627         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2628                 struct line *line = &view->line[pos];
2629                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2630                 char *path2 = text + SIZEOF_TREE_ATTR;
2631                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2633                 if (cmp <= 0)
2634                         continue;
2636                 text = strdup(text);
2637                 if (!text)
2638                         return FALSE;
2640                 if (view->lines > pos)
2641                         memmove(&view->line[pos + 1], &view->line[pos],
2642                                 (view->lines - pos) * sizeof(*line));
2644                 line = &view->line[pos];
2645                 line->data = text;
2646                 line->type = type;
2647                 view->lines++;
2648                 return TRUE;
2649         }
2651         if (!add_line_text(view, text, type))
2652                 return FALSE;
2654         /* Move the current line to the first tree entry. */
2655         if (first_read)
2656                 view->lineno++;
2658         return TRUE;
2661 static bool
2662 tree_enter(struct view *view, struct line *line)
2664         enum open_flags flags;
2665         enum request request;
2667         switch (line->type) {
2668         case LINE_TREE_DIR:
2669                 /* Depending on whether it is a subdir or parent (updir?) link
2670                  * mangle the path buffer. */
2671                 if (line == &view->line[1] && *opt_path) {
2672                         size_t path_len = strlen(opt_path);
2673                         char *dirsep = opt_path + path_len - 1;
2675                         while (dirsep > opt_path && dirsep[-1] != '/')
2676                                 dirsep--;
2678                         dirsep[0] = 0;
2680                 } else {
2681                         size_t pathlen = strlen(opt_path);
2682                         size_t origlen = pathlen;
2683                         char *data = line->data;
2684                         char *basename = data + SIZEOF_TREE_ATTR;
2686                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2687                                 opt_path[origlen] = 0;
2688                                 return TRUE;
2689                         }
2690                 }
2692                 /* Trees and subtrees share the same ID, so they are not not
2693                  * unique like blobs. */
2694                 flags = OPEN_RELOAD;
2695                 request = REQ_VIEW_TREE;
2696                 break;
2698         case LINE_TREE_FILE:
2699                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2700                 request = REQ_VIEW_BLOB;
2701                 break;
2703         default:
2704                 return TRUE;
2705         }
2707         open_view(view, request, flags);
2709         return TRUE;
2712 static void
2713 tree_select(struct view *view, struct line *line)
2715         char *text = line->data + STRING_SIZE("100644 blob ");
2717         if (line->type == LINE_TREE_FILE) {
2718                 string_copy_rev(ref_blob, text);
2720         } else if (line->type != LINE_TREE_DIR) {
2721                 return;
2722         }
2724         string_copy_rev(view->ref, text);
2727 static struct view_ops tree_ops = {
2728         "file",
2729         NULL,
2730         tree_read,
2731         pager_draw,
2732         tree_enter,
2733         pager_grep,
2734         tree_select,
2735 };
2737 static bool
2738 blob_read(struct view *view, char *line)
2740         return add_line_text(view, line, LINE_DEFAULT);
2743 static struct view_ops blob_ops = {
2744         "line",
2745         NULL,
2746         blob_read,
2747         pager_draw,
2748         pager_enter,
2749         pager_grep,
2750         pager_select,
2751 };
2754 /*
2755  * Revision graph
2756  */
2758 struct commit {
2759         char id[SIZEOF_REV];            /* SHA1 ID. */
2760         char title[128];                /* First line of the commit message. */
2761         char author[75];                /* Author of the commit. */
2762         struct tm time;                 /* Date from the author ident. */
2763         struct ref **refs;              /* Repository references. */
2764         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2765         size_t graph_size;              /* The width of the graph array. */
2766 };
2768 /* Size of rev graph with no  "padding" columns */
2769 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
2771 struct rev_graph {
2772         struct rev_graph *prev, *next, *parents;
2773         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2774         size_t size;
2775         struct commit *commit;
2776         size_t pos;
2777 };
2779 /* Parents of the commit being visualized. */
2780 static struct rev_graph graph_parents[4];
2782 /* The current stack of revisions on the graph. */
2783 static struct rev_graph graph_stacks[4] = {
2784         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
2785         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
2786         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
2787         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
2788 };
2790 static inline bool
2791 graph_parent_is_merge(struct rev_graph *graph)
2793         return graph->parents->size > 1;
2796 static inline void
2797 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
2799         struct commit *commit = graph->commit;
2801         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
2802                 commit->graph[commit->graph_size++] = symbol;
2805 static void
2806 done_rev_graph(struct rev_graph *graph)
2808         if (graph_parent_is_merge(graph) &&
2809             graph->pos < graph->size - 1 &&
2810             graph->next->size == graph->size + graph->parents->size - 1) {
2811                 size_t i = graph->pos + graph->parents->size - 1;
2813                 graph->commit->graph_size = i * 2;
2814                 while (i < graph->next->size - 1) {
2815                         append_to_rev_graph(graph, ' ');
2816                         append_to_rev_graph(graph, '\\');
2817                         i++;
2818                 }
2819         }
2821         graph->size = graph->pos = 0;
2822         graph->commit = NULL;
2823         memset(graph->parents, 0, sizeof(*graph->parents));
2826 static void
2827 push_rev_graph(struct rev_graph *graph, char *parent)
2829         int i;
2831         /* "Collapse" duplicate parents lines.
2832          *
2833          * FIXME: This needs to also update update the drawn graph but
2834          * for now it just serves as a method for pruning graph lines. */
2835         for (i = 0; i < graph->size; i++)
2836                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
2837                         return;
2839         if (graph->size < SIZEOF_REVITEMS) {
2840                 string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
2841         }
2844 static chtype
2845 get_rev_graph_symbol(struct rev_graph *graph)
2847         chtype symbol;
2849         if (graph->parents->size == 0)
2850                 symbol = REVGRAPH_INIT;
2851         else if (graph_parent_is_merge(graph))
2852                 symbol = REVGRAPH_MERGE;
2853         else if (graph->pos >= graph->size)
2854                 symbol = REVGRAPH_BRANCH;
2855         else
2856                 symbol = REVGRAPH_COMMIT;
2858         return symbol;
2861 static void
2862 draw_rev_graph(struct rev_graph *graph)
2864         struct rev_filler {
2865                 chtype separator, line;
2866         };
2867         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
2868         static struct rev_filler fillers[] = {
2869                 { ' ',  REVGRAPH_LINE },
2870                 { '`',  '.' },
2871                 { '\'', ' ' },
2872                 { '/',  ' ' },
2873         };
2874         chtype symbol = get_rev_graph_symbol(graph);
2875         struct rev_filler *filler;
2876         size_t i;
2878         filler = &fillers[DEFAULT];
2880         for (i = 0; i < graph->pos; i++) {
2881                 append_to_rev_graph(graph, filler->line);
2882                 if (graph_parent_is_merge(graph->prev) &&
2883                     graph->prev->pos == i)
2884                         filler = &fillers[RSHARP];
2886                 append_to_rev_graph(graph, filler->separator);
2887         }
2889         /* Place the symbol for this revision. */
2890         append_to_rev_graph(graph, symbol);
2892         if (graph->prev->size > graph->size)
2893                 filler = &fillers[RDIAG];
2894         else
2895                 filler = &fillers[DEFAULT];
2897         i++;
2899         for (; i < graph->size; i++) {
2900                 append_to_rev_graph(graph, filler->separator);
2901                 append_to_rev_graph(graph, filler->line);
2902                 if (graph_parent_is_merge(graph->prev) &&
2903                     i < graph->prev->pos + graph->parents->size)
2904                         filler = &fillers[RSHARP];
2905                 if (graph->prev->size > graph->size)
2906                         filler = &fillers[LDIAG];
2907         }
2909         if (graph->prev->size > graph->size) {
2910                 append_to_rev_graph(graph, filler->separator);
2911                 if (filler->line != ' ')
2912                         append_to_rev_graph(graph, filler->line);
2913         }
2916 /* Prepare the next rev graph */
2917 static void
2918 prepare_rev_graph(struct rev_graph *graph)
2920         size_t i;
2922         /* First, traverse all lines of revisions up to the active one. */
2923         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
2924                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
2925                         break;
2927                 push_rev_graph(graph->next, graph->rev[graph->pos]);
2928         }
2930         /* Interleave the new revision parent(s). */
2931         for (i = 0; i < graph->parents->size; i++)
2932                 push_rev_graph(graph->next, graph->parents->rev[i]);
2934         /* Lastly, put any remaining revisions. */
2935         for (i = graph->pos + 1; i < graph->size; i++)
2936                 push_rev_graph(graph->next, graph->rev[i]);
2939 static void
2940 update_rev_graph(struct rev_graph *graph)
2942         /* If this is the finalizing update ... */
2943         if (graph->commit)
2944                 prepare_rev_graph(graph);
2946         /* Graph visualization needs a one rev look-ahead,
2947          * so the first update doesn't visualize anything. */
2948         if (!graph->prev->commit)
2949                 return;
2951         draw_rev_graph(graph->prev);
2952         done_rev_graph(graph->prev->prev);
2956 /*
2957  * Main view backend
2958  */
2960 static bool
2961 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2963         char buf[DATE_COLS + 1];
2964         struct commit *commit = line->data;
2965         enum line_type type;
2966         int col = 0;
2967         size_t timelen;
2968         size_t authorlen;
2969         int trimmed = 1;
2971         if (!*commit->author)
2972                 return FALSE;
2974         wmove(view->win, lineno, col);
2976         if (selected) {
2977                 type = LINE_CURSOR;
2978                 wattrset(view->win, get_line_attr(type));
2979                 wchgat(view->win, -1, 0, type, NULL);
2981         } else {
2982                 type = LINE_MAIN_COMMIT;
2983                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2984         }
2986         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2987         waddnstr(view->win, buf, timelen);
2988         waddstr(view->win, " ");
2990         col += DATE_COLS;
2991         wmove(view->win, lineno, col);
2992         if (type != LINE_CURSOR)
2993                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2995         if (opt_utf8) {
2996                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2997         } else {
2998                 authorlen = strlen(commit->author);
2999                 if (authorlen > AUTHOR_COLS - 2) {
3000                         authorlen = AUTHOR_COLS - 2;
3001                         trimmed = 1;
3002                 }
3003         }
3005         if (trimmed) {
3006                 waddnstr(view->win, commit->author, authorlen);
3007                 if (type != LINE_CURSOR)
3008                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3009                 waddch(view->win, '~');
3010         } else {
3011                 waddstr(view->win, commit->author);
3012         }
3014         col += AUTHOR_COLS;
3015         if (type != LINE_CURSOR)
3016                 wattrset(view->win, A_NORMAL);
3018         if (opt_rev_graph && commit->graph_size) {
3019                 size_t i;
3021                 wmove(view->win, lineno, col);
3022                 /* Using waddch() instead of waddnstr() ensures that
3023                  * they'll be rendered correctly for the cursor line. */
3024                 for (i = 0; i < commit->graph_size; i++)
3025                         waddch(view->win, commit->graph[i]);
3027                 waddch(view->win, ' ');
3028                 col += commit->graph_size + 1;
3029         }
3031         wmove(view->win, lineno, col);
3033         if (commit->refs) {
3034                 size_t i = 0;
3036                 do {
3037                         if (type == LINE_CURSOR)
3038                                 ;
3039                         else if (commit->refs[i]->tag)
3040                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3041                         else if (commit->refs[i]->remote)
3042                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3043                         else
3044                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3045                         waddstr(view->win, "[");
3046                         waddstr(view->win, commit->refs[i]->name);
3047                         waddstr(view->win, "]");
3048                         if (type != LINE_CURSOR)
3049                                 wattrset(view->win, A_NORMAL);
3050                         waddstr(view->win, " ");
3051                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3052                 } while (commit->refs[i++]->next);
3053         }
3055         if (type != LINE_CURSOR)
3056                 wattrset(view->win, get_line_attr(type));
3058         {
3059                 int titlelen = strlen(commit->title);
3061                 if (col + titlelen > view->width)
3062                         titlelen = view->width - col;
3064                 waddnstr(view->win, commit->title, titlelen);
3065         }
3067         return TRUE;
3070 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3071 static bool
3072 main_read(struct view *view, char *line)
3074         static struct rev_graph *graph = graph_stacks;
3075         enum line_type type;
3076         struct commit *commit;
3078         if (!line) {
3079                 update_rev_graph(graph);
3080                 return TRUE;
3081         }
3083         type = get_line_type(line);
3084         if (type == LINE_COMMIT) {
3085                 commit = calloc(1, sizeof(struct commit));
3086                 if (!commit)
3087                         return FALSE;
3089                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3090                 commit->refs = get_refs(commit->id);
3091                 graph->commit = commit;
3092                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3093                 return TRUE;
3094         }
3096         if (!view->lines)
3097                 return TRUE;
3098         commit = view->line[view->lines - 1].data;
3100         switch (type) {
3101         case LINE_PARENT:
3102                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3103                 break;
3105         case LINE_AUTHOR:
3106         {
3107                 /* Parse author lines where the name may be empty:
3108                  *      author  <email@address.tld> 1138474660 +0100
3109                  */
3110                 char *ident = line + STRING_SIZE("author ");
3111                 char *nameend = strchr(ident, '<');
3112                 char *emailend = strchr(ident, '>');
3114                 if (!nameend || !emailend)
3115                         break;
3117                 update_rev_graph(graph);
3118                 graph = graph->next;
3120                 *nameend = *emailend = 0;
3121                 ident = chomp_string(ident);
3122                 if (!*ident) {
3123                         ident = chomp_string(nameend + 1);
3124                         if (!*ident)
3125                                 ident = "Unknown";
3126                 }
3128                 string_copy(commit->author, ident);
3130                 /* Parse epoch and timezone */
3131                 if (emailend[1] == ' ') {
3132                         char *secs = emailend + 2;
3133                         char *zone = strchr(secs, ' ');
3134                         time_t time = (time_t) atol(secs);
3136                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3137                                 long tz;
3139                                 zone++;
3140                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3141                                 tz += ('0' - zone[2]) * 60 * 60;
3142                                 tz += ('0' - zone[3]) * 60;
3143                                 tz += ('0' - zone[4]) * 60;
3145                                 if (zone[0] == '-')
3146                                         tz = -tz;
3148                                 time -= tz;
3149                         }
3151                         gmtime_r(&time, &commit->time);
3152                 }
3153                 break;
3154         }
3155         default:
3156                 /* Fill in the commit title if it has not already been set. */
3157                 if (commit->title[0])
3158                         break;
3160                 /* Require titles to start with a non-space character at the
3161                  * offset used by git log. */
3162                 if (strncmp(line, "    ", 4))
3163                         break;
3164                 line += 4;
3165                 /* Well, if the title starts with a whitespace character,
3166                  * try to be forgiving.  Otherwise we end up with no title. */
3167                 while (isspace(*line))
3168                         line++;
3169                 if (*line == '\0')
3170                         break;
3171                 /* FIXME: More graceful handling of titles; append "..." to
3172                  * shortened titles, etc. */
3174                 string_copy(commit->title, line);
3175         }
3177         return TRUE;
3180 static bool
3181 main_enter(struct view *view, struct line *line)
3183         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3185         open_view(view, REQ_VIEW_DIFF, flags);
3186         return TRUE;
3189 static bool
3190 main_grep(struct view *view, struct line *line)
3192         struct commit *commit = line->data;
3193         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3194         char buf[DATE_COLS + 1];
3195         regmatch_t pmatch;
3197         for (state = S_TITLE; state < S_END; state++) {
3198                 char *text;
3200                 switch (state) {
3201                 case S_TITLE:   text = commit->title;   break;
3202                 case S_AUTHOR:  text = commit->author;  break;
3203                 case S_DATE:
3204                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3205                                 continue;
3206                         text = buf;
3207                         break;
3209                 default:
3210                         return FALSE;
3211                 }
3213                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3214                         return TRUE;
3215         }
3217         return FALSE;
3220 static void
3221 main_select(struct view *view, struct line *line)
3223         struct commit *commit = line->data;
3225         string_copy_rev(view->ref, commit->id);
3226         string_copy_rev(ref_commit, view->ref);
3229 static struct view_ops main_ops = {
3230         "commit",
3231         NULL,
3232         main_read,
3233         main_draw,
3234         main_enter,
3235         main_grep,
3236         main_select,
3237 };
3240 /*
3241  * Unicode / UTF-8 handling
3242  *
3243  * NOTE: Much of the following code for dealing with unicode is derived from
3244  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3245  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3246  */
3248 /* I've (over)annotated a lot of code snippets because I am not entirely
3249  * confident that the approach taken by this small UTF-8 interface is correct.
3250  * --jonas */
3252 static inline int
3253 unicode_width(unsigned long c)
3255         if (c >= 0x1100 &&
3256            (c <= 0x115f                         /* Hangul Jamo */
3257             || c == 0x2329
3258             || c == 0x232a
3259             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
3260                                                 /* CJK ... Yi */
3261             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
3262             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
3263             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
3264             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
3265             || (c >= 0xffe0  && c <= 0xffe6)
3266             || (c >= 0x20000 && c <= 0x2fffd)
3267             || (c >= 0x30000 && c <= 0x3fffd)))
3268                 return 2;
3270         return 1;
3273 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3274  * Illegal bytes are set one. */
3275 static const unsigned char utf8_bytes[256] = {
3276         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3277         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3278         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3279         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3280         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3281         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
3282         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,
3283         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,
3284 };
3286 /* Decode UTF-8 multi-byte representation into a unicode character. */
3287 static inline unsigned long
3288 utf8_to_unicode(const char *string, size_t length)
3290         unsigned long unicode;
3292         switch (length) {
3293         case 1:
3294                 unicode  =   string[0];
3295                 break;
3296         case 2:
3297                 unicode  =  (string[0] & 0x1f) << 6;
3298                 unicode +=  (string[1] & 0x3f);
3299                 break;
3300         case 3:
3301                 unicode  =  (string[0] & 0x0f) << 12;
3302                 unicode += ((string[1] & 0x3f) << 6);
3303                 unicode +=  (string[2] & 0x3f);
3304                 break;
3305         case 4:
3306                 unicode  =  (string[0] & 0x0f) << 18;
3307                 unicode += ((string[1] & 0x3f) << 12);
3308                 unicode += ((string[2] & 0x3f) << 6);
3309                 unicode +=  (string[3] & 0x3f);
3310                 break;
3311         case 5:
3312                 unicode  =  (string[0] & 0x0f) << 24;
3313                 unicode += ((string[1] & 0x3f) << 18);
3314                 unicode += ((string[2] & 0x3f) << 12);
3315                 unicode += ((string[3] & 0x3f) << 6);
3316                 unicode +=  (string[4] & 0x3f);
3317                 break;
3318         case 6:
3319                 unicode  =  (string[0] & 0x01) << 30;
3320                 unicode += ((string[1] & 0x3f) << 24);
3321                 unicode += ((string[2] & 0x3f) << 18);
3322                 unicode += ((string[3] & 0x3f) << 12);
3323                 unicode += ((string[4] & 0x3f) << 6);
3324                 unicode +=  (string[5] & 0x3f);
3325                 break;
3326         default:
3327                 die("Invalid unicode length");
3328         }
3330         /* Invalid characters could return the special 0xfffd value but NUL
3331          * should be just as good. */
3332         return unicode > 0xffff ? 0 : unicode;
3335 /* Calculates how much of string can be shown within the given maximum width
3336  * and sets trimmed parameter to non-zero value if all of string could not be
3337  * shown.
3338  *
3339  * Additionally, adds to coloffset how many many columns to move to align with
3340  * the expected position. Takes into account how multi-byte and double-width
3341  * characters will effect the cursor position.
3342  *
3343  * Returns the number of bytes to output from string to satisfy max_width. */
3344 static size_t
3345 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3347         const char *start = string;
3348         const char *end = strchr(string, '\0');
3349         size_t mbwidth = 0;
3350         size_t width = 0;
3352         *trimmed = 0;
3354         while (string < end) {
3355                 int c = *(unsigned char *) string;
3356                 unsigned char bytes = utf8_bytes[c];
3357                 size_t ucwidth;
3358                 unsigned long unicode;
3360                 if (string + bytes > end)
3361                         break;
3363                 /* Change representation to figure out whether
3364                  * it is a single- or double-width character. */
3366                 unicode = utf8_to_unicode(string, bytes);
3367                 /* FIXME: Graceful handling of invalid unicode character. */
3368                 if (!unicode)
3369                         break;
3371                 ucwidth = unicode_width(unicode);
3372                 width  += ucwidth;
3373                 if (width > max_width) {
3374                         *trimmed = 1;
3375                         break;
3376                 }
3378                 /* The column offset collects the differences between the
3379                  * number of bytes encoding a character and the number of
3380                  * columns will be used for rendering said character.
3381                  *
3382                  * So if some character A is encoded in 2 bytes, but will be
3383                  * represented on the screen using only 1 byte this will and up
3384                  * adding 1 to the multi-byte column offset.
3385                  *
3386                  * Assumes that no double-width character can be encoding in
3387                  * less than two bytes. */
3388                 if (bytes > ucwidth)
3389                         mbwidth += bytes - ucwidth;
3391                 string  += bytes;
3392         }
3394         *coloffset += mbwidth;
3396         return string - start;
3400 /*
3401  * Status management
3402  */
3404 /* Whether or not the curses interface has been initialized. */
3405 static bool cursed = FALSE;
3407 /* The status window is used for polling keystrokes. */
3408 static WINDOW *status_win;
3410 static bool status_empty = TRUE;
3412 /* Update status and title window. */
3413 static void
3414 report(const char *msg, ...)
3416         struct view *view = display[current_view];
3418         if (input_mode)
3419                 return;
3421         if (!status_empty || *msg) {
3422                 va_list args;
3424                 va_start(args, msg);
3426                 wmove(status_win, 0, 0);
3427                 if (*msg) {
3428                         vwprintw(status_win, msg, args);
3429                         status_empty = FALSE;
3430                 } else {
3431                         status_empty = TRUE;
3432                 }
3433                 wclrtoeol(status_win);
3434                 wrefresh(status_win);
3436                 va_end(args);
3437         }
3439         update_view_title(view);
3440         update_display_cursor(view);
3443 /* Controls when nodelay should be in effect when polling user input. */
3444 static void
3445 set_nonblocking_input(bool loading)
3447         static unsigned int loading_views;
3449         if ((loading == FALSE && loading_views-- == 1) ||
3450             (loading == TRUE  && loading_views++ == 0))
3451                 nodelay(status_win, loading);
3454 static void
3455 init_display(void)
3457         int x, y;
3459         /* Initialize the curses library */
3460         if (isatty(STDIN_FILENO)) {
3461                 cursed = !!initscr();
3462         } else {
3463                 /* Leave stdin and stdout alone when acting as a pager. */
3464                 FILE *io = fopen("/dev/tty", "r+");
3466                 if (!io)
3467                         die("Failed to open /dev/tty");
3468                 cursed = !!newterm(NULL, io, io);
3469         }
3471         if (!cursed)
3472                 die("Failed to initialize curses");
3474         nonl();         /* Tell curses not to do NL->CR/NL on output */
3475         cbreak();       /* Take input chars one at a time, no wait for \n */
3476         noecho();       /* Don't echo input */
3477         leaveok(stdscr, TRUE);
3479         if (has_colors())
3480                 init_colors();
3482         getmaxyx(stdscr, y, x);
3483         status_win = newwin(1, 0, y - 1, 0);
3484         if (!status_win)
3485                 die("Failed to create status window");
3487         /* Enable keyboard mapping */
3488         keypad(status_win, TRUE);
3489         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3492 static char *
3493 read_prompt(const char *prompt)
3495         enum { READING, STOP, CANCEL } status = READING;
3496         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3497         int pos = 0;
3499         while (status == READING) {
3500                 struct view *view;
3501                 int i, key;
3503                 input_mode = TRUE;
3505                 foreach_view (view, i)
3506                         update_view(view);
3508                 input_mode = FALSE;
3510                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3511                 wclrtoeol(status_win);
3513                 /* Refresh, accept single keystroke of input */
3514                 key = wgetch(status_win);
3515                 switch (key) {
3516                 case KEY_RETURN:
3517                 case KEY_ENTER:
3518                 case '\n':
3519                         status = pos ? STOP : CANCEL;
3520                         break;
3522                 case KEY_BACKSPACE:
3523                         if (pos > 0)
3524                                 pos--;
3525                         else
3526                                 status = CANCEL;
3527                         break;
3529                 case KEY_ESC:
3530                         status = CANCEL;
3531                         break;
3533                 case ERR:
3534                         break;
3536                 default:
3537                         if (pos >= sizeof(buf)) {
3538                                 report("Input string too long");
3539                                 return NULL;
3540                         }
3542                         if (isprint(key))
3543                                 buf[pos++] = (char) key;
3544                 }
3545         }
3547         /* Clear the status window */
3548         status_empty = FALSE;
3549         report("");
3551         if (status == CANCEL)
3552                 return NULL;
3554         buf[pos++] = 0;
3556         return buf;
3559 /*
3560  * Repository references
3561  */
3563 static struct ref *refs;
3564 static size_t refs_size;
3566 /* Id <-> ref store */
3567 static struct ref ***id_refs;
3568 static size_t id_refs_size;
3570 static struct ref **
3571 get_refs(char *id)
3573         struct ref ***tmp_id_refs;
3574         struct ref **ref_list = NULL;
3575         size_t ref_list_size = 0;
3576         size_t i;
3578         for (i = 0; i < id_refs_size; i++)
3579                 if (!strcmp(id, id_refs[i][0]->id))
3580                         return id_refs[i];
3582         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3583         if (!tmp_id_refs)
3584                 return NULL;
3586         id_refs = tmp_id_refs;
3588         for (i = 0; i < refs_size; i++) {
3589                 struct ref **tmp;
3591                 if (strcmp(id, refs[i].id))
3592                         continue;
3594                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3595                 if (!tmp) {
3596                         if (ref_list)
3597                                 free(ref_list);
3598                         return NULL;
3599                 }
3601                 ref_list = tmp;
3602                 if (ref_list_size > 0)
3603                         ref_list[ref_list_size - 1]->next = 1;
3604                 ref_list[ref_list_size] = &refs[i];
3606                 /* XXX: The properties of the commit chains ensures that we can
3607                  * safely modify the shared ref. The repo references will
3608                  * always be similar for the same id. */
3609                 ref_list[ref_list_size]->next = 0;
3610                 ref_list_size++;
3611         }
3613         if (ref_list)
3614                 id_refs[id_refs_size++] = ref_list;
3616         return ref_list;
3619 static int
3620 read_ref(char *id, int idlen, char *name, int namelen)
3622         struct ref *ref;
3623         bool tag = FALSE;
3624         bool remote = FALSE;
3626         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3627                 /* Commits referenced by tags has "^{}" appended. */
3628                 if (name[namelen - 1] != '}')
3629                         return OK;
3631                 while (namelen > 0 && name[namelen] != '^')
3632                         namelen--;
3634                 tag = TRUE;
3635                 namelen -= STRING_SIZE("refs/tags/");
3636                 name    += STRING_SIZE("refs/tags/");
3638         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
3639                 remote = TRUE;
3640                 namelen -= STRING_SIZE("refs/remotes/");
3641                 name    += STRING_SIZE("refs/remotes/");
3643         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3644                 namelen -= STRING_SIZE("refs/heads/");
3645                 name    += STRING_SIZE("refs/heads/");
3647         } else if (!strcmp(name, "HEAD")) {
3648                 return OK;
3649         }
3651         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3652         if (!refs)
3653                 return ERR;
3655         ref = &refs[refs_size++];
3656         ref->name = malloc(namelen + 1);
3657         if (!ref->name)
3658                 return ERR;
3660         strncpy(ref->name, name, namelen);
3661         ref->name[namelen] = 0;
3662         ref->tag = tag;
3663         ref->remote = remote;
3664         string_copy_rev(ref->id, id);
3666         return OK;
3669 static int
3670 load_refs(void)
3672         const char *cmd_env = getenv("TIG_LS_REMOTE");
3673         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3675         return read_properties(popen(cmd, "r"), "\t", read_ref);
3678 static int
3679 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3681         if (!strcmp(name, "i18n.commitencoding"))
3682                 string_copy(opt_encoding, value);
3684         return OK;
3687 static int
3688 load_repo_config(void)
3690         return read_properties(popen("git repo-config --list", "r"),
3691                                "=", read_repo_config_option);
3694 static int
3695 read_properties(FILE *pipe, const char *separators,
3696                 int (*read_property)(char *, int, char *, int))
3698         char buffer[BUFSIZ];
3699         char *name;
3700         int state = OK;
3702         if (!pipe)
3703                 return ERR;
3705         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3706                 char *value;
3707                 size_t namelen;
3708                 size_t valuelen;
3710                 name = chomp_string(name);
3711                 namelen = strcspn(name, separators);
3713                 if (name[namelen]) {
3714                         name[namelen] = 0;
3715                         value = chomp_string(name + namelen + 1);
3716                         valuelen = strlen(value);
3718                 } else {
3719                         value = "";
3720                         valuelen = 0;
3721                 }
3723                 state = read_property(name, namelen, value, valuelen);
3724         }
3726         if (state != ERR && ferror(pipe))
3727                 state = ERR;
3729         pclose(pipe);
3731         return state;
3735 /*
3736  * Main
3737  */
3739 static void __NORETURN
3740 quit(int sig)
3742         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3743         if (cursed)
3744                 endwin();
3745         exit(0);
3748 static void __NORETURN
3749 die(const char *err, ...)
3751         va_list args;
3753         endwin();
3755         va_start(args, err);
3756         fputs("tig: ", stderr);
3757         vfprintf(stderr, err, args);
3758         fputs("\n", stderr);
3759         va_end(args);
3761         exit(1);
3764 int
3765 main(int argc, char *argv[])
3767         struct view *view;
3768         enum request request;
3769         size_t i;
3771         signal(SIGINT, quit);
3773         if (setlocale(LC_ALL, "")) {
3774                 string_copy(opt_codeset, nl_langinfo(CODESET));
3775         }
3777         if (load_options() == ERR)
3778                 die("Failed to load user config.");
3780         /* Load the repo config file so options can be overwritten from
3781          * the command line.  */
3782         if (load_repo_config() == ERR)
3783                 die("Failed to load repo config.");
3785         if (!parse_options(argc, argv))
3786                 return 0;
3788         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3789                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3790                 if (opt_iconv == ICONV_NONE)
3791                         die("Failed to initialize character set conversion");
3792         }
3794         if (load_refs() == ERR)
3795                 die("Failed to load refs.");
3797         /* Require a git repository unless when running in pager mode. */
3798         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3799                 die("Not a git repository");
3801         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3802                 view->cmd_env = getenv(view->cmd_env);
3804         request = opt_request;
3806         init_display();
3808         while (view_driver(display[current_view], request)) {
3809                 int key;
3810                 int i;
3812                 foreach_view (view, i)
3813                         update_view(view);
3815                 /* Refresh, accept single keystroke of input */
3816                 key = wgetch(status_win);
3818                 /* wgetch() with nodelay() enabled returns ERR when there's no
3819                  * input. */
3820                 if (key == ERR) {
3821                         request = REQ_NONE;
3822                         continue;
3823                 }
3825                 request = get_keybinding(display[current_view]->keymap, key);
3827                 /* Some low-level request handling. This keeps access to
3828                  * status_win restricted. */
3829                 switch (request) {
3830                 case REQ_PROMPT:
3831                 {
3832                         char *cmd = read_prompt(":");
3834                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3835                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3836                                         opt_request = REQ_VIEW_DIFF;
3837                                 } else {
3838                                         opt_request = REQ_VIEW_PAGER;
3839                                 }
3840                                 break;
3841                         }
3843                         request = REQ_NONE;
3844                         break;
3845                 }
3846                 case REQ_SEARCH:
3847                 case REQ_SEARCH_BACK:
3848                 {
3849                         const char *prompt = request == REQ_SEARCH
3850                                            ? "/" : "?";
3851                         char *search = read_prompt(prompt);
3853                         if (search)
3854                                 string_copy(opt_search, search);
3855                         else
3856                                 request = REQ_NONE;
3857                         break;
3858                 }
3859                 case REQ_SCREEN_RESIZE:
3860                 {
3861                         int height, width;
3863                         getmaxyx(stdscr, height, width);
3865                         /* Resize the status view and let the view driver take
3866                          * care of resizing the displayed views. */
3867                         wresize(status_win, 1, width);
3868                         mvwin(status_win, height - 1, 0);
3869                         wrefresh(status_win);
3870                         break;
3871                 }
3872                 default:
3873                         break;
3874                 }
3875         }
3877         quit(0);
3879         return 0;