Code

d9c9df8b947765364652b9c4d144395ff52a88b1
[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 "unknown-version"
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 <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <time.h>
35 #include <regex.h>
37 #include <locale.h>
38 #include <langinfo.h>
39 #include <iconv.h>
41 #include <curses.h>
43 #if __GNUC__ >= 3
44 #define __NORETURN __attribute__((__noreturn__))
45 #else
46 #define __NORETURN
47 #endif
49 static void __NORETURN die(const char *err, ...);
50 static void report(const char *msg, ...);
51 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
52 static void set_nonblocking_input(bool loading);
53 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
55 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
56 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
58 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
59 #define STRING_SIZE(x)  (sizeof(x) - 1)
61 #define SIZEOF_STR      1024    /* Default string size. */
62 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
63 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
65 /* Revision graph */
67 #define REVGRAPH_INIT   'I'
68 #define REVGRAPH_MERGE  'M'
69 #define REVGRAPH_BRANCH '+'
70 #define REVGRAPH_COMMIT '*'
71 #define REVGRAPH_LINE   '|'
73 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
75 /* This color name can be used to refer to the default term colors. */
76 #define COLOR_DEFAULT   (-1)
78 #define ICONV_NONE      ((iconv_t) -1)
80 /* The format and size of the date column in the main view. */
81 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
82 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
84 #define AUTHOR_COLS     20
86 /* The default interval between line numbers. */
87 #define NUMBER_INTERVAL 1
89 #define TABSIZE         8
91 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
93 #define TIG_LS_REMOTE \
94         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
96 #define TIG_DIFF_CMD \
97         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
99 #define TIG_LOG_CMD     \
100         "git log --cc --stat -n100 %s 2>/dev/null"
102 #define TIG_MAIN_CMD \
103         "git log --topo-order --pretty=raw %s 2>/dev/null"
105 #define TIG_TREE_CMD    \
106         "git ls-tree %s %s"
108 #define TIG_BLOB_CMD    \
109         "git cat-file blob %s"
111 /* XXX: Needs to be defined to the empty string. */
112 #define TIG_HELP_CMD    ""
113 #define TIG_PAGER_CMD   ""
114 #define TIG_STATUS_CMD  ""
116 /* Some ascii-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char *name;             /* Ref name; tag or head names are shortened. */
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int remote:1;  /* Is it a remote ref? */
127         unsigned int next:1;    /* For ref lists: are there more refs? */
128 };
130 static struct ref **get_refs(char *id);
132 struct int_map {
133         const char *name;
134         int namelen;
135         int value;
136 };
138 static int
139 set_from_int_map(struct int_map *map, size_t map_size,
140                  int *value, const char *name, int namelen)
143         int i;
145         for (i = 0; i < map_size; i++)
146                 if (namelen == map[i].namelen &&
147                     !strncasecmp(name, map[i].name, namelen)) {
148                         *value = map[i].value;
149                         return OK;
150                 }
152         return ERR;
156 /*
157  * String helpers
158  */
160 static inline void
161 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
163         if (srclen > dstlen - 1)
164                 srclen = dstlen - 1;
166         strncpy(dst, src, srclen);
167         dst[srclen] = 0;
170 /* Shorthands for safely copying into a fixed buffer. */
172 #define string_copy(dst, src) \
173         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
175 #define string_ncopy(dst, src, srclen) \
176         string_ncopy_do(dst, sizeof(dst), src, srclen)
178 #define string_copy_rev(dst, src) \
179         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
181 #define string_add(dst, from, src) \
182         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
184 static char *
185 chomp_string(char *name)
187         int namelen;
189         while (isspace(*name))
190                 name++;
192         namelen = strlen(name) - 1;
193         while (namelen > 0 && isspace(name[namelen]))
194                 name[namelen--] = 0;
196         return name;
199 static bool
200 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
202         va_list args;
203         size_t pos = bufpos ? *bufpos : 0;
205         va_start(args, fmt);
206         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
207         va_end(args);
209         if (bufpos)
210                 *bufpos = pos;
212         return pos >= bufsize ? FALSE : TRUE;
215 #define string_format(buf, fmt, args...) \
216         string_nformat(buf, sizeof(buf), NULL, fmt, args)
218 #define string_format_from(buf, from, fmt, args...) \
219         string_nformat(buf, sizeof(buf), from, fmt, args)
221 static int
222 string_enum_compare(const char *str1, const char *str2, int len)
224         size_t i;
226 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
228         /* Diff-Header == DIFF_HEADER */
229         for (i = 0; i < len; i++) {
230                 if (toupper(str1[i]) == toupper(str2[i]))
231                         continue;
233                 if (string_enum_sep(str1[i]) &&
234                     string_enum_sep(str2[i]))
235                         continue;
237                 return str1[i] - str2[i];
238         }
240         return 0;
243 /* Shell quoting
244  *
245  * NOTE: The following is a slightly modified copy of the git project's shell
246  * quoting routines found in the quote.c file.
247  *
248  * Help to copy the thing properly quoted for the shell safety.  any single
249  * quote is replaced with '\'', any exclamation point is replaced with '\!',
250  * and the whole thing is enclosed in a
251  *
252  * E.g.
253  *  original     sq_quote     result
254  *  name     ==> name      ==> 'name'
255  *  a b      ==> a b       ==> 'a b'
256  *  a'b      ==> a'\''b    ==> 'a'\''b'
257  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
258  */
260 static size_t
261 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
263         char c;
265 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
267         BUFPUT('\'');
268         while ((c = *src++)) {
269                 if (c == '\'' || c == '!') {
270                         BUFPUT('\'');
271                         BUFPUT('\\');
272                         BUFPUT(c);
273                         BUFPUT('\'');
274                 } else {
275                         BUFPUT(c);
276                 }
277         }
278         BUFPUT('\'');
280         if (bufsize < SIZEOF_STR)
281                 buf[bufsize] = 0;
283         return bufsize;
287 /*
288  * User requests
289  */
291 #define REQ_INFO \
292         /* XXX: Keep the view request first and in sync with views[]. */ \
293         REQ_GROUP("View switching") \
294         REQ_(VIEW_MAIN,         "Show main view"), \
295         REQ_(VIEW_DIFF,         "Show diff view"), \
296         REQ_(VIEW_LOG,          "Show log view"), \
297         REQ_(VIEW_TREE,         "Show tree view"), \
298         REQ_(VIEW_BLOB,         "Show blob view"), \
299         REQ_(VIEW_HELP,         "Show help page"), \
300         REQ_(VIEW_PAGER,        "Show pager view"), \
301         REQ_(VIEW_STATUS,       "Show status view"), \
302         \
303         REQ_GROUP("View manipulation") \
304         REQ_(ENTER,             "Enter current line and scroll"), \
305         REQ_(NEXT,              "Move to next"), \
306         REQ_(PREVIOUS,          "Move to previous"), \
307         REQ_(VIEW_NEXT,         "Move focus to next view"), \
308         REQ_(VIEW_CLOSE,        "Close the current view"), \
309         REQ_(QUIT,              "Close all views and quit"), \
310         \
311         REQ_GROUP("Cursor navigation") \
312         REQ_(MOVE_UP,           "Move cursor one line up"), \
313         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
314         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
315         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
316         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
317         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
318         \
319         REQ_GROUP("Scrolling") \
320         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
321         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
322         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
323         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
324         \
325         REQ_GROUP("Searching") \
326         REQ_(SEARCH,            "Search the view"), \
327         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
328         REQ_(FIND_NEXT,         "Find next search match"), \
329         REQ_(FIND_PREV,         "Find previous search match"), \
330         \
331         REQ_GROUP("Misc") \
332         REQ_(NONE,              "Do nothing"), \
333         REQ_(PROMPT,            "Bring up the prompt"), \
334         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
335         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
336         REQ_(SHOW_VERSION,      "Show version information"), \
337         REQ_(STOP_LOADING,      "Stop all loading views"), \
338         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
339         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
340         REQ_(STATUS_UPDATE,     "Update file status") \
343 /* User action requests. */
344 enum request {
345 #define REQ_GROUP(help)
346 #define REQ_(req, help) REQ_##req
348         /* Offset all requests to avoid conflicts with ncurses getch values. */
349         REQ_OFFSET = KEY_MAX + 1,
350         REQ_INFO,
351         REQ_UNKNOWN,
353 #undef  REQ_GROUP
354 #undef  REQ_
355 };
357 struct request_info {
358         enum request request;
359         char *name;
360         int namelen;
361         char *help;
362 };
364 static struct request_info req_info[] = {
365 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
366 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
367         REQ_INFO
368 #undef  REQ_GROUP
369 #undef  REQ_
370 };
372 static enum request
373 get_request(const char *name)
375         int namelen = strlen(name);
376         int i;
378         for (i = 0; i < ARRAY_SIZE(req_info); i++)
379                 if (req_info[i].namelen == namelen &&
380                     !string_enum_compare(req_info[i].name, name, namelen))
381                         return req_info[i].request;
383         return REQ_UNKNOWN;
387 /*
388  * Options
389  */
391 static const char usage[] =
392 "tig " VERSION " (" __DATE__ ")\n"
393 "\n"
394 "Usage: tig [options]\n"
395 "   or: tig [options] [--] [git log options]\n"
396 "   or: tig [options] log  [git log options]\n"
397 "   or: tig [options] diff [git diff options]\n"
398 "   or: tig [options] show [git show options]\n"
399 "   or: tig [options] <    [git command output]\n"
400 "\n"
401 "Options:\n"
402 "  -l                          Start up in log view\n"
403 "  -d                          Start up in diff view\n"
404 "  -S                          Start up in status view\n"
405 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
406 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
407 "  --                          Mark end of tig options\n"
408 "  -v, --version               Show version and exit\n"
409 "  -h, --help                  Show help message and exit\n";
411 /* Option and state variables. */
412 static bool opt_line_number             = FALSE;
413 static bool opt_rev_graph               = FALSE;
414 static int opt_num_interval             = NUMBER_INTERVAL;
415 static int opt_tab_size                 = TABSIZE;
416 static enum request opt_request         = REQ_VIEW_MAIN;
417 static char opt_cmd[SIZEOF_STR]         = "";
418 static char opt_path[SIZEOF_STR]        = "";
419 static FILE *opt_pipe                   = NULL;
420 static char opt_encoding[20]            = "UTF-8";
421 static bool opt_utf8                    = TRUE;
422 static char opt_codeset[20]             = "UTF-8";
423 static iconv_t opt_iconv                = ICONV_NONE;
424 static char opt_search[SIZEOF_STR]      = "";
425 static char opt_cdup[SIZEOF_STR]        = "";
426 static char opt_git_dir[SIZEOF_STR]     = "";
428 enum option_type {
429         OPT_NONE,
430         OPT_INT,
431 };
433 static bool
434 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
436         va_list args;
437         char *value = "";
438         int *number;
440         if (opt[0] != '-')
441                 return FALSE;
443         if (opt[1] == '-') {
444                 int namelen = strlen(name);
446                 opt += 2;
448                 if (strncmp(opt, name, namelen))
449                         return FALSE;
451                 if (opt[namelen] == '=')
452                         value = opt + namelen + 1;
454         } else {
455                 if (!short_name || opt[1] != short_name)
456                         return FALSE;
457                 value = opt + 2;
458         }
460         va_start(args, type);
461         if (type == OPT_INT) {
462                 number = va_arg(args, int *);
463                 if (isdigit(*value))
464                         *number = atoi(value);
465         }
466         va_end(args);
468         return TRUE;
471 /* Returns the index of log or diff command or -1 to exit. */
472 static bool
473 parse_options(int argc, char *argv[])
475         int i;
477         for (i = 1; i < argc; i++) {
478                 char *opt = argv[i];
480                 if (!strcmp(opt, "log") ||
481                     !strcmp(opt, "diff") ||
482                     !strcmp(opt, "show")) {
483                         opt_request = opt[0] == 'l'
484                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
485                         break;
486                 }
488                 if (opt[0] && opt[0] != '-')
489                         break;
491                 if (!strcmp(opt, "-l")) {
492                         opt_request = REQ_VIEW_LOG;
493                         continue;
494                 }
496                 if (!strcmp(opt, "-d")) {
497                         opt_request = REQ_VIEW_DIFF;
498                         continue;
499                 }
501                 if (!strcmp(opt, "-S")) {
502                         opt_request = REQ_VIEW_STATUS;
503                         break;
504                 }
506                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
507                         opt_line_number = TRUE;
508                         continue;
509                 }
511                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
512                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
513                         continue;
514                 }
516                 if (check_option(opt, 'v', "version", OPT_NONE)) {
517                         printf("tig version %s\n", VERSION);
518                         return FALSE;
519                 }
521                 if (check_option(opt, 'h', "help", OPT_NONE)) {
522                         printf(usage);
523                         return FALSE;
524                 }
526                 if (!strcmp(opt, "--")) {
527                         i++;
528                         break;
529                 }
531                 die("unknown option '%s'\n\n%s", opt, usage);
532         }
534         if (!isatty(STDIN_FILENO)) {
535                 opt_request = REQ_VIEW_PAGER;
536                 opt_pipe = stdin;
538         } else if (i < argc) {
539                 size_t buf_size;
541                 if (opt_request == REQ_VIEW_MAIN)
542                         /* XXX: This is vulnerable to the user overriding
543                          * options required for the main view parser. */
544                         string_copy(opt_cmd, "git log --pretty=raw");
545                 else
546                         string_copy(opt_cmd, "git");
547                 buf_size = strlen(opt_cmd);
549                 while (buf_size < sizeof(opt_cmd) && i < argc) {
550                         opt_cmd[buf_size++] = ' ';
551                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
552                 }
554                 if (buf_size >= sizeof(opt_cmd))
555                         die("command too long");
557                 opt_cmd[buf_size] = 0;
558         }
560         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
561                 opt_utf8 = FALSE;
563         return TRUE;
567 /*
568  * Line-oriented content detection.
569  */
571 #define LINE_INFO \
572 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
573 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
574 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
575 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
576 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
577 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
579 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
580 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
581 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
582 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
583 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
584 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
585 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
586 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
587 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
588 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
591 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
593 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
595 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
596 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
597 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
598 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
599 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
600 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
601 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
602 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
603 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
604 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
605 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
607 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
608 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
610 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
611 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
612 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
613 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
614 LINE(STAT_SECTION, "",                  COLOR_DEFAULT,  COLOR_BLUE,     A_BOLD), \
615 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
616 LINE(STAT_STAGED,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
617 LINE(STAT_UNSTAGED,"",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
620 enum line_type {
621 #define LINE(type, line, fg, bg, attr) \
622         LINE_##type
623         LINE_INFO
624 #undef  LINE
625 };
627 struct line_info {
628         const char *name;       /* Option name. */
629         int namelen;            /* Size of option name. */
630         const char *line;       /* The start of line to match. */
631         int linelen;            /* Size of string to match. */
632         int fg, bg, attr;       /* Color and text attributes for the lines. */
633 };
635 static struct line_info line_info[] = {
636 #define LINE(type, line, fg, bg, attr) \
637         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
638         LINE_INFO
639 #undef  LINE
640 };
642 static enum line_type
643 get_line_type(char *line)
645         int linelen = strlen(line);
646         enum line_type type;
648         for (type = 0; type < ARRAY_SIZE(line_info); type++)
649                 /* Case insensitive search matches Signed-off-by lines better. */
650                 if (linelen >= line_info[type].linelen &&
651                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
652                         return type;
654         return LINE_DEFAULT;
657 static inline int
658 get_line_attr(enum line_type type)
660         assert(type < ARRAY_SIZE(line_info));
661         return COLOR_PAIR(type) | line_info[type].attr;
664 static struct line_info *
665 get_line_info(char *name, int namelen)
667         enum line_type type;
669         for (type = 0; type < ARRAY_SIZE(line_info); type++)
670                 if (namelen == line_info[type].namelen &&
671                     !string_enum_compare(line_info[type].name, name, namelen))
672                         return &line_info[type];
674         return NULL;
677 static void
678 init_colors(void)
680         int default_bg = COLOR_BLACK;
681         int default_fg = COLOR_WHITE;
682         enum line_type type;
684         start_color();
686         if (use_default_colors() != ERR) {
687                 default_bg = -1;
688                 default_fg = -1;
689         }
691         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
692                 struct line_info *info = &line_info[type];
693                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
694                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
696                 init_pair(type, fg, bg);
697         }
700 struct line {
701         enum line_type type;
703         /* State flags */
704         unsigned int selected:1;
706         void *data;             /* User data */
707 };
710 /*
711  * Keys
712  */
714 struct keybinding {
715         int alias;
716         enum request request;
717         struct keybinding *next;
718 };
720 static struct keybinding default_keybindings[] = {
721         /* View switching */
722         { 'm',          REQ_VIEW_MAIN },
723         { 'd',          REQ_VIEW_DIFF },
724         { 'l',          REQ_VIEW_LOG },
725         { 't',          REQ_VIEW_TREE },
726         { 'f',          REQ_VIEW_BLOB },
727         { 'p',          REQ_VIEW_PAGER },
728         { 'h',          REQ_VIEW_HELP },
729         { 'S',          REQ_VIEW_STATUS },
731         /* View manipulation */
732         { 'q',          REQ_VIEW_CLOSE },
733         { KEY_TAB,      REQ_VIEW_NEXT },
734         { KEY_RETURN,   REQ_ENTER },
735         { KEY_UP,       REQ_PREVIOUS },
736         { KEY_DOWN,     REQ_NEXT },
738         /* Cursor navigation */
739         { 'k',          REQ_MOVE_UP },
740         { 'j',          REQ_MOVE_DOWN },
741         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
742         { KEY_END,      REQ_MOVE_LAST_LINE },
743         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
744         { ' ',          REQ_MOVE_PAGE_DOWN },
745         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
746         { 'b',          REQ_MOVE_PAGE_UP },
747         { '-',          REQ_MOVE_PAGE_UP },
749         /* Scrolling */
750         { KEY_IC,       REQ_SCROLL_LINE_UP },
751         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
752         { 'w',          REQ_SCROLL_PAGE_UP },
753         { 's',          REQ_SCROLL_PAGE_DOWN },
755         /* Searching */
756         { '/',          REQ_SEARCH },
757         { '?',          REQ_SEARCH_BACK },
758         { 'n',          REQ_FIND_NEXT },
759         { 'N',          REQ_FIND_PREV },
761         /* Misc */
762         { 'Q',          REQ_QUIT },
763         { 'z',          REQ_STOP_LOADING },
764         { 'v',          REQ_SHOW_VERSION },
765         { 'r',          REQ_SCREEN_REDRAW },
766         { '.',          REQ_TOGGLE_LINENO },
767         { 'g',          REQ_TOGGLE_REV_GRAPH },
768         { ':',          REQ_PROMPT },
769         { 'u',          REQ_STATUS_UPDATE },
771         /* Using the ncurses SIGWINCH handler. */
772         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
773 };
775 #define KEYMAP_INFO \
776         KEYMAP_(GENERIC), \
777         KEYMAP_(MAIN), \
778         KEYMAP_(DIFF), \
779         KEYMAP_(LOG), \
780         KEYMAP_(TREE), \
781         KEYMAP_(BLOB), \
782         KEYMAP_(PAGER), \
783         KEYMAP_(HELP), \
784         KEYMAP_(STATUS)
786 enum keymap {
787 #define KEYMAP_(name) KEYMAP_##name
788         KEYMAP_INFO
789 #undef  KEYMAP_
790 };
792 static struct int_map keymap_table[] = {
793 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
794         KEYMAP_INFO
795 #undef  KEYMAP_
796 };
798 #define set_keymap(map, name) \
799         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
801 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
803 static void
804 add_keybinding(enum keymap keymap, enum request request, int key)
806         struct keybinding *keybinding;
808         keybinding = calloc(1, sizeof(*keybinding));
809         if (!keybinding)
810                 die("Failed to allocate keybinding");
812         keybinding->alias = key;
813         keybinding->request = request;
814         keybinding->next = keybindings[keymap];
815         keybindings[keymap] = keybinding;
818 /* Looks for a key binding first in the given map, then in the generic map, and
819  * lastly in the default keybindings. */
820 static enum request
821 get_keybinding(enum keymap keymap, int key)
823         struct keybinding *kbd;
824         int i;
826         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
827                 if (kbd->alias == key)
828                         return kbd->request;
830         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
831                 if (kbd->alias == key)
832                         return kbd->request;
834         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
835                 if (default_keybindings[i].alias == key)
836                         return default_keybindings[i].request;
838         return (enum request) key;
842 struct key {
843         char *name;
844         int value;
845 };
847 static struct key key_table[] = {
848         { "Enter",      KEY_RETURN },
849         { "Space",      ' ' },
850         { "Backspace",  KEY_BACKSPACE },
851         { "Tab",        KEY_TAB },
852         { "Escape",     KEY_ESC },
853         { "Left",       KEY_LEFT },
854         { "Right",      KEY_RIGHT },
855         { "Up",         KEY_UP },
856         { "Down",       KEY_DOWN },
857         { "Insert",     KEY_IC },
858         { "Delete",     KEY_DC },
859         { "Hash",       '#' },
860         { "Home",       KEY_HOME },
861         { "End",        KEY_END },
862         { "PageUp",     KEY_PPAGE },
863         { "PageDown",   KEY_NPAGE },
864         { "F1",         KEY_F(1) },
865         { "F2",         KEY_F(2) },
866         { "F3",         KEY_F(3) },
867         { "F4",         KEY_F(4) },
868         { "F5",         KEY_F(5) },
869         { "F6",         KEY_F(6) },
870         { "F7",         KEY_F(7) },
871         { "F8",         KEY_F(8) },
872         { "F9",         KEY_F(9) },
873         { "F10",        KEY_F(10) },
874         { "F11",        KEY_F(11) },
875         { "F12",        KEY_F(12) },
876 };
878 static int
879 get_key_value(const char *name)
881         int i;
883         for (i = 0; i < ARRAY_SIZE(key_table); i++)
884                 if (!strcasecmp(key_table[i].name, name))
885                         return key_table[i].value;
887         if (strlen(name) == 1 && isprint(*name))
888                 return (int) *name;
890         return ERR;
893 static char *
894 get_key(enum request request)
896         static char buf[BUFSIZ];
897         static char key_char[] = "'X'";
898         size_t pos = 0;
899         char *sep = "";
900         int i;
902         buf[pos] = 0;
904         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
905                 struct keybinding *keybinding = &default_keybindings[i];
906                 char *seq = NULL;
907                 int key;
909                 if (keybinding->request != request)
910                         continue;
912                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
913                         if (key_table[key].value == keybinding->alias)
914                                 seq = key_table[key].name;
916                 if (seq == NULL &&
917                     keybinding->alias < 127 &&
918                     isprint(keybinding->alias)) {
919                         key_char[1] = (char) keybinding->alias;
920                         seq = key_char;
921                 }
923                 if (!seq)
924                         seq = "'?'";
926                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
927                         return "Too many keybindings!";
928                 sep = ", ";
929         }
931         return buf;
935 /*
936  * User config file handling.
937  */
939 static struct int_map color_map[] = {
940 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
941         COLOR_MAP(DEFAULT),
942         COLOR_MAP(BLACK),
943         COLOR_MAP(BLUE),
944         COLOR_MAP(CYAN),
945         COLOR_MAP(GREEN),
946         COLOR_MAP(MAGENTA),
947         COLOR_MAP(RED),
948         COLOR_MAP(WHITE),
949         COLOR_MAP(YELLOW),
950 };
952 #define set_color(color, name) \
953         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
955 static struct int_map attr_map[] = {
956 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
957         ATTR_MAP(NORMAL),
958         ATTR_MAP(BLINK),
959         ATTR_MAP(BOLD),
960         ATTR_MAP(DIM),
961         ATTR_MAP(REVERSE),
962         ATTR_MAP(STANDOUT),
963         ATTR_MAP(UNDERLINE),
964 };
966 #define set_attribute(attr, name) \
967         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
969 static int   config_lineno;
970 static bool  config_errors;
971 static char *config_msg;
973 /* Wants: object fgcolor bgcolor [attr] */
974 static int
975 option_color_command(int argc, char *argv[])
977         struct line_info *info;
979         if (argc != 3 && argc != 4) {
980                 config_msg = "Wrong number of arguments given to color command";
981                 return ERR;
982         }
984         info = get_line_info(argv[0], strlen(argv[0]));
985         if (!info) {
986                 config_msg = "Unknown color name";
987                 return ERR;
988         }
990         if (set_color(&info->fg, argv[1]) == ERR ||
991             set_color(&info->bg, argv[2]) == ERR) {
992                 config_msg = "Unknown color";
993                 return ERR;
994         }
996         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
997                 config_msg = "Unknown attribute";
998                 return ERR;
999         }
1001         return OK;
1004 /* Wants: name = value */
1005 static int
1006 option_set_command(int argc, char *argv[])
1008         if (argc != 3) {
1009                 config_msg = "Wrong number of arguments given to set command";
1010                 return ERR;
1011         }
1013         if (strcmp(argv[1], "=")) {
1014                 config_msg = "No value assigned";
1015                 return ERR;
1016         }
1018         if (!strcmp(argv[0], "show-rev-graph")) {
1019                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1020                                  !strcmp(argv[2], "true") ||
1021                                  !strcmp(argv[2], "yes"));
1022                 return OK;
1023         }
1025         if (!strcmp(argv[0], "line-number-interval")) {
1026                 opt_num_interval = atoi(argv[2]);
1027                 return OK;
1028         }
1030         if (!strcmp(argv[0], "tab-size")) {
1031                 opt_tab_size = atoi(argv[2]);
1032                 return OK;
1033         }
1035         if (!strcmp(argv[0], "commit-encoding")) {
1036                 char *arg = argv[2];
1037                 int delimiter = *arg;
1038                 int i;
1040                 switch (delimiter) {
1041                 case '"':
1042                 case '\'':
1043                         for (arg++, i = 0; arg[i]; i++)
1044                                 if (arg[i] == delimiter) {
1045                                         arg[i] = 0;
1046                                         break;
1047                                 }
1048                 default:
1049                         string_ncopy(opt_encoding, arg, strlen(arg));
1050                         return OK;
1051                 }
1052         }
1054         config_msg = "Unknown variable name";
1055         return ERR;
1058 /* Wants: mode request key */
1059 static int
1060 option_bind_command(int argc, char *argv[])
1062         enum request request;
1063         int keymap;
1064         int key;
1066         if (argc != 3) {
1067                 config_msg = "Wrong number of arguments given to bind command";
1068                 return ERR;
1069         }
1071         if (set_keymap(&keymap, argv[0]) == ERR) {
1072                 config_msg = "Unknown key map";
1073                 return ERR;
1074         }
1076         key = get_key_value(argv[1]);
1077         if (key == ERR) {
1078                 config_msg = "Unknown key";
1079                 return ERR;
1080         }
1082         request = get_request(argv[2]);
1083         if (request == REQ_UNKNOWN) {
1084                 config_msg = "Unknown request name";
1085                 return ERR;
1086         }
1088         add_keybinding(keymap, request, key);
1090         return OK;
1093 static int
1094 set_option(char *opt, char *value)
1096         char *argv[16];
1097         int valuelen;
1098         int argc = 0;
1100         /* Tokenize */
1101         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1102                 argv[argc++] = value;
1104                 value += valuelen;
1105                 if (!*value)
1106                         break;
1108                 *value++ = 0;
1109                 while (isspace(*value))
1110                         value++;
1111         }
1113         if (!strcmp(opt, "color"))
1114                 return option_color_command(argc, argv);
1116         if (!strcmp(opt, "set"))
1117                 return option_set_command(argc, argv);
1119         if (!strcmp(opt, "bind"))
1120                 return option_bind_command(argc, argv);
1122         config_msg = "Unknown option command";
1123         return ERR;
1126 static int
1127 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1129         int status = OK;
1131         config_lineno++;
1132         config_msg = "Internal error";
1134         /* Check for comment markers, since read_properties() will
1135          * only ensure opt and value are split at first " \t". */
1136         optlen = strcspn(opt, "#");
1137         if (optlen == 0)
1138                 return OK;
1140         if (opt[optlen] != 0) {
1141                 config_msg = "No option value";
1142                 status = ERR;
1144         }  else {
1145                 /* Look for comment endings in the value. */
1146                 size_t len = strcspn(value, "#");
1148                 if (len < valuelen) {
1149                         valuelen = len;
1150                         value[valuelen] = 0;
1151                 }
1153                 status = set_option(opt, value);
1154         }
1156         if (status == ERR) {
1157                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1158                         config_lineno, (int) optlen, opt, config_msg);
1159                 config_errors = TRUE;
1160         }
1162         /* Always keep going if errors are encountered. */
1163         return OK;
1166 static int
1167 load_options(void)
1169         char *home = getenv("HOME");
1170         char buf[SIZEOF_STR];
1171         FILE *file;
1173         config_lineno = 0;
1174         config_errors = FALSE;
1176         if (!home || !string_format(buf, "%s/.tigrc", home))
1177                 return ERR;
1179         /* It's ok that the file doesn't exist. */
1180         file = fopen(buf, "r");
1181         if (!file)
1182                 return OK;
1184         if (read_properties(file, " \t", read_option) == ERR ||
1185             config_errors == TRUE)
1186                 fprintf(stderr, "Errors while loading %s.\n", buf);
1188         return OK;
1192 /*
1193  * The viewer
1194  */
1196 struct view;
1197 struct view_ops;
1199 /* The display array of active views and the index of the current view. */
1200 static struct view *display[2];
1201 static unsigned int current_view;
1203 /* Reading from the prompt? */
1204 static bool input_mode = FALSE;
1206 #define foreach_displayed_view(view, i) \
1207         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1209 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1211 /* Current head and commit ID */
1212 static char ref_blob[SIZEOF_REF]        = "";
1213 static char ref_commit[SIZEOF_REF]      = "HEAD";
1214 static char ref_head[SIZEOF_REF]        = "HEAD";
1216 struct view {
1217         const char *name;       /* View name */
1218         const char *cmd_fmt;    /* Default command line format */
1219         const char *cmd_env;    /* Command line set via environment */
1220         const char *id;         /* Points to either of ref_{head,commit,blob} */
1222         struct view_ops *ops;   /* View operations */
1224         enum keymap keymap;     /* What keymap does this view have */
1226         char cmd[SIZEOF_STR];   /* Command buffer */
1227         char ref[SIZEOF_REF];   /* Hovered commit reference */
1228         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1230         int height, width;      /* The width and height of the main window */
1231         WINDOW *win;            /* The main window */
1232         WINDOW *title;          /* The title window living below the main window */
1234         /* Navigation */
1235         unsigned long offset;   /* Offset of the window top */
1236         unsigned long lineno;   /* Current line number */
1238         /* Searching */
1239         char grep[SIZEOF_STR];  /* Search string */
1240         regex_t *regex;         /* Pre-compiled regex */
1242         /* If non-NULL, points to the view that opened this view. If this view
1243          * is closed tig will switch back to the parent view. */
1244         struct view *parent;
1246         /* Buffering */
1247         unsigned long lines;    /* Total number of lines */
1248         struct line *line;      /* Line index */
1249         unsigned long line_size;/* Total number of allocated lines */
1250         unsigned int digits;    /* Number of digits in the lines member. */
1252         /* Loading */
1253         FILE *pipe;
1254         time_t start_time;
1255 };
1257 struct view_ops {
1258         /* What type of content being displayed. Used in the title bar. */
1259         const char *type;
1260         /* Open and reads in all view content. */
1261         bool (*open)(struct view *view);
1262         /* Read one line; updates view->line. */
1263         bool (*read)(struct view *view, char *data);
1264         /* Draw one line; @lineno must be < view->height. */
1265         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1266         /* Depending on view, change display based on current line. */
1267         bool (*enter)(struct view *view, struct line *line);
1268         /* Search for regex in a line. */
1269         bool (*grep)(struct view *view, struct line *line);
1270         /* Select line */
1271         void (*select)(struct view *view, struct line *line);
1272 };
1274 static struct view_ops pager_ops;
1275 static struct view_ops main_ops;
1276 static struct view_ops tree_ops;
1277 static struct view_ops blob_ops;
1278 static struct view_ops help_ops;
1279 static struct view_ops status_ops;
1281 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1282         { name, cmd, #env, ref, ops, map}
1284 #define VIEW_(id, name, ops, ref) \
1285         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1288 static struct view views[] = {
1289         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1290         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1291         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1292         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1293         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1294         VIEW_(HELP,   "help",   &help_ops,   ""),
1295         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1296         VIEW_(STATUS, "status", &status_ops, ""),
1297 };
1299 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1301 #define foreach_view(view, i) \
1302         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1304 #define view_is_displayed(view) \
1305         (view == display[0] || view == display[1])
1307 static bool
1308 draw_view_line(struct view *view, unsigned int lineno)
1310         struct line *line;
1311         bool selected = (view->offset + lineno == view->lineno);
1312         bool draw_ok;
1314         assert(view_is_displayed(view));
1316         if (view->offset + lineno >= view->lines)
1317                 return FALSE;
1319         line = &view->line[view->offset + lineno];
1321         if (selected) {
1322                 line->selected = TRUE;
1323                 view->ops->select(view, line);
1324         } else if (line->selected) {
1325                 line->selected = FALSE;
1326                 wmove(view->win, lineno, 0);
1327                 wclrtoeol(view->win);
1328         }
1330         scrollok(view->win, FALSE);
1331         draw_ok = view->ops->draw(view, line, lineno, selected);
1332         scrollok(view->win, TRUE);
1334         return draw_ok;
1337 static void
1338 redraw_view_from(struct view *view, int lineno)
1340         assert(0 <= lineno && lineno < view->height);
1342         for (; lineno < view->height; lineno++) {
1343                 if (!draw_view_line(view, lineno))
1344                         break;
1345         }
1347         redrawwin(view->win);
1348         if (input_mode)
1349                 wnoutrefresh(view->win);
1350         else
1351                 wrefresh(view->win);
1354 static void
1355 redraw_view(struct view *view)
1357         wclear(view->win);
1358         redraw_view_from(view, 0);
1362 static void
1363 update_view_title(struct view *view)
1365         char buf[SIZEOF_STR];
1366         char state[SIZEOF_STR];
1367         size_t bufpos = 0, statelen = 0;
1369         assert(view_is_displayed(view));
1371         if (view->lines || view->pipe) {
1372                 unsigned int view_lines = view->offset + view->height;
1373                 unsigned int lines = view->lines
1374                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1375                                    : 0;
1377                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1378                                    view->ops->type,
1379                                    view->lineno + 1,
1380                                    view->lines,
1381                                    lines);
1383                 if (view->pipe) {
1384                         time_t secs = time(NULL) - view->start_time;
1386                         /* Three git seconds are a long time ... */
1387                         if (secs > 2)
1388                                 string_format_from(state, &statelen, " %lds", secs);
1389                 }
1390         }
1392         string_format_from(buf, &bufpos, "[%s]", view->name);
1393         if (*view->ref && bufpos < view->width) {
1394                 size_t refsize = strlen(view->ref);
1395                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1397                 if (minsize < view->width)
1398                         refsize = view->width - minsize + 7;
1399                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1400         }
1402         if (statelen && bufpos < view->width) {
1403                 string_format_from(buf, &bufpos, " %s", state);
1404         }
1406         if (view == display[current_view])
1407                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1408         else
1409                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1411         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1412         wclrtoeol(view->title);
1413         wmove(view->title, 0, view->width - 1);
1415         if (input_mode)
1416                 wnoutrefresh(view->title);
1417         else
1418                 wrefresh(view->title);
1421 static void
1422 resize_display(void)
1424         int offset, i;
1425         struct view *base = display[0];
1426         struct view *view = display[1] ? display[1] : display[0];
1428         /* Setup window dimensions */
1430         getmaxyx(stdscr, base->height, base->width);
1432         /* Make room for the status window. */
1433         base->height -= 1;
1435         if (view != base) {
1436                 /* Horizontal split. */
1437                 view->width   = base->width;
1438                 view->height  = SCALE_SPLIT_VIEW(base->height);
1439                 base->height -= view->height;
1441                 /* Make room for the title bar. */
1442                 view->height -= 1;
1443         }
1445         /* Make room for the title bar. */
1446         base->height -= 1;
1448         offset = 0;
1450         foreach_displayed_view (view, i) {
1451                 if (!view->win) {
1452                         view->win = newwin(view->height, 0, offset, 0);
1453                         if (!view->win)
1454                                 die("Failed to create %s view", view->name);
1456                         scrollok(view->win, TRUE);
1458                         view->title = newwin(1, 0, offset + view->height, 0);
1459                         if (!view->title)
1460                                 die("Failed to create title window");
1462                 } else {
1463                         wresize(view->win, view->height, view->width);
1464                         mvwin(view->win,   offset, 0);
1465                         mvwin(view->title, offset + view->height, 0);
1466                 }
1468                 offset += view->height + 1;
1469         }
1472 static void
1473 redraw_display(void)
1475         struct view *view;
1476         int i;
1478         foreach_displayed_view (view, i) {
1479                 redraw_view(view);
1480                 update_view_title(view);
1481         }
1484 static void
1485 update_display_cursor(struct view *view)
1487         /* Move the cursor to the right-most column of the cursor line.
1488          *
1489          * XXX: This could turn out to be a bit expensive, but it ensures that
1490          * the cursor does not jump around. */
1491         if (view->lines) {
1492                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1493                 wrefresh(view->win);
1494         }
1497 /*
1498  * Navigation
1499  */
1501 /* Scrolling backend */
1502 static void
1503 do_scroll_view(struct view *view, int lines)
1505         bool redraw_current_line = FALSE;
1507         /* The rendering expects the new offset. */
1508         view->offset += lines;
1510         assert(0 <= view->offset && view->offset < view->lines);
1511         assert(lines);
1513         /* Move current line into the view. */
1514         if (view->lineno < view->offset) {
1515                 view->lineno = view->offset;
1516                 redraw_current_line = TRUE;
1517         } else if (view->lineno >= view->offset + view->height) {
1518                 view->lineno = view->offset + view->height - 1;
1519                 redraw_current_line = TRUE;
1520         }
1522         assert(view->offset <= view->lineno && view->lineno < view->lines);
1524         /* Redraw the whole screen if scrolling is pointless. */
1525         if (view->height < ABS(lines)) {
1526                 redraw_view(view);
1528         } else {
1529                 int line = lines > 0 ? view->height - lines : 0;
1530                 int end = line + ABS(lines);
1532                 wscrl(view->win, lines);
1534                 for (; line < end; line++) {
1535                         if (!draw_view_line(view, line))
1536                                 break;
1537                 }
1539                 if (redraw_current_line)
1540                         draw_view_line(view, view->lineno - view->offset);
1541         }
1543         redrawwin(view->win);
1544         wrefresh(view->win);
1545         report("");
1548 /* Scroll frontend */
1549 static void
1550 scroll_view(struct view *view, enum request request)
1552         int lines = 1;
1554         assert(view_is_displayed(view));
1556         switch (request) {
1557         case REQ_SCROLL_PAGE_DOWN:
1558                 lines = view->height;
1559         case REQ_SCROLL_LINE_DOWN:
1560                 if (view->offset + lines > view->lines)
1561                         lines = view->lines - view->offset;
1563                 if (lines == 0 || view->offset + view->height >= view->lines) {
1564                         report("Cannot scroll beyond the last line");
1565                         return;
1566                 }
1567                 break;
1569         case REQ_SCROLL_PAGE_UP:
1570                 lines = view->height;
1571         case REQ_SCROLL_LINE_UP:
1572                 if (lines > view->offset)
1573                         lines = view->offset;
1575                 if (lines == 0) {
1576                         report("Cannot scroll beyond the first line");
1577                         return;
1578                 }
1580                 lines = -lines;
1581                 break;
1583         default:
1584                 die("request %d not handled in switch", request);
1585         }
1587         do_scroll_view(view, lines);
1590 /* Cursor moving */
1591 static void
1592 move_view(struct view *view, enum request request)
1594         int scroll_steps = 0;
1595         int steps;
1597         switch (request) {
1598         case REQ_MOVE_FIRST_LINE:
1599                 steps = -view->lineno;
1600                 break;
1602         case REQ_MOVE_LAST_LINE:
1603                 steps = view->lines - view->lineno - 1;
1604                 break;
1606         case REQ_MOVE_PAGE_UP:
1607                 steps = view->height > view->lineno
1608                       ? -view->lineno : -view->height;
1609                 break;
1611         case REQ_MOVE_PAGE_DOWN:
1612                 steps = view->lineno + view->height >= view->lines
1613                       ? view->lines - view->lineno - 1 : view->height;
1614                 break;
1616         case REQ_MOVE_UP:
1617                 steps = -1;
1618                 break;
1620         case REQ_MOVE_DOWN:
1621                 steps = 1;
1622                 break;
1624         default:
1625                 die("request %d not handled in switch", request);
1626         }
1628         if (steps <= 0 && view->lineno == 0) {
1629                 report("Cannot move beyond the first line");
1630                 return;
1632         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1633                 report("Cannot move beyond the last line");
1634                 return;
1635         }
1637         /* Move the current line */
1638         view->lineno += steps;
1639         assert(0 <= view->lineno && view->lineno < view->lines);
1641         /* Check whether the view needs to be scrolled */
1642         if (view->lineno < view->offset ||
1643             view->lineno >= view->offset + view->height) {
1644                 scroll_steps = steps;
1645                 if (steps < 0 && -steps > view->offset) {
1646                         scroll_steps = -view->offset;
1648                 } else if (steps > 0) {
1649                         if (view->lineno == view->lines - 1 &&
1650                             view->lines > view->height) {
1651                                 scroll_steps = view->lines - view->offset - 1;
1652                                 if (scroll_steps >= view->height)
1653                                         scroll_steps -= view->height - 1;
1654                         }
1655                 }
1656         }
1658         if (!view_is_displayed(view)) {
1659                 view->offset += scroll_steps;
1660                 assert(0 <= view->offset && view->offset < view->lines);
1661                 view->ops->select(view, &view->line[view->lineno]);
1662                 return;
1663         }
1665         /* Repaint the old "current" line if we be scrolling */
1666         if (ABS(steps) < view->height)
1667                 draw_view_line(view, view->lineno - steps - view->offset);
1669         if (scroll_steps) {
1670                 do_scroll_view(view, scroll_steps);
1671                 return;
1672         }
1674         /* Draw the current line */
1675         draw_view_line(view, view->lineno - view->offset);
1677         redrawwin(view->win);
1678         wrefresh(view->win);
1679         report("");
1683 /*
1684  * Searching
1685  */
1687 static void search_view(struct view *view, enum request request);
1689 static bool
1690 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1692         assert(view_is_displayed(view));
1694         if (!view->ops->grep(view, line))
1695                 return FALSE;
1697         if (lineno - view->offset >= view->height) {
1698                 view->offset = lineno;
1699                 view->lineno = lineno;
1700                 redraw_view(view);
1702         } else {
1703                 unsigned long old_lineno = view->lineno - view->offset;
1705                 view->lineno = lineno;
1706                 draw_view_line(view, old_lineno);
1708                 draw_view_line(view, view->lineno - view->offset);
1709                 redrawwin(view->win);
1710                 wrefresh(view->win);
1711         }
1713         report("Line %ld matches '%s'", lineno + 1, view->grep);
1714         return TRUE;
1717 static void
1718 find_next(struct view *view, enum request request)
1720         unsigned long lineno = view->lineno;
1721         int direction;
1723         if (!*view->grep) {
1724                 if (!*opt_search)
1725                         report("No previous search");
1726                 else
1727                         search_view(view, request);
1728                 return;
1729         }
1731         switch (request) {
1732         case REQ_SEARCH:
1733         case REQ_FIND_NEXT:
1734                 direction = 1;
1735                 break;
1737         case REQ_SEARCH_BACK:
1738         case REQ_FIND_PREV:
1739                 direction = -1;
1740                 break;
1742         default:
1743                 return;
1744         }
1746         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1747                 lineno += direction;
1749         /* Note, lineno is unsigned long so will wrap around in which case it
1750          * will become bigger than view->lines. */
1751         for (; lineno < view->lines; lineno += direction) {
1752                 struct line *line = &view->line[lineno];
1754                 if (find_next_line(view, lineno, line))
1755                         return;
1756         }
1758         report("No match found for '%s'", view->grep);
1761 static void
1762 search_view(struct view *view, enum request request)
1764         int regex_err;
1766         if (view->regex) {
1767                 regfree(view->regex);
1768                 *view->grep = 0;
1769         } else {
1770                 view->regex = calloc(1, sizeof(*view->regex));
1771                 if (!view->regex)
1772                         return;
1773         }
1775         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1776         if (regex_err != 0) {
1777                 char buf[SIZEOF_STR] = "unknown error";
1779                 regerror(regex_err, view->regex, buf, sizeof(buf));
1780                 report("Search failed: %s", buf);
1781                 return;
1782         }
1784         string_copy(view->grep, opt_search);
1786         find_next(view, request);
1789 /*
1790  * Incremental updating
1791  */
1793 static void
1794 end_update(struct view *view)
1796         if (!view->pipe)
1797                 return;
1798         set_nonblocking_input(FALSE);
1799         if (view->pipe == stdin)
1800                 fclose(view->pipe);
1801         else
1802                 pclose(view->pipe);
1803         view->pipe = NULL;
1806 static bool
1807 begin_update(struct view *view)
1809         if (view->pipe)
1810                 end_update(view);
1812         if (opt_cmd[0]) {
1813                 string_copy(view->cmd, opt_cmd);
1814                 opt_cmd[0] = 0;
1815                 /* When running random commands, initially show the
1816                  * command in the title. However, it maybe later be
1817                  * overwritten if a commit line is selected. */
1818                 string_copy(view->ref, view->cmd);
1820         } else if (view == VIEW(REQ_VIEW_TREE)) {
1821                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1822                 char path[SIZEOF_STR];
1824                 if (strcmp(view->vid, view->id))
1825                         opt_path[0] = path[0] = 0;
1826                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1827                         return FALSE;
1829                 if (!string_format(view->cmd, format, view->id, path))
1830                         return FALSE;
1832         } else {
1833                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1834                 const char *id = view->id;
1836                 if (!string_format(view->cmd, format, id, id, id, id, id))
1837                         return FALSE;
1839                 /* Put the current ref_* value to the view title ref
1840                  * member. This is needed by the blob view. Most other
1841                  * views sets it automatically after loading because the
1842                  * first line is a commit line. */
1843                 string_copy_rev(view->ref, view->id);
1844         }
1846         /* Special case for the pager view. */
1847         if (opt_pipe) {
1848                 view->pipe = opt_pipe;
1849                 opt_pipe = NULL;
1850         } else {
1851                 view->pipe = popen(view->cmd, "r");
1852         }
1854         if (!view->pipe)
1855                 return FALSE;
1857         set_nonblocking_input(TRUE);
1859         view->offset = 0;
1860         view->lines  = 0;
1861         view->lineno = 0;
1862         string_copy_rev(view->vid, view->id);
1864         if (view->line) {
1865                 int i;
1867                 for (i = 0; i < view->lines; i++)
1868                         if (view->line[i].data)
1869                                 free(view->line[i].data);
1871                 free(view->line);
1872                 view->line = NULL;
1873         }
1875         view->start_time = time(NULL);
1877         return TRUE;
1880 static struct line *
1881 realloc_lines(struct view *view, size_t line_size)
1883         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1885         if (!tmp)
1886                 return NULL;
1888         view->line = tmp;
1889         view->line_size = line_size;
1890         return view->line;
1893 static bool
1894 update_view(struct view *view)
1896         char in_buffer[BUFSIZ];
1897         char out_buffer[BUFSIZ * 2];
1898         char *line;
1899         /* The number of lines to read. If too low it will cause too much
1900          * redrawing (and possible flickering), if too high responsiveness
1901          * will suffer. */
1902         unsigned long lines = view->height;
1903         int redraw_from = -1;
1905         if (!view->pipe)
1906                 return TRUE;
1908         /* Only redraw if lines are visible. */
1909         if (view->offset + view->height >= view->lines)
1910                 redraw_from = view->lines - view->offset;
1912         /* FIXME: This is probably not perfect for backgrounded views. */
1913         if (!realloc_lines(view, view->lines + lines))
1914                 goto alloc_error;
1916         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1917                 size_t linelen = strlen(line);
1919                 if (linelen)
1920                         line[linelen - 1] = 0;
1922                 if (opt_iconv != ICONV_NONE) {
1923                         char *inbuf = line;
1924                         size_t inlen = linelen;
1926                         char *outbuf = out_buffer;
1927                         size_t outlen = sizeof(out_buffer);
1929                         size_t ret;
1931                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1932                         if (ret != (size_t) -1) {
1933                                 line = out_buffer;
1934                                 linelen = strlen(out_buffer);
1935                         }
1936                 }
1938                 if (!view->ops->read(view, line))
1939                         goto alloc_error;
1941                 if (lines-- == 1)
1942                         break;
1943         }
1945         {
1946                 int digits;
1948                 lines = view->lines;
1949                 for (digits = 0; lines; digits++)
1950                         lines /= 10;
1952                 /* Keep the displayed view in sync with line number scaling. */
1953                 if (digits != view->digits) {
1954                         view->digits = digits;
1955                         redraw_from = 0;
1956                 }
1957         }
1959         if (!view_is_displayed(view))
1960                 goto check_pipe;
1962         if (view == VIEW(REQ_VIEW_TREE)) {
1963                 /* Clear the view and redraw everything since the tree sorting
1964                  * might have rearranged things. */
1965                 redraw_view(view);
1967         } else if (redraw_from >= 0) {
1968                 /* If this is an incremental update, redraw the previous line
1969                  * since for commits some members could have changed when
1970                  * loading the main view. */
1971                 if (redraw_from > 0)
1972                         redraw_from--;
1974                 /* Since revision graph visualization requires knowledge
1975                  * about the parent commit, it causes a further one-off
1976                  * needed to be redrawn for incremental updates. */
1977                 if (redraw_from > 0 && opt_rev_graph)
1978                         redraw_from--;
1980                 /* Incrementally draw avoids flickering. */
1981                 redraw_view_from(view, redraw_from);
1982         }
1984         /* Update the title _after_ the redraw so that if the redraw picks up a
1985          * commit reference in view->ref it'll be available here. */
1986         update_view_title(view);
1988 check_pipe:
1989         if (ferror(view->pipe)) {
1990                 report("Failed to read: %s", strerror(errno));
1991                 goto end;
1993         } else if (feof(view->pipe)) {
1994                 report("");
1995                 goto end;
1996         }
1998         return TRUE;
2000 alloc_error:
2001         report("Allocation failure");
2003 end:
2004         view->ops->read(view, NULL);
2005         end_update(view);
2006         return FALSE;
2009 static struct line *
2010 add_line_data(struct view *view, void *data, enum line_type type)
2012         struct line *line = &view->line[view->lines++];
2014         memset(line, 0, sizeof(*line));
2015         line->type = type;
2016         line->data = data;
2018         return line;
2021 static struct line *
2022 add_line_text(struct view *view, char *data, enum line_type type)
2024         if (data)
2025                 data = strdup(data);
2027         return data ? add_line_data(view, data, type) : NULL;
2031 /*
2032  * View opening
2033  */
2035 enum open_flags {
2036         OPEN_DEFAULT = 0,       /* Use default view switching. */
2037         OPEN_SPLIT = 1,         /* Split current view. */
2038         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2039         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2040 };
2042 static void
2043 open_view(struct view *prev, enum request request, enum open_flags flags)
2045         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2046         bool split = !!(flags & OPEN_SPLIT);
2047         bool reload = !!(flags & OPEN_RELOAD);
2048         struct view *view = VIEW(request);
2049         int nviews = displayed_views();
2050         struct view *base_view = display[0];
2052         if (view == prev && nviews == 1 && !reload) {
2053                 report("Already in %s view", view->name);
2054                 return;
2055         }
2057         if (view->ops->open) {
2058                 if (!view->ops->open(view)) {
2059                         report("Failed to load %s view", view->name);
2060                         return;
2061                 }
2063         } else if ((reload || strcmp(view->vid, view->id)) &&
2064                    !begin_update(view)) {
2065                 report("Failed to load %s view", view->name);
2066                 return;
2067         }
2069         if (split) {
2070                 display[1] = view;
2071                 if (!backgrounded)
2072                         current_view = 1;
2073         } else {
2074                 /* Maximize the current view. */
2075                 memset(display, 0, sizeof(display));
2076                 current_view = 0;
2077                 display[current_view] = view;
2078         }
2080         /* Resize the view when switching between split- and full-screen,
2081          * or when switching between two different full-screen views. */
2082         if (nviews != displayed_views() ||
2083             (nviews == 1 && base_view != display[0]))
2084                 resize_display();
2086         if (split && prev->lineno - prev->offset >= prev->height) {
2087                 /* Take the title line into account. */
2088                 int lines = prev->lineno - prev->offset - prev->height + 1;
2090                 /* Scroll the view that was split if the current line is
2091                  * outside the new limited view. */
2092                 do_scroll_view(prev, lines);
2093         }
2095         if (prev && view != prev) {
2096                 if (split && !backgrounded) {
2097                         /* "Blur" the previous view. */
2098                         update_view_title(prev);
2099                 }
2101                 view->parent = prev;
2102         }
2104         if (view->pipe && view->lines == 0) {
2105                 /* Clear the old view and let the incremental updating refill
2106                  * the screen. */
2107                 wclear(view->win);
2108                 report("");
2109         } else {
2110                 redraw_view(view);
2111                 report("");
2112         }
2114         /* If the view is backgrounded the above calls to report()
2115          * won't redraw the view title. */
2116         if (backgrounded)
2117                 update_view_title(view);
2121 /*
2122  * User request switch noodle
2123  */
2125 static void status_update(struct view *view);
2127 static int
2128 view_driver(struct view *view, enum request request)
2130         int i;
2132         switch (request) {
2133         case REQ_MOVE_UP:
2134         case REQ_MOVE_DOWN:
2135         case REQ_MOVE_PAGE_UP:
2136         case REQ_MOVE_PAGE_DOWN:
2137         case REQ_MOVE_FIRST_LINE:
2138         case REQ_MOVE_LAST_LINE:
2139                 move_view(view, request);
2140                 break;
2142         case REQ_SCROLL_LINE_DOWN:
2143         case REQ_SCROLL_LINE_UP:
2144         case REQ_SCROLL_PAGE_DOWN:
2145         case REQ_SCROLL_PAGE_UP:
2146                 scroll_view(view, request);
2147                 break;
2149         case REQ_VIEW_BLOB:
2150                 if (!ref_blob[0]) {
2151                         report("No file chosen, press %s to open tree view",
2152                                get_key(REQ_VIEW_TREE));
2153                         break;
2154                 }
2155                 open_view(view, request, OPEN_DEFAULT);
2156                 break;
2158         case REQ_VIEW_PAGER:
2159                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2160                         report("No pager content, press %s to run command from prompt",
2161                                get_key(REQ_PROMPT));
2162                         break;
2163                 }
2164                 open_view(view, request, OPEN_DEFAULT);
2165                 break;
2167         case REQ_VIEW_MAIN:
2168         case REQ_VIEW_DIFF:
2169         case REQ_VIEW_LOG:
2170         case REQ_VIEW_TREE:
2171         case REQ_VIEW_HELP:
2172         case REQ_VIEW_STATUS:
2173                 open_view(view, request, OPEN_DEFAULT);
2174                 break;
2176         case REQ_NEXT:
2177         case REQ_PREVIOUS:
2178                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2180                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2181                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2182                    (view == VIEW(REQ_VIEW_BLOB) &&
2183                      view->parent == VIEW(REQ_VIEW_TREE))) {
2184                         int line;
2186                         view = view->parent;
2187                         line = view->lineno;
2188                         move_view(view, request);
2189                         if (view_is_displayed(view))
2190                                 update_view_title(view);
2191                         if (line == view->lineno)
2192                                 break;
2193                 } else {
2194                         move_view(view, request);
2195                         break;
2196                 }
2197                 /* Fall-through */
2199         case REQ_ENTER:
2200                 if (!view->lines) {
2201                         report("Nothing to enter");
2202                         break;
2203                 }
2204                 return view->ops->enter(view, &view->line[view->lineno]);
2206         case REQ_VIEW_NEXT:
2207         {
2208                 int nviews = displayed_views();
2209                 int next_view = (current_view + 1) % nviews;
2211                 if (next_view == current_view) {
2212                         report("Only one view is displayed");
2213                         break;
2214                 }
2216                 current_view = next_view;
2217                 /* Blur out the title of the previous view. */
2218                 update_view_title(view);
2219                 report("");
2220                 break;
2221         }
2222         case REQ_TOGGLE_LINENO:
2223                 opt_line_number = !opt_line_number;
2224                 redraw_display();
2225                 break;
2227         case REQ_TOGGLE_REV_GRAPH:
2228                 opt_rev_graph = !opt_rev_graph;
2229                 redraw_display();
2230                 break;
2232         case REQ_PROMPT:
2233                 /* Always reload^Wrerun commands from the prompt. */
2234                 open_view(view, opt_request, OPEN_RELOAD);
2235                 break;
2237         case REQ_SEARCH:
2238         case REQ_SEARCH_BACK:
2239                 search_view(view, request);
2240                 break;
2242         case REQ_FIND_NEXT:
2243         case REQ_FIND_PREV:
2244                 find_next(view, request);
2245                 break;
2247         case REQ_STOP_LOADING:
2248                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2249                         view = &views[i];
2250                         if (view->pipe)
2251                                 report("Stopped loading the %s view", view->name),
2252                         end_update(view);
2253                 }
2254                 break;
2256         case REQ_SHOW_VERSION:
2257                 report("tig-%s (built %s)", VERSION, __DATE__);
2258                 return TRUE;
2260         case REQ_SCREEN_RESIZE:
2261                 resize_display();
2262                 /* Fall-through */
2263         case REQ_SCREEN_REDRAW:
2264                 redraw_display();
2265                 break;
2267         case REQ_STATUS_UPDATE:
2268                 status_update(view);
2269                 break;
2271         case REQ_NONE:
2272                 doupdate();
2273                 return TRUE;
2275         case REQ_VIEW_CLOSE:
2276                 /* XXX: Mark closed views by letting view->parent point to the
2277                  * view itself. Parents to closed view should never be
2278                  * followed. */
2279                 if (view->parent &&
2280                     view->parent->parent != view->parent) {
2281                         memset(display, 0, sizeof(display));
2282                         current_view = 0;
2283                         display[current_view] = view->parent;
2284                         view->parent = view;
2285                         resize_display();
2286                         redraw_display();
2287                         break;
2288                 }
2289                 /* Fall-through */
2290         case REQ_QUIT:
2291                 return FALSE;
2293         default:
2294                 /* An unknown key will show most commonly used commands. */
2295                 report("Unknown key, press 'h' for help");
2296                 return TRUE;
2297         }
2299         return TRUE;
2303 /*
2304  * Pager backend
2305  */
2307 static bool
2308 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2310         char *text = line->data;
2311         enum line_type type = line->type;
2312         int textlen = strlen(text);
2313         int attr;
2315         wmove(view->win, lineno, 0);
2317         if (selected) {
2318                 type = LINE_CURSOR;
2319                 wchgat(view->win, -1, 0, type, NULL);
2320         }
2322         attr = get_line_attr(type);
2323         wattrset(view->win, attr);
2325         if (opt_line_number || opt_tab_size < TABSIZE) {
2326                 static char spaces[] = "                    ";
2327                 int col_offset = 0, col = 0;
2329                 if (opt_line_number) {
2330                         unsigned long real_lineno = view->offset + lineno + 1;
2332                         if (real_lineno == 1 ||
2333                             (real_lineno % opt_num_interval) == 0) {
2334                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2336                         } else {
2337                                 waddnstr(view->win, spaces,
2338                                          MIN(view->digits, STRING_SIZE(spaces)));
2339                         }
2340                         waddstr(view->win, ": ");
2341                         col_offset = view->digits + 2;
2342                 }
2344                 while (text && col_offset + col < view->width) {
2345                         int cols_max = view->width - col_offset - col;
2346                         char *pos = text;
2347                         int cols;
2349                         if (*text == '\t') {
2350                                 text++;
2351                                 assert(sizeof(spaces) > TABSIZE);
2352                                 pos = spaces;
2353                                 cols = opt_tab_size - (col % opt_tab_size);
2355                         } else {
2356                                 text = strchr(text, '\t');
2357                                 cols = line ? text - pos : strlen(pos);
2358                         }
2360                         waddnstr(view->win, pos, MIN(cols, cols_max));
2361                         col += cols;
2362                 }
2364         } else {
2365                 int col = 0, pos = 0;
2367                 for (; pos < textlen && col < view->width; pos++, col++)
2368                         if (text[pos] == '\t')
2369                                 col += TABSIZE - (col % TABSIZE) - 1;
2371                 waddnstr(view->win, text, pos);
2372         }
2374         return TRUE;
2377 static bool
2378 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2380         char refbuf[SIZEOF_STR];
2381         char *ref = NULL;
2382         FILE *pipe;
2384         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2385                 return TRUE;
2387         pipe = popen(refbuf, "r");
2388         if (!pipe)
2389                 return TRUE;
2391         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2392                 ref = chomp_string(ref);
2393         pclose(pipe);
2395         if (!ref || !*ref)
2396                 return TRUE;
2398         /* This is the only fatal call, since it can "corrupt" the buffer. */
2399         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2400                 return FALSE;
2402         return TRUE;
2405 static void
2406 add_pager_refs(struct view *view, struct line *line)
2408         char buf[SIZEOF_STR];
2409         char *commit_id = line->data + STRING_SIZE("commit ");
2410         struct ref **refs;
2411         size_t bufpos = 0, refpos = 0;
2412         const char *sep = "Refs: ";
2413         bool is_tag = FALSE;
2415         assert(line->type == LINE_COMMIT);
2417         refs = get_refs(commit_id);
2418         if (!refs) {
2419                 if (view == VIEW(REQ_VIEW_DIFF))
2420                         goto try_add_describe_ref;
2421                 return;
2422         }
2424         do {
2425                 struct ref *ref = refs[refpos];
2426                 char *fmt = ref->tag    ? "%s[%s]" :
2427                             ref->remote ? "%s<%s>" : "%s%s";
2429                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2430                         return;
2431                 sep = ", ";
2432                 if (ref->tag)
2433                         is_tag = TRUE;
2434         } while (refs[refpos++]->next);
2436         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2437 try_add_describe_ref:
2438                 /* Add <tag>-g<commit_id> "fake" reference. */
2439                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2440                         return;
2441         }
2443         if (bufpos == 0)
2444                 return;
2446         if (!realloc_lines(view, view->line_size + 1))
2447                 return;
2449         add_line_text(view, buf, LINE_PP_REFS);
2452 static bool
2453 pager_read(struct view *view, char *data)
2455         struct line *line;
2457         if (!data)
2458                 return TRUE;
2460         line = add_line_text(view, data, get_line_type(data));
2461         if (!line)
2462                 return FALSE;
2464         if (line->type == LINE_COMMIT &&
2465             (view == VIEW(REQ_VIEW_DIFF) ||
2466              view == VIEW(REQ_VIEW_LOG)))
2467                 add_pager_refs(view, line);
2469         return TRUE;
2472 static bool
2473 pager_enter(struct view *view, struct line *line)
2475         int split = 0;
2477         if (line->type == LINE_COMMIT &&
2478            (view == VIEW(REQ_VIEW_LOG) ||
2479             view == VIEW(REQ_VIEW_PAGER))) {
2480                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2481                 split = 1;
2482         }
2484         /* Always scroll the view even if it was split. That way
2485          * you can use Enter to scroll through the log view and
2486          * split open each commit diff. */
2487         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2489         /* FIXME: A minor workaround. Scrolling the view will call report("")
2490          * but if we are scrolling a non-current view this won't properly
2491          * update the view title. */
2492         if (split)
2493                 update_view_title(view);
2495         return TRUE;
2498 static bool
2499 pager_grep(struct view *view, struct line *line)
2501         regmatch_t pmatch;
2502         char *text = line->data;
2504         if (!*text)
2505                 return FALSE;
2507         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2508                 return FALSE;
2510         return TRUE;
2513 static void
2514 pager_select(struct view *view, struct line *line)
2516         if (line->type == LINE_COMMIT) {
2517                 char *text = line->data + STRING_SIZE("commit ");
2519                 if (view != VIEW(REQ_VIEW_PAGER))
2520                         string_copy_rev(view->ref, text);
2521                 string_copy_rev(ref_commit, text);
2522         }
2525 static struct view_ops pager_ops = {
2526         "line",
2527         NULL,
2528         pager_read,
2529         pager_draw,
2530         pager_enter,
2531         pager_grep,
2532         pager_select,
2533 };
2536 /*
2537  * Help backend
2538  */
2540 static bool
2541 help_open(struct view *view)
2543         char buf[BUFSIZ];
2544         int lines = ARRAY_SIZE(req_info) + 2;
2545         int i;
2547         if (view->lines > 0)
2548                 return TRUE;
2550         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2551                 if (!req_info[i].request)
2552                         lines++;
2554         view->line = calloc(lines, sizeof(*view->line));
2555         if (!view->line)
2556                 return FALSE;
2558         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2560         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2561                 char *key;
2563                 if (!req_info[i].request) {
2564                         add_line_text(view, "", LINE_DEFAULT);
2565                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2566                         continue;
2567                 }
2569                 key = get_key(req_info[i].request);
2570                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2571                         continue;
2573                 add_line_text(view, buf, LINE_DEFAULT);
2574         }
2576         return TRUE;
2579 static struct view_ops help_ops = {
2580         "line",
2581         help_open,
2582         NULL,
2583         pager_draw,
2584         pager_enter,
2585         pager_grep,
2586         pager_select,
2587 };
2590 /*
2591  * Tree backend
2592  */
2594 /* Parse output from git-ls-tree(1):
2595  *
2596  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2597  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2598  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2599  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2600  */
2602 #define SIZEOF_TREE_ATTR \
2603         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2605 #define TREE_UP_FORMAT "040000 tree %s\t.."
2607 static int
2608 tree_compare_entry(enum line_type type1, char *name1,
2609                    enum line_type type2, char *name2)
2611         if (type1 != type2) {
2612                 if (type1 == LINE_TREE_DIR)
2613                         return -1;
2614                 return 1;
2615         }
2617         return strcmp(name1, name2);
2620 static bool
2621 tree_read(struct view *view, char *text)
2623         size_t textlen = text ? strlen(text) : 0;
2624         char buf[SIZEOF_STR];
2625         unsigned long pos;
2626         enum line_type type;
2627         bool first_read = view->lines == 0;
2629         if (textlen <= SIZEOF_TREE_ATTR)
2630                 return FALSE;
2632         type = text[STRING_SIZE("100644 ")] == 't'
2633              ? LINE_TREE_DIR : LINE_TREE_FILE;
2635         if (first_read) {
2636                 /* Add path info line */
2637                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2638                     !realloc_lines(view, view->line_size + 1) ||
2639                     !add_line_text(view, buf, LINE_DEFAULT))
2640                         return FALSE;
2642                 /* Insert "link" to parent directory. */
2643                 if (*opt_path) {
2644                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2645                             !realloc_lines(view, view->line_size + 1) ||
2646                             !add_line_text(view, buf, LINE_TREE_DIR))
2647                                 return FALSE;
2648                 }
2649         }
2651         /* Strip the path part ... */
2652         if (*opt_path) {
2653                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2654                 size_t striplen = strlen(opt_path);
2655                 char *path = text + SIZEOF_TREE_ATTR;
2657                 if (pathlen > striplen)
2658                         memmove(path, path + striplen,
2659                                 pathlen - striplen + 1);
2660         }
2662         /* Skip "Directory ..." and ".." line. */
2663         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2664                 struct line *line = &view->line[pos];
2665                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2666                 char *path2 = text + SIZEOF_TREE_ATTR;
2667                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2669                 if (cmp <= 0)
2670                         continue;
2672                 text = strdup(text);
2673                 if (!text)
2674                         return FALSE;
2676                 if (view->lines > pos)
2677                         memmove(&view->line[pos + 1], &view->line[pos],
2678                                 (view->lines - pos) * sizeof(*line));
2680                 line = &view->line[pos];
2681                 line->data = text;
2682                 line->type = type;
2683                 view->lines++;
2684                 return TRUE;
2685         }
2687         if (!add_line_text(view, text, type))
2688                 return FALSE;
2690         /* Move the current line to the first tree entry. */
2691         if (first_read)
2692                 view->lineno++;
2694         return TRUE;
2697 static bool
2698 tree_enter(struct view *view, struct line *line)
2700         enum open_flags flags;
2701         enum request request;
2703         switch (line->type) {
2704         case LINE_TREE_DIR:
2705                 /* Depending on whether it is a subdir or parent (updir?) link
2706                  * mangle the path buffer. */
2707                 if (line == &view->line[1] && *opt_path) {
2708                         size_t path_len = strlen(opt_path);
2709                         char *dirsep = opt_path + path_len - 1;
2711                         while (dirsep > opt_path && dirsep[-1] != '/')
2712                                 dirsep--;
2714                         dirsep[0] = 0;
2716                 } else {
2717                         size_t pathlen = strlen(opt_path);
2718                         size_t origlen = pathlen;
2719                         char *data = line->data;
2720                         char *basename = data + SIZEOF_TREE_ATTR;
2722                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2723                                 opt_path[origlen] = 0;
2724                                 return TRUE;
2725                         }
2726                 }
2728                 /* Trees and subtrees share the same ID, so they are not not
2729                  * unique like blobs. */
2730                 flags = OPEN_RELOAD;
2731                 request = REQ_VIEW_TREE;
2732                 break;
2734         case LINE_TREE_FILE:
2735                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2736                 request = REQ_VIEW_BLOB;
2737                 break;
2739         default:
2740                 return TRUE;
2741         }
2743         open_view(view, request, flags);
2745         return TRUE;
2748 static void
2749 tree_select(struct view *view, struct line *line)
2751         char *text = line->data + STRING_SIZE("100644 blob ");
2753         if (line->type == LINE_TREE_FILE) {
2754                 string_copy_rev(ref_blob, text);
2756         } else if (line->type != LINE_TREE_DIR) {
2757                 return;
2758         }
2760         string_copy_rev(view->ref, text);
2763 static struct view_ops tree_ops = {
2764         "file",
2765         NULL,
2766         tree_read,
2767         pager_draw,
2768         tree_enter,
2769         pager_grep,
2770         tree_select,
2771 };
2773 static bool
2774 blob_read(struct view *view, char *line)
2776         return add_line_text(view, line, LINE_DEFAULT);
2779 static struct view_ops blob_ops = {
2780         "line",
2781         NULL,
2782         blob_read,
2783         pager_draw,
2784         pager_enter,
2785         pager_grep,
2786         pager_select,
2787 };
2790 /*
2791  * Status backend
2792  */
2794 struct status {
2795         char status;
2796         struct {
2797                 mode_t mode;
2798                 char rev[SIZEOF_REV];
2799         } old;
2800         struct {
2801                 mode_t mode;
2802                 char rev[SIZEOF_REV];
2803         } new;
2804         char name[SIZEOF_STR];
2805 };
2807 /* Get fields from the diff line:
2808  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2809  */
2810 static inline bool
2811 status_get_diff(struct status *file, char *buf, size_t bufsize)
2813         char *old_mode = buf +  1;
2814         char *new_mode = buf +  8;
2815         char *old_rev  = buf + 15;
2816         char *new_rev  = buf + 56;
2817         char *status   = buf + 97;
2819         if (bufsize != 99 ||
2820             old_mode[-1] != ':' ||
2821             new_mode[-1] != ' ' ||
2822             old_rev[-1]  != ' ' ||
2823             new_rev[-1]  != ' ' ||
2824             status[-1]   != ' ')
2825                 return FALSE;
2827         file->status = *status;
2829         string_copy_rev(file->old.rev, old_rev);
2830         string_copy_rev(file->new.rev, new_rev);
2832         file->old.mode = strtoul(old_mode, NULL, 8);
2833         file->new.mode = strtoul(new_mode, NULL, 8);
2835         file->name[0] = 0;
2837         return TRUE;
2840 static bool
2841 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2843         struct status *file = NULL;
2844         char buf[SIZEOF_STR * 4];
2845         size_t bufsize = 0;
2846         FILE *pipe;
2848         pipe = popen(cmd, "r");
2849         if (!pipe)
2850                 return FALSE;
2852         add_line_data(view, NULL, type);
2854         while (!feof(pipe) && !ferror(pipe)) {
2855                 char *sep;
2856                 size_t readsize;
2858                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2859                 if (!readsize)
2860                         break;
2861                 bufsize += readsize;
2863                 /* Process while we have NUL chars. */
2864                 while ((sep = memchr(buf, 0, bufsize))) {
2865                         size_t sepsize = sep - buf + 1;
2867                         if (!file) {
2868                                 if (!realloc_lines(view, view->line_size + 1))
2869                                         goto error_out;
2871                                 file = calloc(1, sizeof(*file));
2872                                 if (!file)
2873                                         goto error_out;
2875                                 add_line_data(view, file, type);
2876                         }
2878                         /* Parse diff info part. */
2879                         if (!diff) {
2880                                 file->status = '?';
2882                         } else if (!file->status) {
2883                                 if (!status_get_diff(file, buf, sepsize))
2884                                         goto error_out;
2886                                 bufsize -= sepsize;
2887                                 memmove(buf, sep + 1, bufsize);
2889                                 sep = memchr(buf, 0, bufsize);
2890                                 if (!sep)
2891                                         break;
2892                                 sepsize = sep - buf + 1;
2893                         }
2895                         /* git-ls-files just delivers a NUL separated
2896                          * list of file names similar to the second half
2897                          * of the git-diff-* output. */
2898                         string_ncopy(file->name, buf, sepsize);
2899                         bufsize -= sepsize;
2900                         memmove(buf, sep + 1, bufsize);
2901                         file = NULL;
2902                 }
2903         }
2905         if (ferror(pipe)) {
2906 error_out:
2907                 pclose(pipe);
2908                 return FALSE;
2909         }
2911         if (!view->line[view->lines - 1].data)
2912                 add_line_data(view, NULL, LINE_STAT_NONE);
2914         pclose(pipe);
2915         return TRUE;
2918 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
2919 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
2920 #define STATUS_LIST_OTHER_CMD \
2921         "git ls-files -z --others --exclude-per-directory=.gitignore"
2923 #define STATUS_DIFF_SHOW_CMD \
2924         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
2926 /* First parse staged info using git-diff-index(1), then parse unstaged
2927  * info using git-diff-files(1), and finally untracked files using
2928  * git-ls-files(1). */
2929 static bool
2930 status_open(struct view *view)
2932         struct stat statbuf;
2933         char exclude[SIZEOF_STR];
2934         char cmd[SIZEOF_STR];
2935         size_t i;
2937         for (i = 0; i < view->lines; i++)
2938                 free(view->line[i].data);
2939         free(view->line);
2940         view->lines = view->line_size = 0;
2941         view->line = NULL;
2943         if (!realloc_lines(view, view->line_size + 6))
2944                 return FALSE;
2946         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
2947                 return FALSE;
2949         string_copy(cmd, STATUS_LIST_OTHER_CMD);
2951         if (stat(exclude, &statbuf) >= 0) {
2952                 size_t cmdsize = strlen(cmd);
2954                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
2955                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
2956                         return FALSE;
2957         }
2959         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
2960             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
2961             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
2962                 return FALSE;
2964         return TRUE;
2967 static bool
2968 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2970         struct status *status = line->data;
2972         wmove(view->win, lineno, 0);
2974         if (selected) {
2975                 wattrset(view->win, get_line_attr(LINE_CURSOR));
2976                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
2978         } else if (!status && line->type != LINE_STAT_NONE) {
2979                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
2980                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
2982         } else {
2983                 wattrset(view->win, get_line_attr(line->type));
2984         }
2986         if (!status) {
2987                 char *text;
2989                 switch (line->type) {
2990                 case LINE_STAT_STAGED:
2991                         text = "Changes to be committed:";
2992                         break;
2994                 case LINE_STAT_UNSTAGED:
2995                         text = "Changed but not updated:";
2996                         break;
2998                 case LINE_STAT_UNTRACKED:
2999                         text = "Untracked files:";
3000                         break;
3002                 case LINE_STAT_NONE:
3003                         text = "    (no files)";
3004                         break;
3006                 default:
3007                         return FALSE;
3008                 }
3010                 waddstr(view->win, text);
3011                 return TRUE;
3012         }
3014         waddch(view->win, status->status);
3015         if (!selected)
3016                 wattrset(view->win, A_NORMAL);
3017         wmove(view->win, lineno, 4);
3018         waddstr(view->win, status->name);
3020         return TRUE;
3023 static bool
3024 status_enter(struct view *view, struct line *line)
3026         struct status *status = line->data;
3027         char path[SIZEOF_STR];
3028         char *info;
3029         size_t cmdsize = 0;
3031         if (!status || line->type == LINE_STAT_NONE) {
3032                 report("No file has been chosen");
3033                 return TRUE;
3034         }
3036         if (sq_quote(path, 0, status->name) >= sizeof(path))
3037                 return FALSE;
3039         if (opt_cdup[0] &&
3040             line->type != LINE_STAT_UNTRACKED &&
3041             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3042                 return FALSE;
3044         switch (line->type) {
3045         case LINE_STAT_STAGED:
3046                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3047                                         "--cached", path))
3048                         return FALSE;
3049                 info = "Staged changes to %s";
3050                 break;
3052         case LINE_STAT_UNSTAGED:
3053                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3054                                         "", path))
3055                         return FALSE;
3056                 info = "Unstaged changes to %s";
3057                 break;
3059         case LINE_STAT_UNTRACKED:
3060                 if (opt_pipe)
3061                         return FALSE;
3062                 opt_pipe = fopen(status->name, "r");
3063                 info = "Untracked file %s";
3064                 break;
3066         default:
3067                 die("w00t");
3068         }
3070         open_view(view, REQ_VIEW_DIFF, OPEN_RELOAD | OPEN_SPLIT);
3071         if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
3072                 string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
3073         }
3075         return TRUE;
3078 static bool
3079 status_update_file(struct view *view, struct status *status, enum line_type type)
3081         char cmd[SIZEOF_STR];
3082         char buf[SIZEOF_STR];
3083         size_t cmdsize = 0;
3084         size_t bufsize = 0;
3085         size_t written = 0;
3086         FILE *pipe;
3088         if (opt_cdup[0] &&
3089             type != LINE_STAT_UNTRACKED &&
3090             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3091                 return FALSE;
3093         switch (type) {
3094         case LINE_STAT_STAGED:
3095                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3096                                         status->old.mode,
3097                                         status->old.rev,
3098                                         status->name, 0))
3099                         return FALSE;
3101                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3102                 break;
3104         case LINE_STAT_UNSTAGED:
3105         case LINE_STAT_UNTRACKED:
3106                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3107                         return FALSE;
3109                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3110                 break;
3112         default:
3113                 die("w00t");
3114         }
3116         pipe = popen(cmd, "w");
3117         if (!pipe)
3118                 return FALSE;
3120         while (!ferror(pipe) && written < bufsize) {
3121                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3122         }
3124         pclose(pipe);
3126         if (written != bufsize)
3127                 return FALSE;
3129         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3130         return TRUE;
3133 static void
3134 status_update(struct view *view)
3136         if (view == VIEW(REQ_VIEW_STATUS)) {
3137                 struct line *line = view->lines
3138                                   ? &view->line[view->lineno] : NULL;
3140                 if (!line || !line->data) {
3141                         report("No file has been chosen");
3142                         return;
3143                 }
3145                 if (!status_update_file(view, line->data, line->type))
3146                         report("Failed to update file status");
3147         } else {
3148                 report("This action is only valid for the status view");
3149         }
3152 static void
3153 status_select(struct view *view, struct line *line)
3155         char *text;
3157         switch (line->type) {
3158         case LINE_STAT_STAGED:
3159                 text = "Press Enter to unstage file for commit";
3160                 break;
3162         case LINE_STAT_UNSTAGED:
3163                 text = "Press Enter to stage file for commit  ";
3164                 break;
3166         case LINE_STAT_UNTRACKED:
3167                 text = "Press Enter to stage file for addition";
3168                 break;
3170         case LINE_STAT_NONE:
3171                 return;
3173         default:
3174                 die("w00t");
3175         }
3177         string_ncopy(view->ref, text, strlen(text));
3180 static bool
3181 status_grep(struct view *view, struct line *line)
3183         struct status *status = line->data;
3184         enum { S_STATUS, S_NAME, S_END } state;
3185         char buf[2] = "?";
3186         regmatch_t pmatch;
3188         if (!status)
3189                 return FALSE;
3191         for (state = S_STATUS; state < S_END; state++) {
3192                 char *text;
3194                 switch (state) {
3195                 case S_NAME:    text = status->name;    break;
3196                 case S_STATUS:
3197                         buf[0] = status->status;
3198                         text = buf;
3199                         break;
3201                 default:
3202                         return FALSE;
3203                 }
3205                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3206                         return TRUE;
3207         }
3209         return FALSE;
3212 static struct view_ops status_ops = {
3213         "file",
3214         status_open,
3215         NULL,
3216         status_draw,
3217         status_enter,
3218         status_grep,
3219         status_select,
3220 };
3223 /*
3224  * Revision graph
3225  */
3227 struct commit {
3228         char id[SIZEOF_REV];            /* SHA1 ID. */
3229         char title[128];                /* First line of the commit message. */
3230         char author[75];                /* Author of the commit. */
3231         struct tm time;                 /* Date from the author ident. */
3232         struct ref **refs;              /* Repository references. */
3233         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3234         size_t graph_size;              /* The width of the graph array. */
3235 };
3237 /* Size of rev graph with no  "padding" columns */
3238 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3240 struct rev_graph {
3241         struct rev_graph *prev, *next, *parents;
3242         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3243         size_t size;
3244         struct commit *commit;
3245         size_t pos;
3246 };
3248 /* Parents of the commit being visualized. */
3249 static struct rev_graph graph_parents[4];
3251 /* The current stack of revisions on the graph. */
3252 static struct rev_graph graph_stacks[4] = {
3253         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3254         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3255         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3256         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3257 };
3259 static inline bool
3260 graph_parent_is_merge(struct rev_graph *graph)
3262         return graph->parents->size > 1;
3265 static inline void
3266 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3268         struct commit *commit = graph->commit;
3270         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3271                 commit->graph[commit->graph_size++] = symbol;
3274 static void
3275 done_rev_graph(struct rev_graph *graph)
3277         if (graph_parent_is_merge(graph) &&
3278             graph->pos < graph->size - 1 &&
3279             graph->next->size == graph->size + graph->parents->size - 1) {
3280                 size_t i = graph->pos + graph->parents->size - 1;
3282                 graph->commit->graph_size = i * 2;
3283                 while (i < graph->next->size - 1) {
3284                         append_to_rev_graph(graph, ' ');
3285                         append_to_rev_graph(graph, '\\');
3286                         i++;
3287                 }
3288         }
3290         graph->size = graph->pos = 0;
3291         graph->commit = NULL;
3292         memset(graph->parents, 0, sizeof(*graph->parents));
3295 static void
3296 push_rev_graph(struct rev_graph *graph, char *parent)
3298         int i;
3300         /* "Collapse" duplicate parents lines.
3301          *
3302          * FIXME: This needs to also update update the drawn graph but
3303          * for now it just serves as a method for pruning graph lines. */
3304         for (i = 0; i < graph->size; i++)
3305                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3306                         return;
3308         if (graph->size < SIZEOF_REVITEMS) {
3309                 string_copy_rev(graph->rev[graph->size++], parent);
3310         }
3313 static chtype
3314 get_rev_graph_symbol(struct rev_graph *graph)
3316         chtype symbol;
3318         if (graph->parents->size == 0)
3319                 symbol = REVGRAPH_INIT;
3320         else if (graph_parent_is_merge(graph))
3321                 symbol = REVGRAPH_MERGE;
3322         else if (graph->pos >= graph->size)
3323                 symbol = REVGRAPH_BRANCH;
3324         else
3325                 symbol = REVGRAPH_COMMIT;
3327         return symbol;
3330 static void
3331 draw_rev_graph(struct rev_graph *graph)
3333         struct rev_filler {
3334                 chtype separator, line;
3335         };
3336         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3337         static struct rev_filler fillers[] = {
3338                 { ' ',  REVGRAPH_LINE },
3339                 { '`',  '.' },
3340                 { '\'', ' ' },
3341                 { '/',  ' ' },
3342         };
3343         chtype symbol = get_rev_graph_symbol(graph);
3344         struct rev_filler *filler;
3345         size_t i;
3347         filler = &fillers[DEFAULT];
3349         for (i = 0; i < graph->pos; i++) {
3350                 append_to_rev_graph(graph, filler->line);
3351                 if (graph_parent_is_merge(graph->prev) &&
3352                     graph->prev->pos == i)
3353                         filler = &fillers[RSHARP];
3355                 append_to_rev_graph(graph, filler->separator);
3356         }
3358         /* Place the symbol for this revision. */
3359         append_to_rev_graph(graph, symbol);
3361         if (graph->prev->size > graph->size)
3362                 filler = &fillers[RDIAG];
3363         else
3364                 filler = &fillers[DEFAULT];
3366         i++;
3368         for (; i < graph->size; i++) {
3369                 append_to_rev_graph(graph, filler->separator);
3370                 append_to_rev_graph(graph, filler->line);
3371                 if (graph_parent_is_merge(graph->prev) &&
3372                     i < graph->prev->pos + graph->parents->size)
3373                         filler = &fillers[RSHARP];
3374                 if (graph->prev->size > graph->size)
3375                         filler = &fillers[LDIAG];
3376         }
3378         if (graph->prev->size > graph->size) {
3379                 append_to_rev_graph(graph, filler->separator);
3380                 if (filler->line != ' ')
3381                         append_to_rev_graph(graph, filler->line);
3382         }
3385 /* Prepare the next rev graph */
3386 static void
3387 prepare_rev_graph(struct rev_graph *graph)
3389         size_t i;
3391         /* First, traverse all lines of revisions up to the active one. */
3392         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3393                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3394                         break;
3396                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3397         }
3399         /* Interleave the new revision parent(s). */
3400         for (i = 0; i < graph->parents->size; i++)
3401                 push_rev_graph(graph->next, graph->parents->rev[i]);
3403         /* Lastly, put any remaining revisions. */
3404         for (i = graph->pos + 1; i < graph->size; i++)
3405                 push_rev_graph(graph->next, graph->rev[i]);
3408 static void
3409 update_rev_graph(struct rev_graph *graph)
3411         /* If this is the finalizing update ... */
3412         if (graph->commit)
3413                 prepare_rev_graph(graph);
3415         /* Graph visualization needs a one rev look-ahead,
3416          * so the first update doesn't visualize anything. */
3417         if (!graph->prev->commit)
3418                 return;
3420         draw_rev_graph(graph->prev);
3421         done_rev_graph(graph->prev->prev);
3425 /*
3426  * Main view backend
3427  */
3429 static bool
3430 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3432         char buf[DATE_COLS + 1];
3433         struct commit *commit = line->data;
3434         enum line_type type;
3435         int col = 0;
3436         size_t timelen;
3437         size_t authorlen;
3438         int trimmed = 1;
3440         if (!*commit->author)
3441                 return FALSE;
3443         wmove(view->win, lineno, col);
3445         if (selected) {
3446                 type = LINE_CURSOR;
3447                 wattrset(view->win, get_line_attr(type));
3448                 wchgat(view->win, -1, 0, type, NULL);
3450         } else {
3451                 type = LINE_MAIN_COMMIT;
3452                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3453         }
3455         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3456         waddnstr(view->win, buf, timelen);
3457         waddstr(view->win, " ");
3459         col += DATE_COLS;
3460         wmove(view->win, lineno, col);
3461         if (type != LINE_CURSOR)
3462                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3464         if (opt_utf8) {
3465                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3466         } else {
3467                 authorlen = strlen(commit->author);
3468                 if (authorlen > AUTHOR_COLS - 2) {
3469                         authorlen = AUTHOR_COLS - 2;
3470                         trimmed = 1;
3471                 }
3472         }
3474         if (trimmed) {
3475                 waddnstr(view->win, commit->author, authorlen);
3476                 if (type != LINE_CURSOR)
3477                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3478                 waddch(view->win, '~');
3479         } else {
3480                 waddstr(view->win, commit->author);
3481         }
3483         col += AUTHOR_COLS;
3484         if (type != LINE_CURSOR)
3485                 wattrset(view->win, A_NORMAL);
3487         if (opt_rev_graph && commit->graph_size) {
3488                 size_t i;
3490                 wmove(view->win, lineno, col);
3491                 /* Using waddch() instead of waddnstr() ensures that
3492                  * they'll be rendered correctly for the cursor line. */
3493                 for (i = 0; i < commit->graph_size; i++)
3494                         waddch(view->win, commit->graph[i]);
3496                 waddch(view->win, ' ');
3497                 col += commit->graph_size + 1;
3498         }
3500         wmove(view->win, lineno, col);
3502         if (commit->refs) {
3503                 size_t i = 0;
3505                 do {
3506                         if (type == LINE_CURSOR)
3507                                 ;
3508                         else if (commit->refs[i]->tag)
3509                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3510                         else if (commit->refs[i]->remote)
3511                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3512                         else
3513                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3514                         waddstr(view->win, "[");
3515                         waddstr(view->win, commit->refs[i]->name);
3516                         waddstr(view->win, "]");
3517                         if (type != LINE_CURSOR)
3518                                 wattrset(view->win, A_NORMAL);
3519                         waddstr(view->win, " ");
3520                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3521                 } while (commit->refs[i++]->next);
3522         }
3524         if (type != LINE_CURSOR)
3525                 wattrset(view->win, get_line_attr(type));
3527         {
3528                 int titlelen = strlen(commit->title);
3530                 if (col + titlelen > view->width)
3531                         titlelen = view->width - col;
3533                 waddnstr(view->win, commit->title, titlelen);
3534         }
3536         return TRUE;
3539 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3540 static bool
3541 main_read(struct view *view, char *line)
3543         static struct rev_graph *graph = graph_stacks;
3544         enum line_type type;
3545         struct commit *commit;
3547         if (!line) {
3548                 update_rev_graph(graph);
3549                 return TRUE;
3550         }
3552         type = get_line_type(line);
3553         if (type == LINE_COMMIT) {
3554                 commit = calloc(1, sizeof(struct commit));
3555                 if (!commit)
3556                         return FALSE;
3558                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3559                 commit->refs = get_refs(commit->id);
3560                 graph->commit = commit;
3561                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3562                 return TRUE;
3563         }
3565         if (!view->lines)
3566                 return TRUE;
3567         commit = view->line[view->lines - 1].data;
3569         switch (type) {
3570         case LINE_PARENT:
3571                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3572                 break;
3574         case LINE_AUTHOR:
3575         {
3576                 /* Parse author lines where the name may be empty:
3577                  *      author  <email@address.tld> 1138474660 +0100
3578                  */
3579                 char *ident = line + STRING_SIZE("author ");
3580                 char *nameend = strchr(ident, '<');
3581                 char *emailend = strchr(ident, '>');
3583                 if (!nameend || !emailend)
3584                         break;
3586                 update_rev_graph(graph);
3587                 graph = graph->next;
3589                 *nameend = *emailend = 0;
3590                 ident = chomp_string(ident);
3591                 if (!*ident) {
3592                         ident = chomp_string(nameend + 1);
3593                         if (!*ident)
3594                                 ident = "Unknown";
3595                 }
3597                 string_ncopy(commit->author, ident, strlen(ident));
3599                 /* Parse epoch and timezone */
3600                 if (emailend[1] == ' ') {
3601                         char *secs = emailend + 2;
3602                         char *zone = strchr(secs, ' ');
3603                         time_t time = (time_t) atol(secs);
3605                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3606                                 long tz;
3608                                 zone++;
3609                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3610                                 tz += ('0' - zone[2]) * 60 * 60;
3611                                 tz += ('0' - zone[3]) * 60;
3612                                 tz += ('0' - zone[4]) * 60;
3614                                 if (zone[0] == '-')
3615                                         tz = -tz;
3617                                 time -= tz;
3618                         }
3620                         gmtime_r(&time, &commit->time);
3621                 }
3622                 break;
3623         }
3624         default:
3625                 /* Fill in the commit title if it has not already been set. */
3626                 if (commit->title[0])
3627                         break;
3629                 /* Require titles to start with a non-space character at the
3630                  * offset used by git log. */
3631                 if (strncmp(line, "    ", 4))
3632                         break;
3633                 line += 4;
3634                 /* Well, if the title starts with a whitespace character,
3635                  * try to be forgiving.  Otherwise we end up with no title. */
3636                 while (isspace(*line))
3637                         line++;
3638                 if (*line == '\0')
3639                         break;
3640                 /* FIXME: More graceful handling of titles; append "..." to
3641                  * shortened titles, etc. */
3643                 string_ncopy(commit->title, line, strlen(line));
3644         }
3646         return TRUE;
3649 static bool
3650 main_enter(struct view *view, struct line *line)
3652         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3654         open_view(view, REQ_VIEW_DIFF, flags);
3655         return TRUE;
3658 static bool
3659 main_grep(struct view *view, struct line *line)
3661         struct commit *commit = line->data;
3662         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3663         char buf[DATE_COLS + 1];
3664         regmatch_t pmatch;
3666         for (state = S_TITLE; state < S_END; state++) {
3667                 char *text;
3669                 switch (state) {
3670                 case S_TITLE:   text = commit->title;   break;
3671                 case S_AUTHOR:  text = commit->author;  break;
3672                 case S_DATE:
3673                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3674                                 continue;
3675                         text = buf;
3676                         break;
3678                 default:
3679                         return FALSE;
3680                 }
3682                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3683                         return TRUE;
3684         }
3686         return FALSE;
3689 static void
3690 main_select(struct view *view, struct line *line)
3692         struct commit *commit = line->data;
3694         string_copy_rev(view->ref, commit->id);
3695         string_copy_rev(ref_commit, view->ref);
3698 static struct view_ops main_ops = {
3699         "commit",
3700         NULL,
3701         main_read,
3702         main_draw,
3703         main_enter,
3704         main_grep,
3705         main_select,
3706 };
3709 /*
3710  * Unicode / UTF-8 handling
3711  *
3712  * NOTE: Much of the following code for dealing with unicode is derived from
3713  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3714  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3715  */
3717 /* I've (over)annotated a lot of code snippets because I am not entirely
3718  * confident that the approach taken by this small UTF-8 interface is correct.
3719  * --jonas */
3721 static inline int
3722 unicode_width(unsigned long c)
3724         if (c >= 0x1100 &&
3725            (c <= 0x115f                         /* Hangul Jamo */
3726             || c == 0x2329
3727             || c == 0x232a
3728             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
3729                                                 /* CJK ... Yi */
3730             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
3731             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
3732             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
3733             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
3734             || (c >= 0xffe0  && c <= 0xffe6)
3735             || (c >= 0x20000 && c <= 0x2fffd)
3736             || (c >= 0x30000 && c <= 0x3fffd)))
3737                 return 2;
3739         return 1;
3742 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3743  * Illegal bytes are set one. */
3744 static const unsigned char utf8_bytes[256] = {
3745         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,
3746         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,
3747         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,
3748         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,
3749         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,
3750         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,
3751         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,
3752         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,
3753 };
3755 /* Decode UTF-8 multi-byte representation into a unicode character. */
3756 static inline unsigned long
3757 utf8_to_unicode(const char *string, size_t length)
3759         unsigned long unicode;
3761         switch (length) {
3762         case 1:
3763                 unicode  =   string[0];
3764                 break;
3765         case 2:
3766                 unicode  =  (string[0] & 0x1f) << 6;
3767                 unicode +=  (string[1] & 0x3f);
3768                 break;
3769         case 3:
3770                 unicode  =  (string[0] & 0x0f) << 12;
3771                 unicode += ((string[1] & 0x3f) << 6);
3772                 unicode +=  (string[2] & 0x3f);
3773                 break;
3774         case 4:
3775                 unicode  =  (string[0] & 0x0f) << 18;
3776                 unicode += ((string[1] & 0x3f) << 12);
3777                 unicode += ((string[2] & 0x3f) << 6);
3778                 unicode +=  (string[3] & 0x3f);
3779                 break;
3780         case 5:
3781                 unicode  =  (string[0] & 0x0f) << 24;
3782                 unicode += ((string[1] & 0x3f) << 18);
3783                 unicode += ((string[2] & 0x3f) << 12);
3784                 unicode += ((string[3] & 0x3f) << 6);
3785                 unicode +=  (string[4] & 0x3f);
3786                 break;
3787         case 6:
3788                 unicode  =  (string[0] & 0x01) << 30;
3789                 unicode += ((string[1] & 0x3f) << 24);
3790                 unicode += ((string[2] & 0x3f) << 18);
3791                 unicode += ((string[3] & 0x3f) << 12);
3792                 unicode += ((string[4] & 0x3f) << 6);
3793                 unicode +=  (string[5] & 0x3f);
3794                 break;
3795         default:
3796                 die("Invalid unicode length");
3797         }
3799         /* Invalid characters could return the special 0xfffd value but NUL
3800          * should be just as good. */
3801         return unicode > 0xffff ? 0 : unicode;
3804 /* Calculates how much of string can be shown within the given maximum width
3805  * and sets trimmed parameter to non-zero value if all of string could not be
3806  * shown.
3807  *
3808  * Additionally, adds to coloffset how many many columns to move to align with
3809  * the expected position. Takes into account how multi-byte and double-width
3810  * characters will effect the cursor position.
3811  *
3812  * Returns the number of bytes to output from string to satisfy max_width. */
3813 static size_t
3814 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3816         const char *start = string;
3817         const char *end = strchr(string, '\0');
3818         size_t mbwidth = 0;
3819         size_t width = 0;
3821         *trimmed = 0;
3823         while (string < end) {
3824                 int c = *(unsigned char *) string;
3825                 unsigned char bytes = utf8_bytes[c];
3826                 size_t ucwidth;
3827                 unsigned long unicode;
3829                 if (string + bytes > end)
3830                         break;
3832                 /* Change representation to figure out whether
3833                  * it is a single- or double-width character. */
3835                 unicode = utf8_to_unicode(string, bytes);
3836                 /* FIXME: Graceful handling of invalid unicode character. */
3837                 if (!unicode)
3838                         break;
3840                 ucwidth = unicode_width(unicode);
3841                 width  += ucwidth;
3842                 if (width > max_width) {
3843                         *trimmed = 1;
3844                         break;
3845                 }
3847                 /* The column offset collects the differences between the
3848                  * number of bytes encoding a character and the number of
3849                  * columns will be used for rendering said character.
3850                  *
3851                  * So if some character A is encoded in 2 bytes, but will be
3852                  * represented on the screen using only 1 byte this will and up
3853                  * adding 1 to the multi-byte column offset.
3854                  *
3855                  * Assumes that no double-width character can be encoding in
3856                  * less than two bytes. */
3857                 if (bytes > ucwidth)
3858                         mbwidth += bytes - ucwidth;
3860                 string  += bytes;
3861         }
3863         *coloffset += mbwidth;
3865         return string - start;
3869 /*
3870  * Status management
3871  */
3873 /* Whether or not the curses interface has been initialized. */
3874 static bool cursed = FALSE;
3876 /* The status window is used for polling keystrokes. */
3877 static WINDOW *status_win;
3879 static bool status_empty = TRUE;
3881 /* Update status and title window. */
3882 static void
3883 report(const char *msg, ...)
3885         struct view *view = display[current_view];
3887         if (input_mode)
3888                 return;
3890         if (!status_empty || *msg) {
3891                 va_list args;
3893                 va_start(args, msg);
3895                 wmove(status_win, 0, 0);
3896                 if (*msg) {
3897                         vwprintw(status_win, msg, args);
3898                         status_empty = FALSE;
3899                 } else {
3900                         status_empty = TRUE;
3901                 }
3902                 wclrtoeol(status_win);
3903                 wrefresh(status_win);
3905                 va_end(args);
3906         }
3908         update_view_title(view);
3909         update_display_cursor(view);
3912 /* Controls when nodelay should be in effect when polling user input. */
3913 static void
3914 set_nonblocking_input(bool loading)
3916         static unsigned int loading_views;
3918         if ((loading == FALSE && loading_views-- == 1) ||
3919             (loading == TRUE  && loading_views++ == 0))
3920                 nodelay(status_win, loading);
3923 static void
3924 init_display(void)
3926         int x, y;
3928         /* Initialize the curses library */
3929         if (isatty(STDIN_FILENO)) {
3930                 cursed = !!initscr();
3931         } else {
3932                 /* Leave stdin and stdout alone when acting as a pager. */
3933                 FILE *io = fopen("/dev/tty", "r+");
3935                 if (!io)
3936                         die("Failed to open /dev/tty");
3937                 cursed = !!newterm(NULL, io, io);
3938         }
3940         if (!cursed)
3941                 die("Failed to initialize curses");
3943         nonl();         /* Tell curses not to do NL->CR/NL on output */
3944         cbreak();       /* Take input chars one at a time, no wait for \n */
3945         noecho();       /* Don't echo input */
3946         leaveok(stdscr, TRUE);
3948         if (has_colors())
3949                 init_colors();
3951         getmaxyx(stdscr, y, x);
3952         status_win = newwin(1, 0, y - 1, 0);
3953         if (!status_win)
3954                 die("Failed to create status window");
3956         /* Enable keyboard mapping */
3957         keypad(status_win, TRUE);
3958         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3961 static char *
3962 read_prompt(const char *prompt)
3964         enum { READING, STOP, CANCEL } status = READING;
3965         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3966         int pos = 0;
3968         while (status == READING) {
3969                 struct view *view;
3970                 int i, key;
3972                 input_mode = TRUE;
3974                 foreach_view (view, i)
3975                         update_view(view);
3977                 input_mode = FALSE;
3979                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
3980                 wclrtoeol(status_win);
3982                 /* Refresh, accept single keystroke of input */
3983                 key = wgetch(status_win);
3984                 switch (key) {
3985                 case KEY_RETURN:
3986                 case KEY_ENTER:
3987                 case '\n':
3988                         status = pos ? STOP : CANCEL;
3989                         break;
3991                 case KEY_BACKSPACE:
3992                         if (pos > 0)
3993                                 pos--;
3994                         else
3995                                 status = CANCEL;
3996                         break;
3998                 case KEY_ESC:
3999                         status = CANCEL;
4000                         break;
4002                 case ERR:
4003                         break;
4005                 default:
4006                         if (pos >= sizeof(buf)) {
4007                                 report("Input string too long");
4008                                 return NULL;
4009                         }
4011                         if (isprint(key))
4012                                 buf[pos++] = (char) key;
4013                 }
4014         }
4016         /* Clear the status window */
4017         status_empty = FALSE;
4018         report("");
4020         if (status == CANCEL)
4021                 return NULL;
4023         buf[pos++] = 0;
4025         return buf;
4028 /*
4029  * Repository references
4030  */
4032 static struct ref *refs;
4033 static size_t refs_size;
4035 /* Id <-> ref store */
4036 static struct ref ***id_refs;
4037 static size_t id_refs_size;
4039 static struct ref **
4040 get_refs(char *id)
4042         struct ref ***tmp_id_refs;
4043         struct ref **ref_list = NULL;
4044         size_t ref_list_size = 0;
4045         size_t i;
4047         for (i = 0; i < id_refs_size; i++)
4048                 if (!strcmp(id, id_refs[i][0]->id))
4049                         return id_refs[i];
4051         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4052         if (!tmp_id_refs)
4053                 return NULL;
4055         id_refs = tmp_id_refs;
4057         for (i = 0; i < refs_size; i++) {
4058                 struct ref **tmp;
4060                 if (strcmp(id, refs[i].id))
4061                         continue;
4063                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4064                 if (!tmp) {
4065                         if (ref_list)
4066                                 free(ref_list);
4067                         return NULL;
4068                 }
4070                 ref_list = tmp;
4071                 if (ref_list_size > 0)
4072                         ref_list[ref_list_size - 1]->next = 1;
4073                 ref_list[ref_list_size] = &refs[i];
4075                 /* XXX: The properties of the commit chains ensures that we can
4076                  * safely modify the shared ref. The repo references will
4077                  * always be similar for the same id. */
4078                 ref_list[ref_list_size]->next = 0;
4079                 ref_list_size++;
4080         }
4082         if (ref_list)
4083                 id_refs[id_refs_size++] = ref_list;
4085         return ref_list;
4088 static int
4089 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4091         struct ref *ref;
4092         bool tag = FALSE;
4093         bool remote = FALSE;
4095         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4096                 /* Commits referenced by tags has "^{}" appended. */
4097                 if (name[namelen - 1] != '}')
4098                         return OK;
4100                 while (namelen > 0 && name[namelen] != '^')
4101                         namelen--;
4103                 tag = TRUE;
4104                 namelen -= STRING_SIZE("refs/tags/");
4105                 name    += STRING_SIZE("refs/tags/");
4107         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4108                 remote = TRUE;
4109                 namelen -= STRING_SIZE("refs/remotes/");
4110                 name    += STRING_SIZE("refs/remotes/");
4112         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4113                 namelen -= STRING_SIZE("refs/heads/");
4114                 name    += STRING_SIZE("refs/heads/");
4116         } else if (!strcmp(name, "HEAD")) {
4117                 return OK;
4118         }
4120         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4121         if (!refs)
4122                 return ERR;
4124         ref = &refs[refs_size++];
4125         ref->name = malloc(namelen + 1);
4126         if (!ref->name)
4127                 return ERR;
4129         strncpy(ref->name, name, namelen);
4130         ref->name[namelen] = 0;
4131         ref->tag = tag;
4132         ref->remote = remote;
4133         string_copy_rev(ref->id, id);
4135         return OK;
4138 static int
4139 load_refs(void)
4141         const char *cmd_env = getenv("TIG_LS_REMOTE");
4142         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4144         return read_properties(popen(cmd, "r"), "\t", read_ref);
4147 static int
4148 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4150         if (!strcmp(name, "i18n.commitencoding"))
4151                 string_ncopy(opt_encoding, value, valuelen);
4153         return OK;
4156 static int
4157 load_repo_config(void)
4159         return read_properties(popen("git repo-config --list", "r"),
4160                                "=", read_repo_config_option);
4163 static int
4164 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4166         if (!opt_git_dir[0])
4167                 string_ncopy(opt_git_dir, name, namelen);
4168         else
4169                 string_ncopy(opt_cdup, name, namelen);
4170         return OK;
4173 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4174  * must be the last one! */
4175 static int
4176 load_repo_info(void)
4178         return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4179                                "=", read_repo_info);
4182 static int
4183 read_properties(FILE *pipe, const char *separators,
4184                 int (*read_property)(char *, size_t, char *, size_t))
4186         char buffer[BUFSIZ];
4187         char *name;
4188         int state = OK;
4190         if (!pipe)
4191                 return ERR;
4193         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4194                 char *value;
4195                 size_t namelen;
4196                 size_t valuelen;
4198                 name = chomp_string(name);
4199                 namelen = strcspn(name, separators);
4201                 if (name[namelen]) {
4202                         name[namelen] = 0;
4203                         value = chomp_string(name + namelen + 1);
4204                         valuelen = strlen(value);
4206                 } else {
4207                         value = "";
4208                         valuelen = 0;
4209                 }
4211                 state = read_property(name, namelen, value, valuelen);
4212         }
4214         if (state != ERR && ferror(pipe))
4215                 state = ERR;
4217         pclose(pipe);
4219         return state;
4223 /*
4224  * Main
4225  */
4227 static void __NORETURN
4228 quit(int sig)
4230         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4231         if (cursed)
4232                 endwin();
4233         exit(0);
4236 static void __NORETURN
4237 die(const char *err, ...)
4239         va_list args;
4241         endwin();
4243         va_start(args, err);
4244         fputs("tig: ", stderr);
4245         vfprintf(stderr, err, args);
4246         fputs("\n", stderr);
4247         va_end(args);
4249         exit(1);
4252 int
4253 main(int argc, char *argv[])
4255         struct view *view;
4256         enum request request;
4257         size_t i;
4259         signal(SIGINT, quit);
4261         if (setlocale(LC_ALL, "")) {
4262                 char *codeset = nl_langinfo(CODESET);
4264                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4265         }
4267         if (load_repo_info() == ERR)
4268                 die("Failed to load repo info.");
4270         /* Require a git repository unless when running in pager mode. */
4271         if (!opt_git_dir[0])
4272                 die("Not a git repository");
4274         if (load_options() == ERR)
4275                 die("Failed to load user config.");
4277         /* Load the repo config file so options can be overwritten from
4278          * the command line. */
4279         if (load_repo_config() == ERR)
4280                 die("Failed to load repo config.");
4282         if (!parse_options(argc, argv))
4283                 return 0;
4285         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4286                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4287                 if (opt_iconv == ICONV_NONE)
4288                         die("Failed to initialize character set conversion");
4289         }
4291         if (load_refs() == ERR)
4292                 die("Failed to load refs.");
4294         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4295                 view->cmd_env = getenv(view->cmd_env);
4297         request = opt_request;
4299         init_display();
4301         while (view_driver(display[current_view], request)) {
4302                 int key;
4303                 int i;
4305                 foreach_view (view, i)
4306                         update_view(view);
4308                 /* Refresh, accept single keystroke of input */
4309                 key = wgetch(status_win);
4311                 /* wgetch() with nodelay() enabled returns ERR when there's no
4312                  * input. */
4313                 if (key == ERR) {
4314                         request = REQ_NONE;
4315                         continue;
4316                 }
4318                 request = get_keybinding(display[current_view]->keymap, key);
4320                 /* Some low-level request handling. This keeps access to
4321                  * status_win restricted. */
4322                 switch (request) {
4323                 case REQ_PROMPT:
4324                 {
4325                         char *cmd = read_prompt(":");
4327                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4328                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4329                                         opt_request = REQ_VIEW_DIFF;
4330                                 } else {
4331                                         opt_request = REQ_VIEW_PAGER;
4332                                 }
4333                                 break;
4334                         }
4336                         request = REQ_NONE;
4337                         break;
4338                 }
4339                 case REQ_SEARCH:
4340                 case REQ_SEARCH_BACK:
4341                 {
4342                         const char *prompt = request == REQ_SEARCH
4343                                            ? "/" : "?";
4344                         char *search = read_prompt(prompt);
4346                         if (search)
4347                                 string_ncopy(opt_search, search, strlen(search));
4348                         else
4349                                 request = REQ_NONE;
4350                         break;
4351                 }
4352                 case REQ_SCREEN_RESIZE:
4353                 {
4354                         int height, width;
4356                         getmaxyx(stdscr, height, width);
4358                         /* Resize the status view and let the view driver take
4359                          * care of resizing the displayed views. */
4360                         wresize(status_win, 1, width);
4361                         mvwin(status_win, height - 1, 0);
4362                         wrefresh(status_win);
4363                         break;
4364                 }
4365                 default:
4366                         break;
4367                 }
4368         }
4370         quit(0);
4372         return 0;