Code

Added color option main-revgraph to color the revision graph.
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 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 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
60 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
62 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x)  (sizeof(x) - 1)
65 #define SIZEOF_STR      1024    /* Default string size. */
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT   'I'
72 #define REVGRAPH_MERGE  'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE   '|'
77 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT   (-1)
82 #define ICONV_NONE      ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST     /* nothing */
85 #endif
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
89 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS     20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
96 #define TABSIZE         8
98 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
104 #define TIG_LS_REMOTE \
105         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108         "git show --no-color --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD     \
111         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114         "git log --no-color --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD    \
117         "git ls-tree %s %s"
119 #define TIG_BLOB_CMD    \
120         "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD    ""
124 #define TIG_PAGER_CMD   ""
125 #define TIG_STATUS_CMD  ""
126 #define TIG_STAGE_CMD   ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int tag:1;     /* Is it a tag? */
138         unsigned int remote:1;  /* Is it a remote ref? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(char *id);
144 struct int_map {
145         const char *name;
146         int namelen;
147         int value;
148 };
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152                  int *value, const char *name, int namelen)
155         int i;
157         for (i = 0; i < map_size; i++)
158                 if (namelen == map[i].namelen &&
159                     !strncasecmp(name, map[i].name, namelen)) {
160                         *value = map[i].value;
161                         return OK;
162                 }
164         return ERR;
168 /*
169  * String helpers
170  */
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175         if (srclen > dstlen - 1)
176                 srclen = dstlen - 1;
178         strncpy(dst, src, srclen);
179         dst[srclen] = 0;
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188         string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
196 static char *
197 chomp_string(char *name)
199         int namelen;
201         while (isspace(*name))
202                 name++;
204         namelen = strlen(name) - 1;
205         while (namelen > 0 && isspace(name[namelen]))
206                 name[namelen--] = 0;
208         return name;
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
214         va_list args;
215         size_t pos = bufpos ? *bufpos : 0;
217         va_start(args, fmt);
218         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219         va_end(args);
221         if (bufpos)
222                 *bufpos = pos;
224         return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228         string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231         string_nformat(buf, sizeof(buf), from, fmt, args)
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
236         size_t i;
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240         /* Diff-Header == DIFF_HEADER */
241         for (i = 0; i < len; i++) {
242                 if (toupper(str1[i]) == toupper(str2[i]))
243                         continue;
245                 if (string_enum_sep(str1[i]) &&
246                     string_enum_sep(str2[i]))
247                         continue;
249                 return str1[i] - str2[i];
250         }
252         return 0;
255 /* Shell quoting
256  *
257  * NOTE: The following is a slightly modified copy of the git project's shell
258  * quoting routines found in the quote.c file.
259  *
260  * Help to copy the thing properly quoted for the shell safety.  any single
261  * quote is replaced with '\'', any exclamation point is replaced with '\!',
262  * and the whole thing is enclosed in a
263  *
264  * E.g.
265  *  original     sq_quote     result
266  *  name     ==> name      ==> 'name'
267  *  a b      ==> a b       ==> 'a b'
268  *  a'b      ==> a'\''b    ==> 'a'\''b'
269  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
270  */
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
275         char c;
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
279         BUFPUT('\'');
280         while ((c = *src++)) {
281                 if (c == '\'' || c == '!') {
282                         BUFPUT('\'');
283                         BUFPUT('\\');
284                         BUFPUT(c);
285                         BUFPUT('\'');
286                 } else {
287                         BUFPUT(c);
288                 }
289         }
290         BUFPUT('\'');
292         if (bufsize < SIZEOF_STR)
293                 buf[bufsize] = 0;
295         return bufsize;
299 /*
300  * User requests
301  */
303 #define REQ_INFO \
304         /* XXX: Keep the view request first and in sync with views[]. */ \
305         REQ_GROUP("View switching") \
306         REQ_(VIEW_MAIN,         "Show main view"), \
307         REQ_(VIEW_DIFF,         "Show diff view"), \
308         REQ_(VIEW_LOG,          "Show log view"), \
309         REQ_(VIEW_TREE,         "Show tree view"), \
310         REQ_(VIEW_BLOB,         "Show blob view"), \
311         REQ_(VIEW_HELP,         "Show help page"), \
312         REQ_(VIEW_PAGER,        "Show pager view"), \
313         REQ_(VIEW_STATUS,       "Show status view"), \
314         REQ_(VIEW_STAGE,        "Show stage view"), \
315         \
316         REQ_GROUP("View manipulation") \
317         REQ_(ENTER,             "Enter current line and scroll"), \
318         REQ_(NEXT,              "Move to next"), \
319         REQ_(PREVIOUS,          "Move to previous"), \
320         REQ_(VIEW_NEXT,         "Move focus to next view"), \
321         REQ_(REFRESH,           "Reload and refresh"), \
322         REQ_(VIEW_CLOSE,        "Close the current view"), \
323         REQ_(QUIT,              "Close all views and quit"), \
324         \
325         REQ_GROUP("Cursor navigation") \
326         REQ_(MOVE_UP,           "Move cursor one line up"), \
327         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
328         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
329         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
330         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
331         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
332         \
333         REQ_GROUP("Scrolling") \
334         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
335         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
336         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
337         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
338         \
339         REQ_GROUP("Searching") \
340         REQ_(SEARCH,            "Search the view"), \
341         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
342         REQ_(FIND_NEXT,         "Find next search match"), \
343         REQ_(FIND_PREV,         "Find previous search match"), \
344         \
345         REQ_GROUP("Misc") \
346         REQ_(PROMPT,            "Bring up the prompt"), \
347         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
348         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
349         REQ_(SHOW_VERSION,      "Show version information"), \
350         REQ_(STOP_LOADING,      "Stop all loading views"), \
351         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
352         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
353         REQ_(STATUS_UPDATE,     "Update file status"), \
354         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
355         REQ_(EDIT,              "Open in editor"), \
356         REQ_(NONE,              "Do nothing")
359 /* User action requests. */
360 enum request {
361 #define REQ_GROUP(help)
362 #define REQ_(req, help) REQ_##req
364         /* Offset all requests to avoid conflicts with ncurses getch values. */
365         REQ_OFFSET = KEY_MAX + 1,
366         REQ_INFO
368 #undef  REQ_GROUP
369 #undef  REQ_
370 };
372 struct request_info {
373         enum request request;
374         char *name;
375         int namelen;
376         char *help;
377 };
379 static struct request_info req_info[] = {
380 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
381 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
382         REQ_INFO
383 #undef  REQ_GROUP
384 #undef  REQ_
385 };
387 static enum request
388 get_request(const char *name)
390         int namelen = strlen(name);
391         int i;
393         for (i = 0; i < ARRAY_SIZE(req_info); i++)
394                 if (req_info[i].namelen == namelen &&
395                     !string_enum_compare(req_info[i].name, name, namelen))
396                         return req_info[i].request;
398         return REQ_NONE;
402 /*
403  * Options
404  */
406 static const char usage[] =
407 "tig " TIG_VERSION " (" __DATE__ ")\n"
408 "\n"
409 "Usage: tig [options]\n"
410 "   or: tig [options] [--] [git log options]\n"
411 "   or: tig [options] log  [git log options]\n"
412 "   or: tig [options] diff [git diff options]\n"
413 "   or: tig [options] show [git show options]\n"
414 "   or: tig [options] <    [git command output]\n"
415 "\n"
416 "Options:\n"
417 "  -l                          Start up in log view\n"
418 "  -d                          Start up in diff view\n"
419 "  -S                          Start up in status view\n"
420 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
421 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
422 "  --                          Mark end of tig options\n"
423 "  -v, --version               Show version and exit\n"
424 "  -h, --help                  Show help message and exit\n";
426 /* Option and state variables. */
427 static bool opt_line_number             = FALSE;
428 static bool opt_rev_graph               = FALSE;
429 static int opt_num_interval             = NUMBER_INTERVAL;
430 static int opt_tab_size                 = TABSIZE;
431 static enum request opt_request         = REQ_VIEW_MAIN;
432 static char opt_cmd[SIZEOF_STR]         = "";
433 static char opt_path[SIZEOF_STR]        = "";
434 static FILE *opt_pipe                   = NULL;
435 static char opt_encoding[20]            = "UTF-8";
436 static bool opt_utf8                    = TRUE;
437 static char opt_codeset[20]             = "UTF-8";
438 static iconv_t opt_iconv                = ICONV_NONE;
439 static char opt_search[SIZEOF_STR]      = "";
440 static char opt_cdup[SIZEOF_STR]        = "";
441 static char opt_git_dir[SIZEOF_STR]     = "";
442 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
443 static char opt_editor[SIZEOF_STR]      = "";
445 enum option_type {
446         OPT_NONE,
447         OPT_INT,
448 };
450 static bool
451 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
453         va_list args;
454         char *value = "";
455         int *number;
457         if (opt[0] != '-')
458                 return FALSE;
460         if (opt[1] == '-') {
461                 int namelen = strlen(name);
463                 opt += 2;
465                 if (strncmp(opt, name, namelen))
466                         return FALSE;
468                 if (opt[namelen] == '=')
469                         value = opt + namelen + 1;
471         } else {
472                 if (!short_name || opt[1] != short_name)
473                         return FALSE;
474                 value = opt + 2;
475         }
477         va_start(args, type);
478         if (type == OPT_INT) {
479                 number = va_arg(args, int *);
480                 if (isdigit(*value))
481                         *number = atoi(value);
482         }
483         va_end(args);
485         return TRUE;
488 /* Returns the index of log or diff command or -1 to exit. */
489 static bool
490 parse_options(int argc, char *argv[])
492         int i;
494         for (i = 1; i < argc; i++) {
495                 char *opt = argv[i];
497                 if (!strcmp(opt, "log") ||
498                     !strcmp(opt, "diff") ||
499                     !strcmp(opt, "show")) {
500                         opt_request = opt[0] == 'l'
501                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
502                         break;
503                 }
505                 if (opt[0] && opt[0] != '-')
506                         break;
508                 if (!strcmp(opt, "--")) {
509                         i++;
510                         break;
511                 }
513                 if (check_option(opt, 'v', "version", OPT_NONE)) {
514                         printf("tig version %s\n", TIG_VERSION);
515                         return FALSE;
516                 }
518                 if (check_option(opt, 'h', "help", OPT_NONE)) {
519                         printf(usage);
520                         return FALSE;
521                 }
523                 if (!strcmp(opt, "-S")) {
524                         opt_request = REQ_VIEW_STATUS;
525                         continue;
526                 }
528                 if (!strcmp(opt, "-l")) {
529                         opt_request = REQ_VIEW_LOG;
530                         continue;
531                 }
533                 if (!strcmp(opt, "-d")) {
534                         opt_request = REQ_VIEW_DIFF;
535                         continue;
536                 }
538                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
539                         opt_line_number = TRUE;
540                         continue;
541                 }
543                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
544                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
545                         continue;
546                 }
548                 die("unknown option '%s'\n\n%s", opt, usage);
549         }
551         if (!isatty(STDIN_FILENO)) {
552                 opt_request = REQ_VIEW_PAGER;
553                 opt_pipe = stdin;
555         } else if (i < argc) {
556                 size_t buf_size;
558                 if (opt_request == REQ_VIEW_MAIN)
559                         /* XXX: This is vulnerable to the user overriding
560                          * options required for the main view parser. */
561                         string_copy(opt_cmd, "git log --no-color --pretty=raw");
562                 else
563                         string_copy(opt_cmd, "git");
564                 buf_size = strlen(opt_cmd);
566                 while (buf_size < sizeof(opt_cmd) && i < argc) {
567                         opt_cmd[buf_size++] = ' ';
568                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
569                 }
571                 if (buf_size >= sizeof(opt_cmd))
572                         die("command too long");
574                 opt_cmd[buf_size] = 0;
575         }
577         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
578                 opt_utf8 = FALSE;
580         return TRUE;
584 /*
585  * Line-oriented content detection.
586  */
588 #define LINE_INFO \
589 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
593 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
594 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
603 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
604 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
610 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
612 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
616 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
618 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
619 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
620 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
621 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
622 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
623 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
624 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
625 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
626 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
627 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
630 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
631 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
632 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
633 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
634 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
636 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
638 enum line_type {
639 #define LINE(type, line, fg, bg, attr) \
640         LINE_##type
641         LINE_INFO
642 #undef  LINE
643 };
645 struct line_info {
646         const char *name;       /* Option name. */
647         int namelen;            /* Size of option name. */
648         const char *line;       /* The start of line to match. */
649         int linelen;            /* Size of string to match. */
650         int fg, bg, attr;       /* Color and text attributes for the lines. */
651 };
653 static struct line_info line_info[] = {
654 #define LINE(type, line, fg, bg, attr) \
655         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
656         LINE_INFO
657 #undef  LINE
658 };
660 static enum line_type
661 get_line_type(char *line)
663         int linelen = strlen(line);
664         enum line_type type;
666         for (type = 0; type < ARRAY_SIZE(line_info); type++)
667                 /* Case insensitive search matches Signed-off-by lines better. */
668                 if (linelen >= line_info[type].linelen &&
669                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
670                         return type;
672         return LINE_DEFAULT;
675 static inline int
676 get_line_attr(enum line_type type)
678         assert(type < ARRAY_SIZE(line_info));
679         return COLOR_PAIR(type) | line_info[type].attr;
682 static struct line_info *
683 get_line_info(char *name, int namelen)
685         enum line_type type;
687         for (type = 0; type < ARRAY_SIZE(line_info); type++)
688                 if (namelen == line_info[type].namelen &&
689                     !string_enum_compare(line_info[type].name, name, namelen))
690                         return &line_info[type];
692         return NULL;
695 static void
696 init_colors(void)
698         int default_bg = COLOR_BLACK;
699         int default_fg = COLOR_WHITE;
700         enum line_type type;
702         start_color();
704         if (use_default_colors() != ERR) {
705                 default_bg = -1;
706                 default_fg = -1;
707         }
709         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
710                 struct line_info *info = &line_info[type];
711                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
712                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
714                 init_pair(type, fg, bg);
715         }
718 struct line {
719         enum line_type type;
721         /* State flags */
722         unsigned int selected:1;
724         void *data;             /* User data */
725 };
728 /*
729  * Keys
730  */
732 struct keybinding {
733         int alias;
734         enum request request;
735         struct keybinding *next;
736 };
738 static struct keybinding default_keybindings[] = {
739         /* View switching */
740         { 'm',          REQ_VIEW_MAIN },
741         { 'd',          REQ_VIEW_DIFF },
742         { 'l',          REQ_VIEW_LOG },
743         { 't',          REQ_VIEW_TREE },
744         { 'f',          REQ_VIEW_BLOB },
745         { 'p',          REQ_VIEW_PAGER },
746         { 'h',          REQ_VIEW_HELP },
747         { 'S',          REQ_VIEW_STATUS },
748         { 'c',          REQ_VIEW_STAGE },
750         /* View manipulation */
751         { 'q',          REQ_VIEW_CLOSE },
752         { KEY_TAB,      REQ_VIEW_NEXT },
753         { KEY_RETURN,   REQ_ENTER },
754         { KEY_UP,       REQ_PREVIOUS },
755         { KEY_DOWN,     REQ_NEXT },
756         { 'R',          REQ_REFRESH },
758         /* Cursor navigation */
759         { 'k',          REQ_MOVE_UP },
760         { 'j',          REQ_MOVE_DOWN },
761         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
762         { KEY_END,      REQ_MOVE_LAST_LINE },
763         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
764         { ' ',          REQ_MOVE_PAGE_DOWN },
765         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
766         { 'b',          REQ_MOVE_PAGE_UP },
767         { '-',          REQ_MOVE_PAGE_UP },
769         /* Scrolling */
770         { KEY_IC,       REQ_SCROLL_LINE_UP },
771         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
772         { 'w',          REQ_SCROLL_PAGE_UP },
773         { 's',          REQ_SCROLL_PAGE_DOWN },
775         /* Searching */
776         { '/',          REQ_SEARCH },
777         { '?',          REQ_SEARCH_BACK },
778         { 'n',          REQ_FIND_NEXT },
779         { 'N',          REQ_FIND_PREV },
781         /* Misc */
782         { 'Q',          REQ_QUIT },
783         { 'z',          REQ_STOP_LOADING },
784         { 'v',          REQ_SHOW_VERSION },
785         { 'r',          REQ_SCREEN_REDRAW },
786         { '.',          REQ_TOGGLE_LINENO },
787         { 'g',          REQ_TOGGLE_REV_GRAPH },
788         { ':',          REQ_PROMPT },
789         { 'u',          REQ_STATUS_UPDATE },
790         { 'M',          REQ_STATUS_MERGE },
791         { 'e',          REQ_EDIT },
793         /* Using the ncurses SIGWINCH handler. */
794         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
795 };
797 #define KEYMAP_INFO \
798         KEYMAP_(GENERIC), \
799         KEYMAP_(MAIN), \
800         KEYMAP_(DIFF), \
801         KEYMAP_(LOG), \
802         KEYMAP_(TREE), \
803         KEYMAP_(BLOB), \
804         KEYMAP_(PAGER), \
805         KEYMAP_(HELP), \
806         KEYMAP_(STATUS), \
807         KEYMAP_(STAGE)
809 enum keymap {
810 #define KEYMAP_(name) KEYMAP_##name
811         KEYMAP_INFO
812 #undef  KEYMAP_
813 };
815 static struct int_map keymap_table[] = {
816 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
817         KEYMAP_INFO
818 #undef  KEYMAP_
819 };
821 #define set_keymap(map, name) \
822         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
824 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
826 static void
827 add_keybinding(enum keymap keymap, enum request request, int key)
829         struct keybinding *keybinding;
831         keybinding = calloc(1, sizeof(*keybinding));
832         if (!keybinding)
833                 die("Failed to allocate keybinding");
835         keybinding->alias = key;
836         keybinding->request = request;
837         keybinding->next = keybindings[keymap];
838         keybindings[keymap] = keybinding;
841 /* Looks for a key binding first in the given map, then in the generic map, and
842  * lastly in the default keybindings. */
843 static enum request
844 get_keybinding(enum keymap keymap, int key)
846         struct keybinding *kbd;
847         int i;
849         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
850                 if (kbd->alias == key)
851                         return kbd->request;
853         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
854                 if (kbd->alias == key)
855                         return kbd->request;
857         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
858                 if (default_keybindings[i].alias == key)
859                         return default_keybindings[i].request;
861         return (enum request) key;
865 struct key {
866         char *name;
867         int value;
868 };
870 static struct key key_table[] = {
871         { "Enter",      KEY_RETURN },
872         { "Space",      ' ' },
873         { "Backspace",  KEY_BACKSPACE },
874         { "Tab",        KEY_TAB },
875         { "Escape",     KEY_ESC },
876         { "Left",       KEY_LEFT },
877         { "Right",      KEY_RIGHT },
878         { "Up",         KEY_UP },
879         { "Down",       KEY_DOWN },
880         { "Insert",     KEY_IC },
881         { "Delete",     KEY_DC },
882         { "Hash",       '#' },
883         { "Home",       KEY_HOME },
884         { "End",        KEY_END },
885         { "PageUp",     KEY_PPAGE },
886         { "PageDown",   KEY_NPAGE },
887         { "F1",         KEY_F(1) },
888         { "F2",         KEY_F(2) },
889         { "F3",         KEY_F(3) },
890         { "F4",         KEY_F(4) },
891         { "F5",         KEY_F(5) },
892         { "F6",         KEY_F(6) },
893         { "F7",         KEY_F(7) },
894         { "F8",         KEY_F(8) },
895         { "F9",         KEY_F(9) },
896         { "F10",        KEY_F(10) },
897         { "F11",        KEY_F(11) },
898         { "F12",        KEY_F(12) },
899 };
901 static int
902 get_key_value(const char *name)
904         int i;
906         for (i = 0; i < ARRAY_SIZE(key_table); i++)
907                 if (!strcasecmp(key_table[i].name, name))
908                         return key_table[i].value;
910         if (strlen(name) == 1 && isprint(*name))
911                 return (int) *name;
913         return ERR;
916 static char *
917 get_key_name(int key_value)
919         static char key_char[] = "'X'";
920         char *seq = NULL;
921         int key;
923         for (key = 0; key < ARRAY_SIZE(key_table); key++)
924                 if (key_table[key].value == key_value)
925                         seq = key_table[key].name;
927         if (seq == NULL &&
928             key_value < 127 &&
929             isprint(key_value)) {
930                 key_char[1] = (char) key_value;
931                 seq = key_char;
932         }
934         return seq ? seq : "'?'";
937 static char *
938 get_key(enum request request)
940         static char buf[BUFSIZ];
941         size_t pos = 0;
942         char *sep = "";
943         int i;
945         buf[pos] = 0;
947         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
948                 struct keybinding *keybinding = &default_keybindings[i];
950                 if (keybinding->request != request)
951                         continue;
953                 if (!string_format_from(buf, &pos, "%s%s", sep,
954                                         get_key_name(keybinding->alias)))
955                         return "Too many keybindings!";
956                 sep = ", ";
957         }
959         return buf;
962 struct run_request {
963         enum keymap keymap;
964         int key;
965         char cmd[SIZEOF_STR];
966 };
968 static struct run_request *run_request;
969 static size_t run_requests;
971 static enum request
972 add_run_request(enum keymap keymap, int key, int argc, char **argv)
974         struct run_request *tmp;
975         struct run_request req = { keymap, key };
976         size_t bufpos;
978         for (bufpos = 0; argc > 0; argc--, argv++)
979                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
980                         return REQ_NONE;
982         req.cmd[bufpos - 1] = 0;
984         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
985         if (!tmp)
986                 return REQ_NONE;
988         run_request = tmp;
989         run_request[run_requests++] = req;
991         return REQ_NONE + run_requests;
994 static struct run_request *
995 get_run_request(enum request request)
997         if (request <= REQ_NONE)
998                 return NULL;
999         return &run_request[request - REQ_NONE - 1];
1002 static void
1003 add_builtin_run_requests(void)
1005         struct {
1006                 enum keymap keymap;
1007                 int key;
1008                 char *argv[1];
1009         } reqs[] = {
1010                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1011                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1012         };
1013         int i;
1015         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1016                 enum request req;
1018                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1019                 if (req != REQ_NONE)
1020                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1021         }
1024 /*
1025  * User config file handling.
1026  */
1028 static struct int_map color_map[] = {
1029 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1030         COLOR_MAP(DEFAULT),
1031         COLOR_MAP(BLACK),
1032         COLOR_MAP(BLUE),
1033         COLOR_MAP(CYAN),
1034         COLOR_MAP(GREEN),
1035         COLOR_MAP(MAGENTA),
1036         COLOR_MAP(RED),
1037         COLOR_MAP(WHITE),
1038         COLOR_MAP(YELLOW),
1039 };
1041 #define set_color(color, name) \
1042         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1044 static struct int_map attr_map[] = {
1045 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1046         ATTR_MAP(NORMAL),
1047         ATTR_MAP(BLINK),
1048         ATTR_MAP(BOLD),
1049         ATTR_MAP(DIM),
1050         ATTR_MAP(REVERSE),
1051         ATTR_MAP(STANDOUT),
1052         ATTR_MAP(UNDERLINE),
1053 };
1055 #define set_attribute(attr, name) \
1056         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1058 static int   config_lineno;
1059 static bool  config_errors;
1060 static char *config_msg;
1062 /* Wants: object fgcolor bgcolor [attr] */
1063 static int
1064 option_color_command(int argc, char *argv[])
1066         struct line_info *info;
1068         if (argc != 3 && argc != 4) {
1069                 config_msg = "Wrong number of arguments given to color command";
1070                 return ERR;
1071         }
1073         info = get_line_info(argv[0], strlen(argv[0]));
1074         if (!info) {
1075                 config_msg = "Unknown color name";
1076                 return ERR;
1077         }
1079         if (set_color(&info->fg, argv[1]) == ERR ||
1080             set_color(&info->bg, argv[2]) == ERR) {
1081                 config_msg = "Unknown color";
1082                 return ERR;
1083         }
1085         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1086                 config_msg = "Unknown attribute";
1087                 return ERR;
1088         }
1090         return OK;
1093 /* Wants: name = value */
1094 static int
1095 option_set_command(int argc, char *argv[])
1097         if (argc != 3) {
1098                 config_msg = "Wrong number of arguments given to set command";
1099                 return ERR;
1100         }
1102         if (strcmp(argv[1], "=")) {
1103                 config_msg = "No value assigned";
1104                 return ERR;
1105         }
1107         if (!strcmp(argv[0], "show-rev-graph")) {
1108                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1109                                  !strcmp(argv[2], "true") ||
1110                                  !strcmp(argv[2], "yes"));
1111                 return OK;
1112         }
1114         if (!strcmp(argv[0], "line-number-interval")) {
1115                 opt_num_interval = atoi(argv[2]);
1116                 return OK;
1117         }
1119         if (!strcmp(argv[0], "tab-size")) {
1120                 opt_tab_size = atoi(argv[2]);
1121                 return OK;
1122         }
1124         if (!strcmp(argv[0], "commit-encoding")) {
1125                 char *arg = argv[2];
1126                 int delimiter = *arg;
1127                 int i;
1129                 switch (delimiter) {
1130                 case '"':
1131                 case '\'':
1132                         for (arg++, i = 0; arg[i]; i++)
1133                                 if (arg[i] == delimiter) {
1134                                         arg[i] = 0;
1135                                         break;
1136                                 }
1137                 default:
1138                         string_ncopy(opt_encoding, arg, strlen(arg));
1139                         return OK;
1140                 }
1141         }
1143         config_msg = "Unknown variable name";
1144         return ERR;
1147 /* Wants: mode request key */
1148 static int
1149 option_bind_command(int argc, char *argv[])
1151         enum request request;
1152         int keymap;
1153         int key;
1155         if (argc < 3) {
1156                 config_msg = "Wrong number of arguments given to bind command";
1157                 return ERR;
1158         }
1160         if (set_keymap(&keymap, argv[0]) == ERR) {
1161                 config_msg = "Unknown key map";
1162                 return ERR;
1163         }
1165         key = get_key_value(argv[1]);
1166         if (key == ERR) {
1167                 config_msg = "Unknown key";
1168                 return ERR;
1169         }
1171         request = get_request(argv[2]);
1172         if (request == REQ_NONE) {
1173                 const char *obsolete[] = { "cherry-pick" };
1174                 size_t namelen = strlen(argv[2]);
1175                 int i;
1177                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1178                         if (namelen == strlen(obsolete[i]) &&
1179                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1180                                 config_msg = "Obsolete request name";
1181                                 return ERR;
1182                         }
1183                 }
1184         }
1185         if (request == REQ_NONE && *argv[2]++ == '!')
1186                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1187         if (request == REQ_NONE) {
1188                 config_msg = "Unknown request name";
1189                 return ERR;
1190         }
1192         add_keybinding(keymap, request, key);
1194         return OK;
1197 static int
1198 set_option(char *opt, char *value)
1200         char *argv[16];
1201         int valuelen;
1202         int argc = 0;
1204         /* Tokenize */
1205         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1206                 argv[argc++] = value;
1207                 value += valuelen;
1209                 /* Nothing more to tokenize or last available token. */
1210                 if (!*value || argc >= ARRAY_SIZE(argv))
1211                         break;
1213                 *value++ = 0;
1214                 while (isspace(*value))
1215                         value++;
1216         }
1218         if (!strcmp(opt, "color"))
1219                 return option_color_command(argc, argv);
1221         if (!strcmp(opt, "set"))
1222                 return option_set_command(argc, argv);
1224         if (!strcmp(opt, "bind"))
1225                 return option_bind_command(argc, argv);
1227         config_msg = "Unknown option command";
1228         return ERR;
1231 static int
1232 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1234         int status = OK;
1236         config_lineno++;
1237         config_msg = "Internal error";
1239         /* Check for comment markers, since read_properties() will
1240          * only ensure opt and value are split at first " \t". */
1241         optlen = strcspn(opt, "#");
1242         if (optlen == 0)
1243                 return OK;
1245         if (opt[optlen] != 0) {
1246                 config_msg = "No option value";
1247                 status = ERR;
1249         }  else {
1250                 /* Look for comment endings in the value. */
1251                 size_t len = strcspn(value, "#");
1253                 if (len < valuelen) {
1254                         valuelen = len;
1255                         value[valuelen] = 0;
1256                 }
1258                 status = set_option(opt, value);
1259         }
1261         if (status == ERR) {
1262                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1263                         config_lineno, (int) optlen, opt, config_msg);
1264                 config_errors = TRUE;
1265         }
1267         /* Always keep going if errors are encountered. */
1268         return OK;
1271 static int
1272 load_options(void)
1274         char *home = getenv("HOME");
1275         char buf[SIZEOF_STR];
1276         FILE *file;
1278         config_lineno = 0;
1279         config_errors = FALSE;
1281         add_builtin_run_requests();
1283         if (!home || !string_format(buf, "%s/.tigrc", home))
1284                 return ERR;
1286         /* It's ok that the file doesn't exist. */
1287         file = fopen(buf, "r");
1288         if (!file)
1289                 return OK;
1291         if (read_properties(file, " \t", read_option) == ERR ||
1292             config_errors == TRUE)
1293                 fprintf(stderr, "Errors while loading %s.\n", buf);
1295         return OK;
1299 /*
1300  * The viewer
1301  */
1303 struct view;
1304 struct view_ops;
1306 /* The display array of active views and the index of the current view. */
1307 static struct view *display[2];
1308 static unsigned int current_view;
1310 /* Reading from the prompt? */
1311 static bool input_mode = FALSE;
1313 #define foreach_displayed_view(view, i) \
1314         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1316 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1318 /* Current head and commit ID */
1319 static char ref_blob[SIZEOF_REF]        = "";
1320 static char ref_commit[SIZEOF_REF]      = "HEAD";
1321 static char ref_head[SIZEOF_REF]        = "HEAD";
1323 struct view {
1324         const char *name;       /* View name */
1325         const char *cmd_fmt;    /* Default command line format */
1326         const char *cmd_env;    /* Command line set via environment */
1327         const char *id;         /* Points to either of ref_{head,commit,blob} */
1329         struct view_ops *ops;   /* View operations */
1331         enum keymap keymap;     /* What keymap does this view have */
1333         char cmd[SIZEOF_STR];   /* Command buffer */
1334         char ref[SIZEOF_REF];   /* Hovered commit reference */
1335         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1337         int height, width;      /* The width and height of the main window */
1338         WINDOW *win;            /* The main window */
1339         WINDOW *title;          /* The title window living below the main window */
1341         /* Navigation */
1342         unsigned long offset;   /* Offset of the window top */
1343         unsigned long lineno;   /* Current line number */
1345         /* Searching */
1346         char grep[SIZEOF_STR];  /* Search string */
1347         regex_t *regex;         /* Pre-compiled regex */
1349         /* If non-NULL, points to the view that opened this view. If this view
1350          * is closed tig will switch back to the parent view. */
1351         struct view *parent;
1353         /* Buffering */
1354         unsigned long lines;    /* Total number of lines */
1355         struct line *line;      /* Line index */
1356         unsigned long line_size;/* Total number of allocated lines */
1357         unsigned int digits;    /* Number of digits in the lines member. */
1359         /* Loading */
1360         FILE *pipe;
1361         time_t start_time;
1362 };
1364 struct view_ops {
1365         /* What type of content being displayed. Used in the title bar. */
1366         const char *type;
1367         /* Open and reads in all view content. */
1368         bool (*open)(struct view *view);
1369         /* Read one line; updates view->line. */
1370         bool (*read)(struct view *view, char *data);
1371         /* Draw one line; @lineno must be < view->height. */
1372         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1373         /* Depending on view handle a special requests. */
1374         enum request (*request)(struct view *view, enum request request, struct line *line);
1375         /* Search for regex in a line. */
1376         bool (*grep)(struct view *view, struct line *line);
1377         /* Select line */
1378         void (*select)(struct view *view, struct line *line);
1379 };
1381 static struct view_ops pager_ops;
1382 static struct view_ops main_ops;
1383 static struct view_ops tree_ops;
1384 static struct view_ops blob_ops;
1385 static struct view_ops help_ops;
1386 static struct view_ops status_ops;
1387 static struct view_ops stage_ops;
1389 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1390         { name, cmd, #env, ref, ops, map}
1392 #define VIEW_(id, name, ops, ref) \
1393         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1396 static struct view views[] = {
1397         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1398         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1399         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1400         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1401         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1402         VIEW_(HELP,   "help",   &help_ops,   ""),
1403         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1404         VIEW_(STATUS, "status", &status_ops, ""),
1405         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1406 };
1408 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1410 #define foreach_view(view, i) \
1411         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1413 #define view_is_displayed(view) \
1414         (view == display[0] || view == display[1])
1416 static bool
1417 draw_view_line(struct view *view, unsigned int lineno)
1419         struct line *line;
1420         bool selected = (view->offset + lineno == view->lineno);
1421         bool draw_ok;
1423         assert(view_is_displayed(view));
1425         if (view->offset + lineno >= view->lines)
1426                 return FALSE;
1428         line = &view->line[view->offset + lineno];
1430         if (selected) {
1431                 line->selected = TRUE;
1432                 view->ops->select(view, line);
1433         } else if (line->selected) {
1434                 line->selected = FALSE;
1435                 wmove(view->win, lineno, 0);
1436                 wclrtoeol(view->win);
1437         }
1439         scrollok(view->win, FALSE);
1440         draw_ok = view->ops->draw(view, line, lineno, selected);
1441         scrollok(view->win, TRUE);
1443         return draw_ok;
1446 static void
1447 redraw_view_from(struct view *view, int lineno)
1449         assert(0 <= lineno && lineno < view->height);
1451         for (; lineno < view->height; lineno++) {
1452                 if (!draw_view_line(view, lineno))
1453                         break;
1454         }
1456         redrawwin(view->win);
1457         if (input_mode)
1458                 wnoutrefresh(view->win);
1459         else
1460                 wrefresh(view->win);
1463 static void
1464 redraw_view(struct view *view)
1466         wclear(view->win);
1467         redraw_view_from(view, 0);
1471 static void
1472 update_view_title(struct view *view)
1474         char buf[SIZEOF_STR];
1475         char state[SIZEOF_STR];
1476         size_t bufpos = 0, statelen = 0;
1478         assert(view_is_displayed(view));
1480         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1481                 unsigned int view_lines = view->offset + view->height;
1482                 unsigned int lines = view->lines
1483                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1484                                    : 0;
1486                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1487                                    view->ops->type,
1488                                    view->lineno + 1,
1489                                    view->lines,
1490                                    lines);
1492                 if (view->pipe) {
1493                         time_t secs = time(NULL) - view->start_time;
1495                         /* Three git seconds are a long time ... */
1496                         if (secs > 2)
1497                                 string_format_from(state, &statelen, " %lds", secs);
1498                 }
1499         }
1501         string_format_from(buf, &bufpos, "[%s]", view->name);
1502         if (*view->ref && bufpos < view->width) {
1503                 size_t refsize = strlen(view->ref);
1504                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1506                 if (minsize < view->width)
1507                         refsize = view->width - minsize + 7;
1508                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1509         }
1511         if (statelen && bufpos < view->width) {
1512                 string_format_from(buf, &bufpos, " %s", state);
1513         }
1515         if (view == display[current_view])
1516                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1517         else
1518                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1520         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1521         wclrtoeol(view->title);
1522         wmove(view->title, 0, view->width - 1);
1524         if (input_mode)
1525                 wnoutrefresh(view->title);
1526         else
1527                 wrefresh(view->title);
1530 static void
1531 resize_display(void)
1533         int offset, i;
1534         struct view *base = display[0];
1535         struct view *view = display[1] ? display[1] : display[0];
1537         /* Setup window dimensions */
1539         getmaxyx(stdscr, base->height, base->width);
1541         /* Make room for the status window. */
1542         base->height -= 1;
1544         if (view != base) {
1545                 /* Horizontal split. */
1546                 view->width   = base->width;
1547                 view->height  = SCALE_SPLIT_VIEW(base->height);
1548                 base->height -= view->height;
1550                 /* Make room for the title bar. */
1551                 view->height -= 1;
1552         }
1554         /* Make room for the title bar. */
1555         base->height -= 1;
1557         offset = 0;
1559         foreach_displayed_view (view, i) {
1560                 if (!view->win) {
1561                         view->win = newwin(view->height, 0, offset, 0);
1562                         if (!view->win)
1563                                 die("Failed to create %s view", view->name);
1565                         scrollok(view->win, TRUE);
1567                         view->title = newwin(1, 0, offset + view->height, 0);
1568                         if (!view->title)
1569                                 die("Failed to create title window");
1571                 } else {
1572                         wresize(view->win, view->height, view->width);
1573                         mvwin(view->win,   offset, 0);
1574                         mvwin(view->title, offset + view->height, 0);
1575                 }
1577                 offset += view->height + 1;
1578         }
1581 static void
1582 redraw_display(void)
1584         struct view *view;
1585         int i;
1587         foreach_displayed_view (view, i) {
1588                 redraw_view(view);
1589                 update_view_title(view);
1590         }
1593 static void
1594 update_display_cursor(struct view *view)
1596         /* Move the cursor to the right-most column of the cursor line.
1597          *
1598          * XXX: This could turn out to be a bit expensive, but it ensures that
1599          * the cursor does not jump around. */
1600         if (view->lines) {
1601                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1602                 wrefresh(view->win);
1603         }
1606 /*
1607  * Navigation
1608  */
1610 /* Scrolling backend */
1611 static void
1612 do_scroll_view(struct view *view, int lines)
1614         bool redraw_current_line = FALSE;
1616         /* The rendering expects the new offset. */
1617         view->offset += lines;
1619         assert(0 <= view->offset && view->offset < view->lines);
1620         assert(lines);
1622         /* Move current line into the view. */
1623         if (view->lineno < view->offset) {
1624                 view->lineno = view->offset;
1625                 redraw_current_line = TRUE;
1626         } else if (view->lineno >= view->offset + view->height) {
1627                 view->lineno = view->offset + view->height - 1;
1628                 redraw_current_line = TRUE;
1629         }
1631         assert(view->offset <= view->lineno && view->lineno < view->lines);
1633         /* Redraw the whole screen if scrolling is pointless. */
1634         if (view->height < ABS(lines)) {
1635                 redraw_view(view);
1637         } else {
1638                 int line = lines > 0 ? view->height - lines : 0;
1639                 int end = line + ABS(lines);
1641                 wscrl(view->win, lines);
1643                 for (; line < end; line++) {
1644                         if (!draw_view_line(view, line))
1645                                 break;
1646                 }
1648                 if (redraw_current_line)
1649                         draw_view_line(view, view->lineno - view->offset);
1650         }
1652         redrawwin(view->win);
1653         wrefresh(view->win);
1654         report("");
1657 /* Scroll frontend */
1658 static void
1659 scroll_view(struct view *view, enum request request)
1661         int lines = 1;
1663         assert(view_is_displayed(view));
1665         switch (request) {
1666         case REQ_SCROLL_PAGE_DOWN:
1667                 lines = view->height;
1668         case REQ_SCROLL_LINE_DOWN:
1669                 if (view->offset + lines > view->lines)
1670                         lines = view->lines - view->offset;
1672                 if (lines == 0 || view->offset + view->height >= view->lines) {
1673                         report("Cannot scroll beyond the last line");
1674                         return;
1675                 }
1676                 break;
1678         case REQ_SCROLL_PAGE_UP:
1679                 lines = view->height;
1680         case REQ_SCROLL_LINE_UP:
1681                 if (lines > view->offset)
1682                         lines = view->offset;
1684                 if (lines == 0) {
1685                         report("Cannot scroll beyond the first line");
1686                         return;
1687                 }
1689                 lines = -lines;
1690                 break;
1692         default:
1693                 die("request %d not handled in switch", request);
1694         }
1696         do_scroll_view(view, lines);
1699 /* Cursor moving */
1700 static void
1701 move_view(struct view *view, enum request request)
1703         int scroll_steps = 0;
1704         int steps;
1706         switch (request) {
1707         case REQ_MOVE_FIRST_LINE:
1708                 steps = -view->lineno;
1709                 break;
1711         case REQ_MOVE_LAST_LINE:
1712                 steps = view->lines - view->lineno - 1;
1713                 break;
1715         case REQ_MOVE_PAGE_UP:
1716                 steps = view->height > view->lineno
1717                       ? -view->lineno : -view->height;
1718                 break;
1720         case REQ_MOVE_PAGE_DOWN:
1721                 steps = view->lineno + view->height >= view->lines
1722                       ? view->lines - view->lineno - 1 : view->height;
1723                 break;
1725         case REQ_MOVE_UP:
1726                 steps = -1;
1727                 break;
1729         case REQ_MOVE_DOWN:
1730                 steps = 1;
1731                 break;
1733         default:
1734                 die("request %d not handled in switch", request);
1735         }
1737         if (steps <= 0 && view->lineno == 0) {
1738                 report("Cannot move beyond the first line");
1739                 return;
1741         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1742                 report("Cannot move beyond the last line");
1743                 return;
1744         }
1746         /* Move the current line */
1747         view->lineno += steps;
1748         assert(0 <= view->lineno && view->lineno < view->lines);
1750         /* Check whether the view needs to be scrolled */
1751         if (view->lineno < view->offset ||
1752             view->lineno >= view->offset + view->height) {
1753                 scroll_steps = steps;
1754                 if (steps < 0 && -steps > view->offset) {
1755                         scroll_steps = -view->offset;
1757                 } else if (steps > 0) {
1758                         if (view->lineno == view->lines - 1 &&
1759                             view->lines > view->height) {
1760                                 scroll_steps = view->lines - view->offset - 1;
1761                                 if (scroll_steps >= view->height)
1762                                         scroll_steps -= view->height - 1;
1763                         }
1764                 }
1765         }
1767         if (!view_is_displayed(view)) {
1768                 view->offset += scroll_steps;
1769                 assert(0 <= view->offset && view->offset < view->lines);
1770                 view->ops->select(view, &view->line[view->lineno]);
1771                 return;
1772         }
1774         /* Repaint the old "current" line if we be scrolling */
1775         if (ABS(steps) < view->height)
1776                 draw_view_line(view, view->lineno - steps - view->offset);
1778         if (scroll_steps) {
1779                 do_scroll_view(view, scroll_steps);
1780                 return;
1781         }
1783         /* Draw the current line */
1784         draw_view_line(view, view->lineno - view->offset);
1786         redrawwin(view->win);
1787         wrefresh(view->win);
1788         report("");
1792 /*
1793  * Searching
1794  */
1796 static void search_view(struct view *view, enum request request);
1798 static bool
1799 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1801         assert(view_is_displayed(view));
1803         if (!view->ops->grep(view, line))
1804                 return FALSE;
1806         if (lineno - view->offset >= view->height) {
1807                 view->offset = lineno;
1808                 view->lineno = lineno;
1809                 redraw_view(view);
1811         } else {
1812                 unsigned long old_lineno = view->lineno - view->offset;
1814                 view->lineno = lineno;
1815                 draw_view_line(view, old_lineno);
1817                 draw_view_line(view, view->lineno - view->offset);
1818                 redrawwin(view->win);
1819                 wrefresh(view->win);
1820         }
1822         report("Line %ld matches '%s'", lineno + 1, view->grep);
1823         return TRUE;
1826 static void
1827 find_next(struct view *view, enum request request)
1829         unsigned long lineno = view->lineno;
1830         int direction;
1832         if (!*view->grep) {
1833                 if (!*opt_search)
1834                         report("No previous search");
1835                 else
1836                         search_view(view, request);
1837                 return;
1838         }
1840         switch (request) {
1841         case REQ_SEARCH:
1842         case REQ_FIND_NEXT:
1843                 direction = 1;
1844                 break;
1846         case REQ_SEARCH_BACK:
1847         case REQ_FIND_PREV:
1848                 direction = -1;
1849                 break;
1851         default:
1852                 return;
1853         }
1855         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1856                 lineno += direction;
1858         /* Note, lineno is unsigned long so will wrap around in which case it
1859          * will become bigger than view->lines. */
1860         for (; lineno < view->lines; lineno += direction) {
1861                 struct line *line = &view->line[lineno];
1863                 if (find_next_line(view, lineno, line))
1864                         return;
1865         }
1867         report("No match found for '%s'", view->grep);
1870 static void
1871 search_view(struct view *view, enum request request)
1873         int regex_err;
1875         if (view->regex) {
1876                 regfree(view->regex);
1877                 *view->grep = 0;
1878         } else {
1879                 view->regex = calloc(1, sizeof(*view->regex));
1880                 if (!view->regex)
1881                         return;
1882         }
1884         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1885         if (regex_err != 0) {
1886                 char buf[SIZEOF_STR] = "unknown error";
1888                 regerror(regex_err, view->regex, buf, sizeof(buf));
1889                 report("Search failed: %s", buf);
1890                 return;
1891         }
1893         string_copy(view->grep, opt_search);
1895         find_next(view, request);
1898 /*
1899  * Incremental updating
1900  */
1902 static void
1903 end_update(struct view *view)
1905         if (!view->pipe)
1906                 return;
1907         set_nonblocking_input(FALSE);
1908         if (view->pipe == stdin)
1909                 fclose(view->pipe);
1910         else
1911                 pclose(view->pipe);
1912         view->pipe = NULL;
1915 static bool
1916 begin_update(struct view *view)
1918         if (view->pipe)
1919                 end_update(view);
1921         if (opt_cmd[0]) {
1922                 string_copy(view->cmd, opt_cmd);
1923                 opt_cmd[0] = 0;
1924                 /* When running random commands, initially show the
1925                  * command in the title. However, it maybe later be
1926                  * overwritten if a commit line is selected. */
1927                 if (view == VIEW(REQ_VIEW_PAGER))
1928                         string_copy(view->ref, view->cmd);
1929                 else
1930                         view->ref[0] = 0;
1932         } else if (view == VIEW(REQ_VIEW_TREE)) {
1933                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1934                 char path[SIZEOF_STR];
1936                 if (strcmp(view->vid, view->id))
1937                         opt_path[0] = path[0] = 0;
1938                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1939                         return FALSE;
1941                 if (!string_format(view->cmd, format, view->id, path))
1942                         return FALSE;
1944         } else {
1945                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1946                 const char *id = view->id;
1948                 if (!string_format(view->cmd, format, id, id, id, id, id))
1949                         return FALSE;
1951                 /* Put the current ref_* value to the view title ref
1952                  * member. This is needed by the blob view. Most other
1953                  * views sets it automatically after loading because the
1954                  * first line is a commit line. */
1955                 string_copy_rev(view->ref, view->id);
1956         }
1958         /* Special case for the pager view. */
1959         if (opt_pipe) {
1960                 view->pipe = opt_pipe;
1961                 opt_pipe = NULL;
1962         } else {
1963                 view->pipe = popen(view->cmd, "r");
1964         }
1966         if (!view->pipe)
1967                 return FALSE;
1969         set_nonblocking_input(TRUE);
1971         view->offset = 0;
1972         view->lines  = 0;
1973         view->lineno = 0;
1974         string_copy_rev(view->vid, view->id);
1976         if (view->line) {
1977                 int i;
1979                 for (i = 0; i < view->lines; i++)
1980                         if (view->line[i].data)
1981                                 free(view->line[i].data);
1983                 free(view->line);
1984                 view->line = NULL;
1985         }
1987         view->start_time = time(NULL);
1989         return TRUE;
1992 static struct line *
1993 realloc_lines(struct view *view, size_t line_size)
1995         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1997         if (!tmp)
1998                 return NULL;
2000         view->line = tmp;
2001         view->line_size = line_size;
2002         return view->line;
2005 static bool
2006 update_view(struct view *view)
2008         char in_buffer[BUFSIZ];
2009         char out_buffer[BUFSIZ * 2];
2010         char *line;
2011         /* The number of lines to read. If too low it will cause too much
2012          * redrawing (and possible flickering), if too high responsiveness
2013          * will suffer. */
2014         unsigned long lines = view->height;
2015         int redraw_from = -1;
2017         if (!view->pipe)
2018                 return TRUE;
2020         /* Only redraw if lines are visible. */
2021         if (view->offset + view->height >= view->lines)
2022                 redraw_from = view->lines - view->offset;
2024         /* FIXME: This is probably not perfect for backgrounded views. */
2025         if (!realloc_lines(view, view->lines + lines))
2026                 goto alloc_error;
2028         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2029                 size_t linelen = strlen(line);
2031                 if (linelen)
2032                         line[linelen - 1] = 0;
2034                 if (opt_iconv != ICONV_NONE) {
2035                         ICONV_CONST char *inbuf = line;
2036                         size_t inlen = linelen;
2038                         char *outbuf = out_buffer;
2039                         size_t outlen = sizeof(out_buffer);
2041                         size_t ret;
2043                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2044                         if (ret != (size_t) -1) {
2045                                 line = out_buffer;
2046                                 linelen = strlen(out_buffer);
2047                         }
2048                 }
2050                 if (!view->ops->read(view, line))
2051                         goto alloc_error;
2053                 if (lines-- == 1)
2054                         break;
2055         }
2057         {
2058                 int digits;
2060                 lines = view->lines;
2061                 for (digits = 0; lines; digits++)
2062                         lines /= 10;
2064                 /* Keep the displayed view in sync with line number scaling. */
2065                 if (digits != view->digits) {
2066                         view->digits = digits;
2067                         redraw_from = 0;
2068                 }
2069         }
2071         if (!view_is_displayed(view))
2072                 goto check_pipe;
2074         if (view == VIEW(REQ_VIEW_TREE)) {
2075                 /* Clear the view and redraw everything since the tree sorting
2076                  * might have rearranged things. */
2077                 redraw_view(view);
2079         } else if (redraw_from >= 0) {
2080                 /* If this is an incremental update, redraw the previous line
2081                  * since for commits some members could have changed when
2082                  * loading the main view. */
2083                 if (redraw_from > 0)
2084                         redraw_from--;
2086                 /* Since revision graph visualization requires knowledge
2087                  * about the parent commit, it causes a further one-off
2088                  * needed to be redrawn for incremental updates. */
2089                 if (redraw_from > 0 && opt_rev_graph)
2090                         redraw_from--;
2092                 /* Incrementally draw avoids flickering. */
2093                 redraw_view_from(view, redraw_from);
2094         }
2096         /* Update the title _after_ the redraw so that if the redraw picks up a
2097          * commit reference in view->ref it'll be available here. */
2098         update_view_title(view);
2100 check_pipe:
2101         if (ferror(view->pipe)) {
2102                 report("Failed to read: %s", strerror(errno));
2103                 goto end;
2105         } else if (feof(view->pipe)) {
2106                 report("");
2107                 goto end;
2108         }
2110         return TRUE;
2112 alloc_error:
2113         report("Allocation failure");
2115 end:
2116         view->ops->read(view, NULL);
2117         end_update(view);
2118         return FALSE;
2121 static struct line *
2122 add_line_data(struct view *view, void *data, enum line_type type)
2124         struct line *line = &view->line[view->lines++];
2126         memset(line, 0, sizeof(*line));
2127         line->type = type;
2128         line->data = data;
2130         return line;
2133 static struct line *
2134 add_line_text(struct view *view, char *data, enum line_type type)
2136         if (data)
2137                 data = strdup(data);
2139         return data ? add_line_data(view, data, type) : NULL;
2143 /*
2144  * View opening
2145  */
2147 enum open_flags {
2148         OPEN_DEFAULT = 0,       /* Use default view switching. */
2149         OPEN_SPLIT = 1,         /* Split current view. */
2150         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2151         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2152 };
2154 static void
2155 open_view(struct view *prev, enum request request, enum open_flags flags)
2157         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2158         bool split = !!(flags & OPEN_SPLIT);
2159         bool reload = !!(flags & OPEN_RELOAD);
2160         struct view *view = VIEW(request);
2161         int nviews = displayed_views();
2162         struct view *base_view = display[0];
2164         if (view == prev && nviews == 1 && !reload) {
2165                 report("Already in %s view", view->name);
2166                 return;
2167         }
2169         if (view->ops->open) {
2170                 if (!view->ops->open(view)) {
2171                         report("Failed to load %s view", view->name);
2172                         return;
2173                 }
2175         } else if ((reload || strcmp(view->vid, view->id)) &&
2176                    !begin_update(view)) {
2177                 report("Failed to load %s view", view->name);
2178                 return;
2179         }
2181         if (split) {
2182                 display[1] = view;
2183                 if (!backgrounded)
2184                         current_view = 1;
2185         } else {
2186                 /* Maximize the current view. */
2187                 memset(display, 0, sizeof(display));
2188                 current_view = 0;
2189                 display[current_view] = view;
2190         }
2192         /* Resize the view when switching between split- and full-screen,
2193          * or when switching between two different full-screen views. */
2194         if (nviews != displayed_views() ||
2195             (nviews == 1 && base_view != display[0]))
2196                 resize_display();
2198         if (split && prev->lineno - prev->offset >= prev->height) {
2199                 /* Take the title line into account. */
2200                 int lines = prev->lineno - prev->offset - prev->height + 1;
2202                 /* Scroll the view that was split if the current line is
2203                  * outside the new limited view. */
2204                 do_scroll_view(prev, lines);
2205         }
2207         if (prev && view != prev) {
2208                 if (split && !backgrounded) {
2209                         /* "Blur" the previous view. */
2210                         update_view_title(prev);
2211                 }
2213                 view->parent = prev;
2214         }
2216         if (view->pipe && view->lines == 0) {
2217                 /* Clear the old view and let the incremental updating refill
2218                  * the screen. */
2219                 wclear(view->win);
2220                 report("");
2221         } else {
2222                 redraw_view(view);
2223                 report("");
2224         }
2226         /* If the view is backgrounded the above calls to report()
2227          * won't redraw the view title. */
2228         if (backgrounded)
2229                 update_view_title(view);
2232 static void
2233 open_external_viewer(const char *cmd)
2235         def_prog_mode();           /* save current tty modes */
2236         endwin();                  /* restore original tty modes */
2237         system(cmd);
2238         fprintf(stderr, "Press Enter to continue");
2239         getc(stdin);
2240         reset_prog_mode();
2241         redraw_display();
2244 static void
2245 open_mergetool(const char *file)
2247         char cmd[SIZEOF_STR];
2248         char file_sq[SIZEOF_STR];
2250         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2251             string_format(cmd, "git mergetool %s", file_sq)) {
2252                 open_external_viewer(cmd);
2253         }
2256 static void
2257 open_editor(bool from_root, const char *file)
2259         char cmd[SIZEOF_STR];
2260         char file_sq[SIZEOF_STR];
2261         char *editor;
2262         char *prefix = from_root ? opt_cdup : "";
2264         editor = getenv("GIT_EDITOR");
2265         if (!editor && *opt_editor)
2266                 editor = opt_editor;
2267         if (!editor)
2268                 editor = getenv("VISUAL");
2269         if (!editor)
2270                 editor = getenv("EDITOR");
2271         if (!editor)
2272                 editor = "vi";
2274         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2275             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2276                 open_external_viewer(cmd);
2277         }
2280 static void
2281 open_run_request(enum request request)
2283         struct run_request *req = get_run_request(request);
2284         char buf[SIZEOF_STR * 2];
2285         size_t bufpos;
2286         char *cmd;
2288         if (!req) {
2289                 report("Unknown run request");
2290                 return;
2291         }
2293         bufpos = 0;
2294         cmd = req->cmd;
2296         while (cmd) {
2297                 char *next = strstr(cmd, "%(");
2298                 int len = next - cmd;
2299                 char *value;
2301                 if (!next) {
2302                         len = strlen(cmd);
2303                         value = "";
2305                 } else if (!strncmp(next, "%(head)", 7)) {
2306                         value = ref_head;
2308                 } else if (!strncmp(next, "%(commit)", 9)) {
2309                         value = ref_commit;
2311                 } else if (!strncmp(next, "%(blob)", 7)) {
2312                         value = ref_blob;
2314                 } else {
2315                         report("Unknown replacement in run request: `%s`", req->cmd);
2316                         return;
2317                 }
2319                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2320                         return;
2322                 if (next)
2323                         next = strchr(next, ')') + 1;
2324                 cmd = next;
2325         }
2327         open_external_viewer(buf);
2330 /*
2331  * User request switch noodle
2332  */
2334 static int
2335 view_driver(struct view *view, enum request request)
2337         int i;
2339         if (request == REQ_NONE) {
2340                 doupdate();
2341                 return TRUE;
2342         }
2344         if (request > REQ_NONE) {
2345                 open_run_request(request);
2346                 return TRUE;
2347         }
2349         if (view && view->lines) {
2350                 request = view->ops->request(view, request, &view->line[view->lineno]);
2351                 if (request == REQ_NONE)
2352                         return TRUE;
2353         }
2355         switch (request) {
2356         case REQ_MOVE_UP:
2357         case REQ_MOVE_DOWN:
2358         case REQ_MOVE_PAGE_UP:
2359         case REQ_MOVE_PAGE_DOWN:
2360         case REQ_MOVE_FIRST_LINE:
2361         case REQ_MOVE_LAST_LINE:
2362                 move_view(view, request);
2363                 break;
2365         case REQ_SCROLL_LINE_DOWN:
2366         case REQ_SCROLL_LINE_UP:
2367         case REQ_SCROLL_PAGE_DOWN:
2368         case REQ_SCROLL_PAGE_UP:
2369                 scroll_view(view, request);
2370                 break;
2372         case REQ_VIEW_BLOB:
2373                 if (!ref_blob[0]) {
2374                         report("No file chosen, press %s to open tree view",
2375                                get_key(REQ_VIEW_TREE));
2376                         break;
2377                 }
2378                 open_view(view, request, OPEN_DEFAULT);
2379                 break;
2381         case REQ_VIEW_PAGER:
2382                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2383                         report("No pager content, press %s to run command from prompt",
2384                                get_key(REQ_PROMPT));
2385                         break;
2386                 }
2387                 open_view(view, request, OPEN_DEFAULT);
2388                 break;
2390         case REQ_VIEW_STAGE:
2391                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2392                         report("No stage content, press %s to open the status view and choose file",
2393                                get_key(REQ_VIEW_STATUS));
2394                         break;
2395                 }
2396                 open_view(view, request, OPEN_DEFAULT);
2397                 break;
2399         case REQ_VIEW_STATUS:
2400                 if (opt_is_inside_work_tree == FALSE) {
2401                         report("The status view requires a working tree");
2402                         break;
2403                 }
2404                 open_view(view, request, OPEN_DEFAULT);
2405                 break;
2407         case REQ_VIEW_MAIN:
2408         case REQ_VIEW_DIFF:
2409         case REQ_VIEW_LOG:
2410         case REQ_VIEW_TREE:
2411         case REQ_VIEW_HELP:
2412                 open_view(view, request, OPEN_DEFAULT);
2413                 break;
2415         case REQ_NEXT:
2416         case REQ_PREVIOUS:
2417                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2419                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2420                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2421                    (view == VIEW(REQ_VIEW_STAGE) &&
2422                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2423                    (view == VIEW(REQ_VIEW_BLOB) &&
2424                      view->parent == VIEW(REQ_VIEW_TREE))) {
2425                         int line;
2427                         view = view->parent;
2428                         line = view->lineno;
2429                         move_view(view, request);
2430                         if (view_is_displayed(view))
2431                                 update_view_title(view);
2432                         if (line != view->lineno)
2433                                 view->ops->request(view, REQ_ENTER,
2434                                                    &view->line[view->lineno]);
2436                 } else {
2437                         move_view(view, request);
2438                 }
2439                 break;
2441         case REQ_VIEW_NEXT:
2442         {
2443                 int nviews = displayed_views();
2444                 int next_view = (current_view + 1) % nviews;
2446                 if (next_view == current_view) {
2447                         report("Only one view is displayed");
2448                         break;
2449                 }
2451                 current_view = next_view;
2452                 /* Blur out the title of the previous view. */
2453                 update_view_title(view);
2454                 report("");
2455                 break;
2456         }
2457         case REQ_REFRESH:
2458                 report("Refreshing is not yet supported for the %s view", view->name);
2459                 break;
2461         case REQ_TOGGLE_LINENO:
2462                 opt_line_number = !opt_line_number;
2463                 redraw_display();
2464                 break;
2466         case REQ_TOGGLE_REV_GRAPH:
2467                 opt_rev_graph = !opt_rev_graph;
2468                 redraw_display();
2469                 break;
2471         case REQ_PROMPT:
2472                 /* Always reload^Wrerun commands from the prompt. */
2473                 open_view(view, opt_request, OPEN_RELOAD);
2474                 break;
2476         case REQ_SEARCH:
2477         case REQ_SEARCH_BACK:
2478                 search_view(view, request);
2479                 break;
2481         case REQ_FIND_NEXT:
2482         case REQ_FIND_PREV:
2483                 find_next(view, request);
2484                 break;
2486         case REQ_STOP_LOADING:
2487                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2488                         view = &views[i];
2489                         if (view->pipe)
2490                                 report("Stopped loading the %s view", view->name),
2491                         end_update(view);
2492                 }
2493                 break;
2495         case REQ_SHOW_VERSION:
2496                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2497                 return TRUE;
2499         case REQ_SCREEN_RESIZE:
2500                 resize_display();
2501                 /* Fall-through */
2502         case REQ_SCREEN_REDRAW:
2503                 redraw_display();
2504                 break;
2506         case REQ_EDIT:
2507                 report("Nothing to edit");
2508                 break;
2511         case REQ_ENTER:
2512                 report("Nothing to enter");
2513                 break;
2516         case REQ_VIEW_CLOSE:
2517                 /* XXX: Mark closed views by letting view->parent point to the
2518                  * view itself. Parents to closed view should never be
2519                  * followed. */
2520                 if (view->parent &&
2521                     view->parent->parent != view->parent) {
2522                         memset(display, 0, sizeof(display));
2523                         current_view = 0;
2524                         display[current_view] = view->parent;
2525                         view->parent = view;
2526                         resize_display();
2527                         redraw_display();
2528                         break;
2529                 }
2530                 /* Fall-through */
2531         case REQ_QUIT:
2532                 return FALSE;
2534         default:
2535                 /* An unknown key will show most commonly used commands. */
2536                 report("Unknown key, press 'h' for help");
2537                 return TRUE;
2538         }
2540         return TRUE;
2544 /*
2545  * Pager backend
2546  */
2548 static bool
2549 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2551         char *text = line->data;
2552         enum line_type type = line->type;
2553         int textlen = strlen(text);
2554         int attr;
2556         wmove(view->win, lineno, 0);
2558         if (selected) {
2559                 type = LINE_CURSOR;
2560                 wchgat(view->win, -1, 0, type, NULL);
2561         }
2563         attr = get_line_attr(type);
2564         wattrset(view->win, attr);
2566         if (opt_line_number || opt_tab_size < TABSIZE) {
2567                 static char spaces[] = "                    ";
2568                 int col_offset = 0, col = 0;
2570                 if (opt_line_number) {
2571                         unsigned long real_lineno = view->offset + lineno + 1;
2573                         if (real_lineno == 1 ||
2574                             (real_lineno % opt_num_interval) == 0) {
2575                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2577                         } else {
2578                                 waddnstr(view->win, spaces,
2579                                          MIN(view->digits, STRING_SIZE(spaces)));
2580                         }
2581                         waddstr(view->win, ": ");
2582                         col_offset = view->digits + 2;
2583                 }
2585                 while (text && col_offset + col < view->width) {
2586                         int cols_max = view->width - col_offset - col;
2587                         char *pos = text;
2588                         int cols;
2590                         if (*text == '\t') {
2591                                 text++;
2592                                 assert(sizeof(spaces) > TABSIZE);
2593                                 pos = spaces;
2594                                 cols = opt_tab_size - (col % opt_tab_size);
2596                         } else {
2597                                 text = strchr(text, '\t');
2598                                 cols = line ? text - pos : strlen(pos);
2599                         }
2601                         waddnstr(view->win, pos, MIN(cols, cols_max));
2602                         col += cols;
2603                 }
2605         } else {
2606                 int col = 0, pos = 0;
2608                 for (; pos < textlen && col < view->width; pos++, col++)
2609                         if (text[pos] == '\t')
2610                                 col += TABSIZE - (col % TABSIZE) - 1;
2612                 waddnstr(view->win, text, pos);
2613         }
2615         return TRUE;
2618 static bool
2619 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2621         char refbuf[SIZEOF_STR];
2622         char *ref = NULL;
2623         FILE *pipe;
2625         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2626                 return TRUE;
2628         pipe = popen(refbuf, "r");
2629         if (!pipe)
2630                 return TRUE;
2632         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2633                 ref = chomp_string(ref);
2634         pclose(pipe);
2636         if (!ref || !*ref)
2637                 return TRUE;
2639         /* This is the only fatal call, since it can "corrupt" the buffer. */
2640         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2641                 return FALSE;
2643         return TRUE;
2646 static void
2647 add_pager_refs(struct view *view, struct line *line)
2649         char buf[SIZEOF_STR];
2650         char *commit_id = line->data + STRING_SIZE("commit ");
2651         struct ref **refs;
2652         size_t bufpos = 0, refpos = 0;
2653         const char *sep = "Refs: ";
2654         bool is_tag = FALSE;
2656         assert(line->type == LINE_COMMIT);
2658         refs = get_refs(commit_id);
2659         if (!refs) {
2660                 if (view == VIEW(REQ_VIEW_DIFF))
2661                         goto try_add_describe_ref;
2662                 return;
2663         }
2665         do {
2666                 struct ref *ref = refs[refpos];
2667                 char *fmt = ref->tag    ? "%s[%s]" :
2668                             ref->remote ? "%s<%s>" : "%s%s";
2670                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2671                         return;
2672                 sep = ", ";
2673                 if (ref->tag)
2674                         is_tag = TRUE;
2675         } while (refs[refpos++]->next);
2677         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2678 try_add_describe_ref:
2679                 /* Add <tag>-g<commit_id> "fake" reference. */
2680                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2681                         return;
2682         }
2684         if (bufpos == 0)
2685                 return;
2687         if (!realloc_lines(view, view->line_size + 1))
2688                 return;
2690         add_line_text(view, buf, LINE_PP_REFS);
2693 static bool
2694 pager_read(struct view *view, char *data)
2696         struct line *line;
2698         if (!data)
2699                 return TRUE;
2701         line = add_line_text(view, data, get_line_type(data));
2702         if (!line)
2703                 return FALSE;
2705         if (line->type == LINE_COMMIT &&
2706             (view == VIEW(REQ_VIEW_DIFF) ||
2707              view == VIEW(REQ_VIEW_LOG)))
2708                 add_pager_refs(view, line);
2710         return TRUE;
2713 static enum request
2714 pager_request(struct view *view, enum request request, struct line *line)
2716         int split = 0;
2718         if (request != REQ_ENTER)
2719                 return request;
2721         if (line->type == LINE_COMMIT &&
2722            (view == VIEW(REQ_VIEW_LOG) ||
2723             view == VIEW(REQ_VIEW_PAGER))) {
2724                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2725                 split = 1;
2726         }
2728         /* Always scroll the view even if it was split. That way
2729          * you can use Enter to scroll through the log view and
2730          * split open each commit diff. */
2731         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2733         /* FIXME: A minor workaround. Scrolling the view will call report("")
2734          * but if we are scrolling a non-current view this won't properly
2735          * update the view title. */
2736         if (split)
2737                 update_view_title(view);
2739         return REQ_NONE;
2742 static bool
2743 pager_grep(struct view *view, struct line *line)
2745         regmatch_t pmatch;
2746         char *text = line->data;
2748         if (!*text)
2749                 return FALSE;
2751         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2752                 return FALSE;
2754         return TRUE;
2757 static void
2758 pager_select(struct view *view, struct line *line)
2760         if (line->type == LINE_COMMIT) {
2761                 char *text = line->data + STRING_SIZE("commit ");
2763                 if (view != VIEW(REQ_VIEW_PAGER))
2764                         string_copy_rev(view->ref, text);
2765                 string_copy_rev(ref_commit, text);
2766         }
2769 static struct view_ops pager_ops = {
2770         "line",
2771         NULL,
2772         pager_read,
2773         pager_draw,
2774         pager_request,
2775         pager_grep,
2776         pager_select,
2777 };
2780 /*
2781  * Help backend
2782  */
2784 static bool
2785 help_open(struct view *view)
2787         char buf[BUFSIZ];
2788         int lines = ARRAY_SIZE(req_info) + 2;
2789         int i;
2791         if (view->lines > 0)
2792                 return TRUE;
2794         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2795                 if (!req_info[i].request)
2796                         lines++;
2798         lines += run_requests + 1;
2800         view->line = calloc(lines, sizeof(*view->line));
2801         if (!view->line)
2802                 return FALSE;
2804         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2806         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2807                 char *key;
2809                 if (req_info[i].request == REQ_NONE)
2810                         continue;
2812                 if (!req_info[i].request) {
2813                         add_line_text(view, "", LINE_DEFAULT);
2814                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2815                         continue;
2816                 }
2818                 key = get_key(req_info[i].request);
2819                 if (!*key)
2820                         key = "(no key defined)";
2822                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2823                         continue;
2825                 add_line_text(view, buf, LINE_DEFAULT);
2826         }
2828         if (run_requests) {
2829                 add_line_text(view, "", LINE_DEFAULT);
2830                 add_line_text(view, "External commands:", LINE_DEFAULT);
2831         }
2833         for (i = 0; i < run_requests; i++) {
2834                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2835                 char *key;
2837                 if (!req)
2838                         continue;
2840                 key = get_key_name(req->key);
2841                 if (!*key)
2842                         key = "(no key defined)";
2844                 if (!string_format(buf, "    %-10s %-14s `%s`",
2845                                    keymap_table[req->keymap].name,
2846                                    key, req->cmd))
2847                         continue;
2849                 add_line_text(view, buf, LINE_DEFAULT);
2850         }
2852         return TRUE;
2855 static struct view_ops help_ops = {
2856         "line",
2857         help_open,
2858         NULL,
2859         pager_draw,
2860         pager_request,
2861         pager_grep,
2862         pager_select,
2863 };
2866 /*
2867  * Tree backend
2868  */
2870 struct tree_stack_entry {
2871         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2872         unsigned long lineno;           /* Line number to restore */
2873         char *name;                     /* Position of name in opt_path */
2874 };
2876 /* The top of the path stack. */
2877 static struct tree_stack_entry *tree_stack = NULL;
2878 unsigned long tree_lineno = 0;
2880 static void
2881 pop_tree_stack_entry(void)
2883         struct tree_stack_entry *entry = tree_stack;
2885         tree_lineno = entry->lineno;
2886         entry->name[0] = 0;
2887         tree_stack = entry->prev;
2888         free(entry);
2891 static void
2892 push_tree_stack_entry(char *name, unsigned long lineno)
2894         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2895         size_t pathlen = strlen(opt_path);
2897         if (!entry)
2898                 return;
2900         entry->prev = tree_stack;
2901         entry->name = opt_path + pathlen;
2902         tree_stack = entry;
2904         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2905                 pop_tree_stack_entry();
2906                 return;
2907         }
2909         /* Move the current line to the first tree entry. */
2910         tree_lineno = 1;
2911         entry->lineno = lineno;
2914 /* Parse output from git-ls-tree(1):
2915  *
2916  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2917  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2918  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2919  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2920  */
2922 #define SIZEOF_TREE_ATTR \
2923         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2925 #define TREE_UP_FORMAT "040000 tree %s\t.."
2927 static int
2928 tree_compare_entry(enum line_type type1, char *name1,
2929                    enum line_type type2, char *name2)
2931         if (type1 != type2) {
2932                 if (type1 == LINE_TREE_DIR)
2933                         return -1;
2934                 return 1;
2935         }
2937         return strcmp(name1, name2);
2940 static bool
2941 tree_read(struct view *view, char *text)
2943         size_t textlen = text ? strlen(text) : 0;
2944         char buf[SIZEOF_STR];
2945         unsigned long pos;
2946         enum line_type type;
2947         bool first_read = view->lines == 0;
2949         if (textlen <= SIZEOF_TREE_ATTR)
2950                 return FALSE;
2952         type = text[STRING_SIZE("100644 ")] == 't'
2953              ? LINE_TREE_DIR : LINE_TREE_FILE;
2955         if (first_read) {
2956                 /* Add path info line */
2957                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2958                     !realloc_lines(view, view->line_size + 1) ||
2959                     !add_line_text(view, buf, LINE_DEFAULT))
2960                         return FALSE;
2962                 /* Insert "link" to parent directory. */
2963                 if (*opt_path) {
2964                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2965                             !realloc_lines(view, view->line_size + 1) ||
2966                             !add_line_text(view, buf, LINE_TREE_DIR))
2967                                 return FALSE;
2968                 }
2969         }
2971         /* Strip the path part ... */
2972         if (*opt_path) {
2973                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2974                 size_t striplen = strlen(opt_path);
2975                 char *path = text + SIZEOF_TREE_ATTR;
2977                 if (pathlen > striplen)
2978                         memmove(path, path + striplen,
2979                                 pathlen - striplen + 1);
2980         }
2982         /* Skip "Directory ..." and ".." line. */
2983         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2984                 struct line *line = &view->line[pos];
2985                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2986                 char *path2 = text + SIZEOF_TREE_ATTR;
2987                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2989                 if (cmp <= 0)
2990                         continue;
2992                 text = strdup(text);
2993                 if (!text)
2994                         return FALSE;
2996                 if (view->lines > pos)
2997                         memmove(&view->line[pos + 1], &view->line[pos],
2998                                 (view->lines - pos) * sizeof(*line));
3000                 line = &view->line[pos];
3001                 line->data = text;
3002                 line->type = type;
3003                 view->lines++;
3004                 return TRUE;
3005         }
3007         if (!add_line_text(view, text, type))
3008                 return FALSE;
3010         if (tree_lineno > view->lineno) {
3011                 view->lineno = tree_lineno;
3012                 tree_lineno = 0;
3013         }
3015         return TRUE;
3018 static enum request
3019 tree_request(struct view *view, enum request request, struct line *line)
3021         enum open_flags flags;
3023         if (request != REQ_ENTER)
3024                 return request;
3026         /* Cleanup the stack if the tree view is at a different tree. */
3027         while (!*opt_path && tree_stack)
3028                 pop_tree_stack_entry();
3030         switch (line->type) {
3031         case LINE_TREE_DIR:
3032                 /* Depending on whether it is a subdir or parent (updir?) link
3033                  * mangle the path buffer. */
3034                 if (line == &view->line[1] && *opt_path) {
3035                         pop_tree_stack_entry();
3037                 } else {
3038                         char *data = line->data;
3039                         char *basename = data + SIZEOF_TREE_ATTR;
3041                         push_tree_stack_entry(basename, view->lineno);
3042                 }
3044                 /* Trees and subtrees share the same ID, so they are not not
3045                  * unique like blobs. */
3046                 flags = OPEN_RELOAD;
3047                 request = REQ_VIEW_TREE;
3048                 break;
3050         case LINE_TREE_FILE:
3051                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3052                 request = REQ_VIEW_BLOB;
3053                 break;
3055         default:
3056                 return TRUE;
3057         }
3059         open_view(view, request, flags);
3060         if (request == REQ_VIEW_TREE) {
3061                 view->lineno = tree_lineno;
3062         }
3064         return REQ_NONE;
3067 static void
3068 tree_select(struct view *view, struct line *line)
3070         char *text = line->data + STRING_SIZE("100644 blob ");
3072         if (line->type == LINE_TREE_FILE) {
3073                 string_copy_rev(ref_blob, text);
3075         } else if (line->type != LINE_TREE_DIR) {
3076                 return;
3077         }
3079         string_copy_rev(view->ref, text);
3082 static struct view_ops tree_ops = {
3083         "file",
3084         NULL,
3085         tree_read,
3086         pager_draw,
3087         tree_request,
3088         pager_grep,
3089         tree_select,
3090 };
3092 static bool
3093 blob_read(struct view *view, char *line)
3095         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3098 static struct view_ops blob_ops = {
3099         "line",
3100         NULL,
3101         blob_read,
3102         pager_draw,
3103         pager_request,
3104         pager_grep,
3105         pager_select,
3106 };
3109 /*
3110  * Status backend
3111  */
3113 struct status {
3114         char status;
3115         struct {
3116                 mode_t mode;
3117                 char rev[SIZEOF_REV];
3118         } old;
3119         struct {
3120                 mode_t mode;
3121                 char rev[SIZEOF_REV];
3122         } new;
3123         char name[SIZEOF_STR];
3124 };
3126 static struct status stage_status;
3127 static enum line_type stage_line_type;
3129 /* Get fields from the diff line:
3130  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3131  */
3132 static inline bool
3133 status_get_diff(struct status *file, char *buf, size_t bufsize)
3135         char *old_mode = buf +  1;
3136         char *new_mode = buf +  8;
3137         char *old_rev  = buf + 15;
3138         char *new_rev  = buf + 56;
3139         char *status   = buf + 97;
3141         if (bufsize != 99 ||
3142             old_mode[-1] != ':' ||
3143             new_mode[-1] != ' ' ||
3144             old_rev[-1]  != ' ' ||
3145             new_rev[-1]  != ' ' ||
3146             status[-1]   != ' ')
3147                 return FALSE;
3149         file->status = *status;
3151         string_copy_rev(file->old.rev, old_rev);
3152         string_copy_rev(file->new.rev, new_rev);
3154         file->old.mode = strtoul(old_mode, NULL, 8);
3155         file->new.mode = strtoul(new_mode, NULL, 8);
3157         file->name[0] = 0;
3159         return TRUE;
3162 static bool
3163 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3165         struct status *file = NULL;
3166         struct status *unmerged = NULL;
3167         char buf[SIZEOF_STR * 4];
3168         size_t bufsize = 0;
3169         FILE *pipe;
3171         pipe = popen(cmd, "r");
3172         if (!pipe)
3173                 return FALSE;
3175         add_line_data(view, NULL, type);
3177         while (!feof(pipe) && !ferror(pipe)) {
3178                 char *sep;
3179                 size_t readsize;
3181                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3182                 if (!readsize)
3183                         break;
3184                 bufsize += readsize;
3186                 /* Process while we have NUL chars. */
3187                 while ((sep = memchr(buf, 0, bufsize))) {
3188                         size_t sepsize = sep - buf + 1;
3190                         if (!file) {
3191                                 if (!realloc_lines(view, view->line_size + 1))
3192                                         goto error_out;
3194                                 file = calloc(1, sizeof(*file));
3195                                 if (!file)
3196                                         goto error_out;
3198                                 add_line_data(view, file, type);
3199                         }
3201                         /* Parse diff info part. */
3202                         if (!diff) {
3203                                 file->status = '?';
3205                         } else if (!file->status) {
3206                                 if (!status_get_diff(file, buf, sepsize))
3207                                         goto error_out;
3209                                 bufsize -= sepsize;
3210                                 memmove(buf, sep + 1, bufsize);
3212                                 sep = memchr(buf, 0, bufsize);
3213                                 if (!sep)
3214                                         break;
3215                                 sepsize = sep - buf + 1;
3217                                 /* Collapse all 'M'odified entries that
3218                                  * follow a associated 'U'nmerged entry.
3219                                  */
3220                                 if (file->status == 'U') {
3221                                         unmerged = file;
3223                                 } else if (unmerged) {
3224                                         int collapse = !strcmp(buf, unmerged->name);
3226                                         unmerged = NULL;
3227                                         if (collapse) {
3228                                                 free(file);
3229                                                 view->lines--;
3230                                                 continue;
3231                                         }
3232                                 }
3233                         }
3235                         /* git-ls-files just delivers a NUL separated
3236                          * list of file names similar to the second half
3237                          * of the git-diff-* output. */
3238                         string_ncopy(file->name, buf, sepsize);
3239                         bufsize -= sepsize;
3240                         memmove(buf, sep + 1, bufsize);
3241                         file = NULL;
3242                 }
3243         }
3245         if (ferror(pipe)) {
3246 error_out:
3247                 pclose(pipe);
3248                 return FALSE;
3249         }
3251         if (!view->line[view->lines - 1].data)
3252                 add_line_data(view, NULL, LINE_STAT_NONE);
3254         pclose(pipe);
3255         return TRUE;
3258 /* Don't show unmerged entries in the staged section. */
3259 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3260 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3261 #define STATUS_LIST_OTHER_CMD \
3262         "git ls-files -z --others --exclude-per-directory=.gitignore"
3264 #define STATUS_DIFF_INDEX_SHOW_CMD \
3265         "git diff-index --root --patch-with-stat --find-copies-harder -B -C --cached HEAD -- %s 2>/dev/null"
3267 #define STATUS_DIFF_FILES_SHOW_CMD \
3268         "git diff-files --root --patch-with-stat --find-copies-harder -B -C -- %s 2>/dev/null"
3270 /* First parse staged info using git-diff-index(1), then parse unstaged
3271  * info using git-diff-files(1), and finally untracked files using
3272  * git-ls-files(1). */
3273 static bool
3274 status_open(struct view *view)
3276         struct stat statbuf;
3277         char exclude[SIZEOF_STR];
3278         char cmd[SIZEOF_STR];
3279         unsigned long prev_lineno = view->lineno;
3280         size_t i;
3282         for (i = 0; i < view->lines; i++)
3283                 free(view->line[i].data);
3284         free(view->line);
3285         view->lines = view->line_size = view->lineno = 0;
3286         view->line = NULL;
3288         if (!realloc_lines(view, view->line_size + 6))
3289                 return FALSE;
3291         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3292                 return FALSE;
3294         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3296         if (stat(exclude, &statbuf) >= 0) {
3297                 size_t cmdsize = strlen(cmd);
3299                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3300                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3301                         return FALSE;
3302         }
3304         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3305             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3306             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3307                 return FALSE;
3309         /* If all went well restore the previous line number to stay in
3310          * the context. */
3311         if (prev_lineno < view->lines)
3312                 view->lineno = prev_lineno;
3313         else
3314                 view->lineno = view->lines - 1;
3316         return TRUE;
3319 static bool
3320 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3322         struct status *status = line->data;
3324         wmove(view->win, lineno, 0);
3326         if (selected) {
3327                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3328                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3330         } else if (!status && line->type != LINE_STAT_NONE) {
3331                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3332                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3334         } else {
3335                 wattrset(view->win, get_line_attr(line->type));
3336         }
3338         if (!status) {
3339                 char *text;
3341                 switch (line->type) {
3342                 case LINE_STAT_STAGED:
3343                         text = "Changes to be committed:";
3344                         break;
3346                 case LINE_STAT_UNSTAGED:
3347                         text = "Changed but not updated:";
3348                         break;
3350                 case LINE_STAT_UNTRACKED:
3351                         text = "Untracked files:";
3352                         break;
3354                 case LINE_STAT_NONE:
3355                         text = "    (no files)";
3356                         break;
3358                 default:
3359                         return FALSE;
3360                 }
3362                 waddstr(view->win, text);
3363                 return TRUE;
3364         }
3366         waddch(view->win, status->status);
3367         if (!selected)
3368                 wattrset(view->win, A_NORMAL);
3369         wmove(view->win, lineno, 4);
3370         waddstr(view->win, status->name);
3372         return TRUE;
3375 static enum request
3376 status_enter(struct view *view, struct line *line)
3378         struct status *status = line->data;
3379         char path[SIZEOF_STR] = "";
3380         char *info;
3381         size_t cmdsize = 0;
3383         if (line->type == LINE_STAT_NONE ||
3384             (!status && line[1].type == LINE_STAT_NONE)) {
3385                 report("No file to diff");
3386                 return REQ_NONE;
3387         }
3389         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3390                 return REQ_QUIT;
3392         if (opt_cdup[0] &&
3393             line->type != LINE_STAT_UNTRACKED &&
3394             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3395                 return REQ_QUIT;
3397         switch (line->type) {
3398         case LINE_STAT_STAGED:
3399                 if (!string_format_from(opt_cmd, &cmdsize,
3400                                         STATUS_DIFF_INDEX_SHOW_CMD, path))
3401                         return REQ_QUIT;
3402                 if (status)
3403                         info = "Staged changes to %s";
3404                 else
3405                         info = "Staged changes";
3406                 break;
3408         case LINE_STAT_UNSTAGED:
3409                 if (!string_format_from(opt_cmd, &cmdsize,
3410                                         STATUS_DIFF_FILES_SHOW_CMD, path))
3411                         return REQ_QUIT;
3412                 if (status)
3413                         info = "Unstaged changes to %s";
3414                 else
3415                         info = "Unstaged changes";
3416                 break;
3418         case LINE_STAT_UNTRACKED:
3419                 if (opt_pipe)
3420                         return REQ_QUIT;
3423                 if (!status) {
3424                         report("No file to show");
3425                         return REQ_NONE;
3426                 }
3428                 opt_pipe = fopen(status->name, "r");
3429                 info = "Untracked file %s";
3430                 break;
3432         default:
3433                 die("line type %d not handled in switch", line->type);
3434         }
3436         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3437         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3438                 if (status) {
3439                         stage_status = *status;
3440                 } else {
3441                         memset(&stage_status, 0, sizeof(stage_status));
3442                 }
3444                 stage_line_type = line->type;
3445                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3446         }
3448         return REQ_NONE;
3452 static bool
3453 status_update_file(struct view *view, struct status *status, enum line_type type)
3455         char cmd[SIZEOF_STR];
3456         char buf[SIZEOF_STR];
3457         size_t cmdsize = 0;
3458         size_t bufsize = 0;
3459         size_t written = 0;
3460         FILE *pipe;
3462         if (opt_cdup[0] &&
3463             type != LINE_STAT_UNTRACKED &&
3464             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3465                 return FALSE;
3467         switch (type) {
3468         case LINE_STAT_STAGED:
3469                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3470                                         status->old.mode,
3471                                         status->old.rev,
3472                                         status->name, 0))
3473                         return FALSE;
3475                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3476                 break;
3478         case LINE_STAT_UNSTAGED:
3479         case LINE_STAT_UNTRACKED:
3480                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3481                         return FALSE;
3483                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3484                 break;
3486         default:
3487                 die("line type %d not handled in switch", type);
3488         }
3490         pipe = popen(cmd, "w");
3491         if (!pipe)
3492                 return FALSE;
3494         while (!ferror(pipe) && written < bufsize) {
3495                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3496         }
3498         pclose(pipe);
3500         if (written != bufsize)
3501                 return FALSE;
3503         return TRUE;
3506 static void
3507 status_update(struct view *view)
3509         struct line *line = &view->line[view->lineno];
3511         assert(view->lines);
3513         if (!line->data) {
3514                 while (++line < view->line + view->lines && line->data) {
3515                         if (!status_update_file(view, line->data, line->type))
3516                                 report("Failed to update file status");
3517                 }
3519                 if (!line[-1].data) {
3520                         report("Nothing to update");
3521                         return;
3522                 }
3524         } else if (!status_update_file(view, line->data, line->type)) {
3525                 report("Failed to update file status");
3526         }
3529 static enum request
3530 status_request(struct view *view, enum request request, struct line *line)
3532         struct status *status = line->data;
3534         switch (request) {
3535         case REQ_STATUS_UPDATE:
3536                 status_update(view);
3537                 break;
3539         case REQ_STATUS_MERGE:
3540                 if (!status || status->status != 'U') {
3541                         report("Merging only possible for files with unmerged status ('U').");
3542                         return REQ_NONE;
3543                 }
3544                 open_mergetool(status->name);
3545                 break;
3547         case REQ_EDIT:
3548                 if (!status)
3549                         return request;
3551                 open_editor(status->status != '?', status->name);
3552                 break;
3554         case REQ_ENTER:
3555                 /* After returning the status view has been split to
3556                  * show the stage view. No further reloading is
3557                  * necessary. */
3558                 status_enter(view, line);
3559                 return REQ_NONE;
3561         case REQ_REFRESH:
3562                 /* Simply reload the view. */
3563                 break;
3565         default:
3566                 return request;
3567         }
3569         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3571         return REQ_NONE;
3574 static void
3575 status_select(struct view *view, struct line *line)
3577         struct status *status = line->data;
3578         char file[SIZEOF_STR] = "all files";
3579         char *text;
3580         char *key;
3582         if (status && !string_format(file, "'%s'", status->name))
3583                 return;
3585         if (!status && line[1].type == LINE_STAT_NONE)
3586                 line++;
3588         switch (line->type) {
3589         case LINE_STAT_STAGED:
3590                 text = "Press %s to unstage %s for commit";
3591                 break;
3593         case LINE_STAT_UNSTAGED:
3594                 text = "Press %s to stage %s for commit";
3595                 break;
3597         case LINE_STAT_UNTRACKED:
3598                 text = "Press %s to stage %s for addition";
3599                 break;
3601         case LINE_STAT_NONE:
3602                 text = "Nothing to update";
3603                 break;
3605         default:
3606                 die("line type %d not handled in switch", line->type);
3607         }
3609         if (status && status->status == 'U') {
3610                 text = "Press %s to resolve conflict in %s";
3611                 key = get_key(REQ_STATUS_MERGE);
3613         } else {
3614                 key = get_key(REQ_STATUS_UPDATE);
3615         }
3617         string_format(view->ref, text, key, file);
3620 static bool
3621 status_grep(struct view *view, struct line *line)
3623         struct status *status = line->data;
3624         enum { S_STATUS, S_NAME, S_END } state;
3625         char buf[2] = "?";
3626         regmatch_t pmatch;
3628         if (!status)
3629                 return FALSE;
3631         for (state = S_STATUS; state < S_END; state++) {
3632                 char *text;
3634                 switch (state) {
3635                 case S_NAME:    text = status->name;    break;
3636                 case S_STATUS:
3637                         buf[0] = status->status;
3638                         text = buf;
3639                         break;
3641                 default:
3642                         return FALSE;
3643                 }
3645                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3646                         return TRUE;
3647         }
3649         return FALSE;
3652 static struct view_ops status_ops = {
3653         "file",
3654         status_open,
3655         NULL,
3656         status_draw,
3657         status_request,
3658         status_grep,
3659         status_select,
3660 };
3663 static bool
3664 stage_diff_line(FILE *pipe, struct line *line)
3666         char *buf = line->data;
3667         size_t bufsize = strlen(buf);
3668         size_t written = 0;
3670         while (!ferror(pipe) && written < bufsize) {
3671                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3672         }
3674         fputc('\n', pipe);
3676         return written == bufsize;
3679 static struct line *
3680 stage_diff_hdr(struct view *view, struct line *line)
3682         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3683         struct line *diff_hdr;
3685         if (line->type == LINE_DIFF_CHUNK)
3686                 diff_hdr = line - 1;
3687         else
3688                 diff_hdr = view->line + 1;
3690         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3691                 if (diff_hdr->type == LINE_DIFF_HEADER)
3692                         return diff_hdr;
3694                 diff_hdr += diff_hdr_dir;
3695         }
3697         return NULL;
3700 static bool
3701 stage_update_chunk(struct view *view, struct line *line)
3703         char cmd[SIZEOF_STR];
3704         size_t cmdsize = 0;
3705         struct line *diff_hdr, *diff_chunk, *diff_end;
3706         FILE *pipe;
3708         diff_hdr = stage_diff_hdr(view, line);
3709         if (!diff_hdr)
3710                 return FALSE;
3712         if (opt_cdup[0] &&
3713             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3714                 return FALSE;
3716         if (!string_format_from(cmd, &cmdsize,
3717                                 "git apply --cached %s - && "
3718                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3719                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3720                 return FALSE;
3722         pipe = popen(cmd, "w");
3723         if (!pipe)
3724                 return FALSE;
3726         diff_end = view->line + view->lines;
3727         if (line->type != LINE_DIFF_CHUNK) {
3728                 diff_chunk = diff_hdr;
3730         } else {
3731                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3732                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3733                             diff_chunk->type == LINE_DIFF_HEADER)
3734                                 diff_end = diff_chunk;
3736                 diff_chunk = line;
3738                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3739                         switch (diff_hdr->type) {
3740                         case LINE_DIFF_HEADER:
3741                         case LINE_DIFF_INDEX:
3742                         case LINE_DIFF_ADD:
3743                         case LINE_DIFF_DEL:
3744                                 break;
3746                         default:
3747                                 diff_hdr++;
3748                                 continue;
3749                         }
3751                         if (!stage_diff_line(pipe, diff_hdr++)) {
3752                                 pclose(pipe);
3753                                 return FALSE;
3754                         }
3755                 }
3756         }
3758         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3759                 diff_chunk++;
3761         pclose(pipe);
3763         if (diff_chunk != diff_end)
3764                 return FALSE;
3766         return TRUE;
3769 static void
3770 stage_update(struct view *view, struct line *line)
3772         if (stage_line_type != LINE_STAT_UNTRACKED &&
3773             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3774                 if (!stage_update_chunk(view, line)) {
3775                         report("Failed to apply chunk");
3776                         return;
3777                 }
3779         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3780                 report("Failed to update file");
3781                 return;
3782         }
3784         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3786         view = VIEW(REQ_VIEW_STATUS);
3787         if (view_is_displayed(view))
3788                 status_enter(view, &view->line[view->lineno]);
3791 static enum request
3792 stage_request(struct view *view, enum request request, struct line *line)
3794         switch (request) {
3795         case REQ_STATUS_UPDATE:
3796                 stage_update(view, line);
3797                 break;
3799         case REQ_EDIT:
3800                 if (!stage_status.name[0])
3801                         return request;
3803                 open_editor(stage_status.status != '?', stage_status.name);
3804                 break;
3806         case REQ_ENTER:
3807                 pager_request(view, request, line);
3808                 break;
3810         default:
3811                 return request;
3812         }
3814         return REQ_NONE;
3817 static struct view_ops stage_ops = {
3818         "line",
3819         NULL,
3820         pager_read,
3821         pager_draw,
3822         stage_request,
3823         pager_grep,
3824         pager_select,
3825 };
3828 /*
3829  * Revision graph
3830  */
3832 struct commit {
3833         char id[SIZEOF_REV];            /* SHA1 ID. */
3834         char title[128];                /* First line of the commit message. */
3835         char author[75];                /* Author of the commit. */
3836         struct tm time;                 /* Date from the author ident. */
3837         struct ref **refs;              /* Repository references. */
3838         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3839         size_t graph_size;              /* The width of the graph array. */
3840 };
3842 /* Size of rev graph with no  "padding" columns */
3843 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3845 struct rev_graph {
3846         struct rev_graph *prev, *next, *parents;
3847         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3848         size_t size;
3849         struct commit *commit;
3850         size_t pos;
3851 };
3853 /* Parents of the commit being visualized. */
3854 static struct rev_graph graph_parents[4];
3856 /* The current stack of revisions on the graph. */
3857 static struct rev_graph graph_stacks[4] = {
3858         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3859         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3860         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3861         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3862 };
3864 static inline bool
3865 graph_parent_is_merge(struct rev_graph *graph)
3867         return graph->parents->size > 1;
3870 static inline void
3871 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3873         struct commit *commit = graph->commit;
3875         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3876                 commit->graph[commit->graph_size++] = symbol;
3879 static void
3880 done_rev_graph(struct rev_graph *graph)
3882         if (graph_parent_is_merge(graph) &&
3883             graph->pos < graph->size - 1 &&
3884             graph->next->size == graph->size + graph->parents->size - 1) {
3885                 size_t i = graph->pos + graph->parents->size - 1;
3887                 graph->commit->graph_size = i * 2;
3888                 while (i < graph->next->size - 1) {
3889                         append_to_rev_graph(graph, ' ');
3890                         append_to_rev_graph(graph, '\\');
3891                         i++;
3892                 }
3893         }
3895         graph->size = graph->pos = 0;
3896         graph->commit = NULL;
3897         memset(graph->parents, 0, sizeof(*graph->parents));
3900 static void
3901 push_rev_graph(struct rev_graph *graph, char *parent)
3903         int i;
3905         /* "Collapse" duplicate parents lines.
3906          *
3907          * FIXME: This needs to also update update the drawn graph but
3908          * for now it just serves as a method for pruning graph lines. */
3909         for (i = 0; i < graph->size; i++)
3910                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3911                         return;
3913         if (graph->size < SIZEOF_REVITEMS) {
3914                 string_copy_rev(graph->rev[graph->size++], parent);
3915         }
3918 static chtype
3919 get_rev_graph_symbol(struct rev_graph *graph)
3921         chtype symbol;
3923         if (graph->parents->size == 0)
3924                 symbol = REVGRAPH_INIT;
3925         else if (graph_parent_is_merge(graph))
3926                 symbol = REVGRAPH_MERGE;
3927         else if (graph->pos >= graph->size)
3928                 symbol = REVGRAPH_BRANCH;
3929         else
3930                 symbol = REVGRAPH_COMMIT;
3932         return symbol;
3935 static void
3936 draw_rev_graph(struct rev_graph *graph)
3938         struct rev_filler {
3939                 chtype separator, line;
3940         };
3941         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3942         static struct rev_filler fillers[] = {
3943                 { ' ',  REVGRAPH_LINE },
3944                 { '`',  '.' },
3945                 { '\'', ' ' },
3946                 { '/',  ' ' },
3947         };
3948         chtype symbol = get_rev_graph_symbol(graph);
3949         struct rev_filler *filler;
3950         size_t i;
3952         filler = &fillers[DEFAULT];
3954         for (i = 0; i < graph->pos; i++) {
3955                 append_to_rev_graph(graph, filler->line);
3956                 if (graph_parent_is_merge(graph->prev) &&
3957                     graph->prev->pos == i)
3958                         filler = &fillers[RSHARP];
3960                 append_to_rev_graph(graph, filler->separator);
3961         }
3963         /* Place the symbol for this revision. */
3964         append_to_rev_graph(graph, symbol);
3966         if (graph->prev->size > graph->size)
3967                 filler = &fillers[RDIAG];
3968         else
3969                 filler = &fillers[DEFAULT];
3971         i++;
3973         for (; i < graph->size; i++) {
3974                 append_to_rev_graph(graph, filler->separator);
3975                 append_to_rev_graph(graph, filler->line);
3976                 if (graph_parent_is_merge(graph->prev) &&
3977                     i < graph->prev->pos + graph->parents->size)
3978                         filler = &fillers[RSHARP];
3979                 if (graph->prev->size > graph->size)
3980                         filler = &fillers[LDIAG];
3981         }
3983         if (graph->prev->size > graph->size) {
3984                 append_to_rev_graph(graph, filler->separator);
3985                 if (filler->line != ' ')
3986                         append_to_rev_graph(graph, filler->line);
3987         }
3990 /* Prepare the next rev graph */
3991 static void
3992 prepare_rev_graph(struct rev_graph *graph)
3994         size_t i;
3996         /* First, traverse all lines of revisions up to the active one. */
3997         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3998                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3999                         break;
4001                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4002         }
4004         /* Interleave the new revision parent(s). */
4005         for (i = 0; i < graph->parents->size; i++)
4006                 push_rev_graph(graph->next, graph->parents->rev[i]);
4008         /* Lastly, put any remaining revisions. */
4009         for (i = graph->pos + 1; i < graph->size; i++)
4010                 push_rev_graph(graph->next, graph->rev[i]);
4013 static void
4014 update_rev_graph(struct rev_graph *graph)
4016         /* If this is the finalizing update ... */
4017         if (graph->commit)
4018                 prepare_rev_graph(graph);
4020         /* Graph visualization needs a one rev look-ahead,
4021          * so the first update doesn't visualize anything. */
4022         if (!graph->prev->commit)
4023                 return;
4025         draw_rev_graph(graph->prev);
4026         done_rev_graph(graph->prev->prev);
4030 /*
4031  * Main view backend
4032  */
4034 static bool
4035 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4037         char buf[DATE_COLS + 1];
4038         struct commit *commit = line->data;
4039         enum line_type type;
4040         int col = 0;
4041         size_t timelen;
4042         size_t authorlen;
4043         int trimmed = 1;
4045         if (!*commit->author)
4046                 return FALSE;
4048         wmove(view->win, lineno, col);
4050         if (selected) {
4051                 type = LINE_CURSOR;
4052                 wattrset(view->win, get_line_attr(type));
4053                 wchgat(view->win, -1, 0, type, NULL);
4055         } else {
4056                 type = LINE_MAIN_COMMIT;
4057                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4058         }
4060         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4061         waddnstr(view->win, buf, timelen);
4062         waddstr(view->win, " ");
4064         col += DATE_COLS;
4065         wmove(view->win, lineno, col);
4066         if (type != LINE_CURSOR)
4067                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4069         if (opt_utf8) {
4070                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4071         } else {
4072                 authorlen = strlen(commit->author);
4073                 if (authorlen > AUTHOR_COLS - 2) {
4074                         authorlen = AUTHOR_COLS - 2;
4075                         trimmed = 1;
4076                 }
4077         }
4079         if (trimmed) {
4080                 waddnstr(view->win, commit->author, authorlen);
4081                 if (type != LINE_CURSOR)
4082                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4083                 waddch(view->win, '~');
4084         } else {
4085                 waddstr(view->win, commit->author);
4086         }
4088         col += AUTHOR_COLS;
4090         if (opt_rev_graph && commit->graph_size) {
4091                 size_t i;
4093                 if (type != LINE_CURSOR)
4094                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4095                 wmove(view->win, lineno, col);
4096                 /* Using waddch() instead of waddnstr() ensures that
4097                  * they'll be rendered correctly for the cursor line. */
4098                 for (i = 0; i < commit->graph_size; i++)
4099                         waddch(view->win, commit->graph[i]);
4101                 waddch(view->win, ' ');
4102                 col += commit->graph_size + 1;
4103         }
4104         if (type != LINE_CURSOR)
4105                 wattrset(view->win, A_NORMAL);
4107         wmove(view->win, lineno, col);
4109         if (commit->refs) {
4110                 size_t i = 0;
4112                 do {
4113                         if (type == LINE_CURSOR)
4114                                 ;
4115                         else if (commit->refs[i]->tag)
4116                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4117                         else if (commit->refs[i]->remote)
4118                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4119                         else
4120                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4121                         waddstr(view->win, "[");
4122                         waddstr(view->win, commit->refs[i]->name);
4123                         waddstr(view->win, "]");
4124                         if (type != LINE_CURSOR)
4125                                 wattrset(view->win, A_NORMAL);
4126                         waddstr(view->win, " ");
4127                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4128                 } while (commit->refs[i++]->next);
4129         }
4131         if (type != LINE_CURSOR)
4132                 wattrset(view->win, get_line_attr(type));
4134         {
4135                 int titlelen = strlen(commit->title);
4137                 if (col + titlelen > view->width)
4138                         titlelen = view->width - col;
4140                 waddnstr(view->win, commit->title, titlelen);
4141         }
4143         return TRUE;
4146 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4147 static bool
4148 main_read(struct view *view, char *line)
4150         static struct rev_graph *graph = graph_stacks;
4151         enum line_type type;
4152         struct commit *commit;
4154         if (!line) {
4155                 update_rev_graph(graph);
4156                 return TRUE;
4157         }
4159         type = get_line_type(line);
4160         if (type == LINE_COMMIT) {
4161                 commit = calloc(1, sizeof(struct commit));
4162                 if (!commit)
4163                         return FALSE;
4165                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
4166                 commit->refs = get_refs(commit->id);
4167                 graph->commit = commit;
4168                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4169                 return TRUE;
4170         }
4172         if (!view->lines)
4173                 return TRUE;
4174         commit = view->line[view->lines - 1].data;
4176         switch (type) {
4177         case LINE_PARENT:
4178                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4179                 break;
4181         case LINE_AUTHOR:
4182         {
4183                 /* Parse author lines where the name may be empty:
4184                  *      author  <email@address.tld> 1138474660 +0100
4185                  */
4186                 char *ident = line + STRING_SIZE("author ");
4187                 char *nameend = strchr(ident, '<');
4188                 char *emailend = strchr(ident, '>');
4190                 if (!nameend || !emailend)
4191                         break;
4193                 update_rev_graph(graph);
4194                 graph = graph->next;
4196                 *nameend = *emailend = 0;
4197                 ident = chomp_string(ident);
4198                 if (!*ident) {
4199                         ident = chomp_string(nameend + 1);
4200                         if (!*ident)
4201                                 ident = "Unknown";
4202                 }
4204                 string_ncopy(commit->author, ident, strlen(ident));
4206                 /* Parse epoch and timezone */
4207                 if (emailend[1] == ' ') {
4208                         char *secs = emailend + 2;
4209                         char *zone = strchr(secs, ' ');
4210                         time_t time = (time_t) atol(secs);
4212                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4213                                 long tz;
4215                                 zone++;
4216                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4217                                 tz += ('0' - zone[2]) * 60 * 60;
4218                                 tz += ('0' - zone[3]) * 60;
4219                                 tz += ('0' - zone[4]) * 60;
4221                                 if (zone[0] == '-')
4222                                         tz = -tz;
4224                                 time -= tz;
4225                         }
4227                         gmtime_r(&time, &commit->time);
4228                 }
4229                 break;
4230         }
4231         default:
4232                 /* Fill in the commit title if it has not already been set. */
4233                 if (commit->title[0])
4234                         break;
4236                 /* Require titles to start with a non-space character at the
4237                  * offset used by git log. */
4238                 if (strncmp(line, "    ", 4))
4239                         break;
4240                 line += 4;
4241                 /* Well, if the title starts with a whitespace character,
4242                  * try to be forgiving.  Otherwise we end up with no title. */
4243                 while (isspace(*line))
4244                         line++;
4245                 if (*line == '\0')
4246                         break;
4247                 /* FIXME: More graceful handling of titles; append "..." to
4248                  * shortened titles, etc. */
4250                 string_ncopy(commit->title, line, strlen(line));
4251         }
4253         return TRUE;
4256 static enum request
4257 main_request(struct view *view, enum request request, struct line *line)
4259         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4261         if (request == REQ_ENTER)
4262                 open_view(view, REQ_VIEW_DIFF, flags);
4263         else
4264                 return request;
4266         return REQ_NONE;
4269 static bool
4270 main_grep(struct view *view, struct line *line)
4272         struct commit *commit = line->data;
4273         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4274         char buf[DATE_COLS + 1];
4275         regmatch_t pmatch;
4277         for (state = S_TITLE; state < S_END; state++) {
4278                 char *text;
4280                 switch (state) {
4281                 case S_TITLE:   text = commit->title;   break;
4282                 case S_AUTHOR:  text = commit->author;  break;
4283                 case S_DATE:
4284                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4285                                 continue;
4286                         text = buf;
4287                         break;
4289                 default:
4290                         return FALSE;
4291                 }
4293                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4294                         return TRUE;
4295         }
4297         return FALSE;
4300 static void
4301 main_select(struct view *view, struct line *line)
4303         struct commit *commit = line->data;
4305         string_copy_rev(view->ref, commit->id);
4306         string_copy_rev(ref_commit, view->ref);
4309 static struct view_ops main_ops = {
4310         "commit",
4311         NULL,
4312         main_read,
4313         main_draw,
4314         main_request,
4315         main_grep,
4316         main_select,
4317 };
4320 /*
4321  * Unicode / UTF-8 handling
4322  *
4323  * NOTE: Much of the following code for dealing with unicode is derived from
4324  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4325  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4326  */
4328 /* I've (over)annotated a lot of code snippets because I am not entirely
4329  * confident that the approach taken by this small UTF-8 interface is correct.
4330  * --jonas */
4332 static inline int
4333 unicode_width(unsigned long c)
4335         if (c >= 0x1100 &&
4336            (c <= 0x115f                         /* Hangul Jamo */
4337             || c == 0x2329
4338             || c == 0x232a
4339             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4340                                                 /* CJK ... Yi */
4341             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4342             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4343             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4344             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4345             || (c >= 0xffe0  && c <= 0xffe6)
4346             || (c >= 0x20000 && c <= 0x2fffd)
4347             || (c >= 0x30000 && c <= 0x3fffd)))
4348                 return 2;
4350         return 1;
4353 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4354  * Illegal bytes are set one. */
4355 static const unsigned char utf8_bytes[256] = {
4356         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,
4357         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,
4358         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,
4359         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,
4360         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,
4361         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,
4362         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,
4363         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,
4364 };
4366 /* Decode UTF-8 multi-byte representation into a unicode character. */
4367 static inline unsigned long
4368 utf8_to_unicode(const char *string, size_t length)
4370         unsigned long unicode;
4372         switch (length) {
4373         case 1:
4374                 unicode  =   string[0];
4375                 break;
4376         case 2:
4377                 unicode  =  (string[0] & 0x1f) << 6;
4378                 unicode +=  (string[1] & 0x3f);
4379                 break;
4380         case 3:
4381                 unicode  =  (string[0] & 0x0f) << 12;
4382                 unicode += ((string[1] & 0x3f) << 6);
4383                 unicode +=  (string[2] & 0x3f);
4384                 break;
4385         case 4:
4386                 unicode  =  (string[0] & 0x0f) << 18;
4387                 unicode += ((string[1] & 0x3f) << 12);
4388                 unicode += ((string[2] & 0x3f) << 6);
4389                 unicode +=  (string[3] & 0x3f);
4390                 break;
4391         case 5:
4392                 unicode  =  (string[0] & 0x0f) << 24;
4393                 unicode += ((string[1] & 0x3f) << 18);
4394                 unicode += ((string[2] & 0x3f) << 12);
4395                 unicode += ((string[3] & 0x3f) << 6);
4396                 unicode +=  (string[4] & 0x3f);
4397                 break;
4398         case 6:
4399                 unicode  =  (string[0] & 0x01) << 30;
4400                 unicode += ((string[1] & 0x3f) << 24);
4401                 unicode += ((string[2] & 0x3f) << 18);
4402                 unicode += ((string[3] & 0x3f) << 12);
4403                 unicode += ((string[4] & 0x3f) << 6);
4404                 unicode +=  (string[5] & 0x3f);
4405                 break;
4406         default:
4407                 die("Invalid unicode length");
4408         }
4410         /* Invalid characters could return the special 0xfffd value but NUL
4411          * should be just as good. */
4412         return unicode > 0xffff ? 0 : unicode;
4415 /* Calculates how much of string can be shown within the given maximum width
4416  * and sets trimmed parameter to non-zero value if all of string could not be
4417  * shown.
4418  *
4419  * Additionally, adds to coloffset how many many columns to move to align with
4420  * the expected position. Takes into account how multi-byte and double-width
4421  * characters will effect the cursor position.
4422  *
4423  * Returns the number of bytes to output from string to satisfy max_width. */
4424 static size_t
4425 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4427         const char *start = string;
4428         const char *end = strchr(string, '\0');
4429         size_t mbwidth = 0;
4430         size_t width = 0;
4432         *trimmed = 0;
4434         while (string < end) {
4435                 int c = *(unsigned char *) string;
4436                 unsigned char bytes = utf8_bytes[c];
4437                 size_t ucwidth;
4438                 unsigned long unicode;
4440                 if (string + bytes > end)
4441                         break;
4443                 /* Change representation to figure out whether
4444                  * it is a single- or double-width character. */
4446                 unicode = utf8_to_unicode(string, bytes);
4447                 /* FIXME: Graceful handling of invalid unicode character. */
4448                 if (!unicode)
4449                         break;
4451                 ucwidth = unicode_width(unicode);
4452                 width  += ucwidth;
4453                 if (width > max_width) {
4454                         *trimmed = 1;
4455                         break;
4456                 }
4458                 /* The column offset collects the differences between the
4459                  * number of bytes encoding a character and the number of
4460                  * columns will be used for rendering said character.
4461                  *
4462                  * So if some character A is encoded in 2 bytes, but will be
4463                  * represented on the screen using only 1 byte this will and up
4464                  * adding 1 to the multi-byte column offset.
4465                  *
4466                  * Assumes that no double-width character can be encoding in
4467                  * less than two bytes. */
4468                 if (bytes > ucwidth)
4469                         mbwidth += bytes - ucwidth;
4471                 string  += bytes;
4472         }
4474         *coloffset += mbwidth;
4476         return string - start;
4480 /*
4481  * Status management
4482  */
4484 /* Whether or not the curses interface has been initialized. */
4485 static bool cursed = FALSE;
4487 /* The status window is used for polling keystrokes. */
4488 static WINDOW *status_win;
4490 static bool status_empty = TRUE;
4492 /* Update status and title window. */
4493 static void
4494 report(const char *msg, ...)
4496         struct view *view = display[current_view];
4498         if (input_mode)
4499                 return;
4501         if (!view) {
4502                 char buf[SIZEOF_STR];
4503                 va_list args;
4505                 va_start(args, msg);
4506                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4507                         buf[sizeof(buf) - 1] = 0;
4508                         buf[sizeof(buf) - 2] = '.';
4509                         buf[sizeof(buf) - 3] = '.';
4510                         buf[sizeof(buf) - 4] = '.';
4511                 }
4512                 va_end(args);
4513                 die("%s", buf);
4514         }
4516         if (!status_empty || *msg) {
4517                 va_list args;
4519                 va_start(args, msg);
4521                 wmove(status_win, 0, 0);
4522                 if (*msg) {
4523                         vwprintw(status_win, msg, args);
4524                         status_empty = FALSE;
4525                 } else {
4526                         status_empty = TRUE;
4527                 }
4528                 wclrtoeol(status_win);
4529                 wrefresh(status_win);
4531                 va_end(args);
4532         }
4534         update_view_title(view);
4535         update_display_cursor(view);
4538 /* Controls when nodelay should be in effect when polling user input. */
4539 static void
4540 set_nonblocking_input(bool loading)
4542         static unsigned int loading_views;
4544         if ((loading == FALSE && loading_views-- == 1) ||
4545             (loading == TRUE  && loading_views++ == 0))
4546                 nodelay(status_win, loading);
4549 static void
4550 init_display(void)
4552         int x, y;
4554         /* Initialize the curses library */
4555         if (isatty(STDIN_FILENO)) {
4556                 cursed = !!initscr();
4557         } else {
4558                 /* Leave stdin and stdout alone when acting as a pager. */
4559                 FILE *io = fopen("/dev/tty", "r+");
4561                 if (!io)
4562                         die("Failed to open /dev/tty");
4563                 cursed = !!newterm(NULL, io, io);
4564         }
4566         if (!cursed)
4567                 die("Failed to initialize curses");
4569         nonl();         /* Tell curses not to do NL->CR/NL on output */
4570         cbreak();       /* Take input chars one at a time, no wait for \n */
4571         noecho();       /* Don't echo input */
4572         leaveok(stdscr, TRUE);
4574         if (has_colors())
4575                 init_colors();
4577         getmaxyx(stdscr, y, x);
4578         status_win = newwin(1, 0, y - 1, 0);
4579         if (!status_win)
4580                 die("Failed to create status window");
4582         /* Enable keyboard mapping */
4583         keypad(status_win, TRUE);
4584         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4587 static char *
4588 read_prompt(const char *prompt)
4590         enum { READING, STOP, CANCEL } status = READING;
4591         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4592         int pos = 0;
4594         while (status == READING) {
4595                 struct view *view;
4596                 int i, key;
4598                 input_mode = TRUE;
4600                 foreach_view (view, i)
4601                         update_view(view);
4603                 input_mode = FALSE;
4605                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4606                 wclrtoeol(status_win);
4608                 /* Refresh, accept single keystroke of input */
4609                 key = wgetch(status_win);
4610                 switch (key) {
4611                 case KEY_RETURN:
4612                 case KEY_ENTER:
4613                 case '\n':
4614                         status = pos ? STOP : CANCEL;
4615                         break;
4617                 case KEY_BACKSPACE:
4618                         if (pos > 0)
4619                                 pos--;
4620                         else
4621                                 status = CANCEL;
4622                         break;
4624                 case KEY_ESC:
4625                         status = CANCEL;
4626                         break;
4628                 case ERR:
4629                         break;
4631                 default:
4632                         if (pos >= sizeof(buf)) {
4633                                 report("Input string too long");
4634                                 return NULL;
4635                         }
4637                         if (isprint(key))
4638                                 buf[pos++] = (char) key;
4639                 }
4640         }
4642         /* Clear the status window */
4643         status_empty = FALSE;
4644         report("");
4646         if (status == CANCEL)
4647                 return NULL;
4649         buf[pos++] = 0;
4651         return buf;
4654 /*
4655  * Repository references
4656  */
4658 static struct ref *refs;
4659 static size_t refs_size;
4661 /* Id <-> ref store */
4662 static struct ref ***id_refs;
4663 static size_t id_refs_size;
4665 static struct ref **
4666 get_refs(char *id)
4668         struct ref ***tmp_id_refs;
4669         struct ref **ref_list = NULL;
4670         size_t ref_list_size = 0;
4671         size_t i;
4673         for (i = 0; i < id_refs_size; i++)
4674                 if (!strcmp(id, id_refs[i][0]->id))
4675                         return id_refs[i];
4677         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4678         if (!tmp_id_refs)
4679                 return NULL;
4681         id_refs = tmp_id_refs;
4683         for (i = 0; i < refs_size; i++) {
4684                 struct ref **tmp;
4686                 if (strcmp(id, refs[i].id))
4687                         continue;
4689                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4690                 if (!tmp) {
4691                         if (ref_list)
4692                                 free(ref_list);
4693                         return NULL;
4694                 }
4696                 ref_list = tmp;
4697                 if (ref_list_size > 0)
4698                         ref_list[ref_list_size - 1]->next = 1;
4699                 ref_list[ref_list_size] = &refs[i];
4701                 /* XXX: The properties of the commit chains ensures that we can
4702                  * safely modify the shared ref. The repo references will
4703                  * always be similar for the same id. */
4704                 ref_list[ref_list_size]->next = 0;
4705                 ref_list_size++;
4706         }
4708         if (ref_list)
4709                 id_refs[id_refs_size++] = ref_list;
4711         return ref_list;
4714 static int
4715 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4717         struct ref *ref;
4718         bool tag = FALSE;
4719         bool remote = FALSE;
4721         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4722                 /* Commits referenced by tags has "^{}" appended. */
4723                 if (name[namelen - 1] != '}')
4724                         return OK;
4726                 while (namelen > 0 && name[namelen] != '^')
4727                         namelen--;
4729                 tag = TRUE;
4730                 namelen -= STRING_SIZE("refs/tags/");
4731                 name    += STRING_SIZE("refs/tags/");
4733         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4734                 remote = TRUE;
4735                 namelen -= STRING_SIZE("refs/remotes/");
4736                 name    += STRING_SIZE("refs/remotes/");
4738         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4739                 namelen -= STRING_SIZE("refs/heads/");
4740                 name    += STRING_SIZE("refs/heads/");
4742         } else if (!strcmp(name, "HEAD")) {
4743                 return OK;
4744         }
4746         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4747         if (!refs)
4748                 return ERR;
4750         ref = &refs[refs_size++];
4751         ref->name = malloc(namelen + 1);
4752         if (!ref->name)
4753                 return ERR;
4755         strncpy(ref->name, name, namelen);
4756         ref->name[namelen] = 0;
4757         ref->tag = tag;
4758         ref->remote = remote;
4759         string_copy_rev(ref->id, id);
4761         return OK;
4764 static int
4765 load_refs(void)
4767         const char *cmd_env = getenv("TIG_LS_REMOTE");
4768         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4770         return read_properties(popen(cmd, "r"), "\t", read_ref);
4773 static int
4774 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4776         if (!strcmp(name, "i18n.commitencoding"))
4777                 string_ncopy(opt_encoding, value, valuelen);
4779         if (!strcmp(name, "core.editor"))
4780                 string_ncopy(opt_editor, value, valuelen);
4782         return OK;
4785 static int
4786 load_repo_config(void)
4788         return read_properties(popen(GIT_CONFIG " --list", "r"),
4789                                "=", read_repo_config_option);
4792 static int
4793 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4795         if (!opt_git_dir[0]) {
4796                 string_ncopy(opt_git_dir, name, namelen);
4798         } else if (opt_is_inside_work_tree == -1) {
4799                 /* This can be 3 different values depending on the
4800                  * version of git being used. If git-rev-parse does not
4801                  * understand --is-inside-work-tree it will simply echo
4802                  * the option else either "true" or "false" is printed.
4803                  * Default to true for the unknown case. */
4804                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4806         } else {
4807                 string_ncopy(opt_cdup, name, namelen);
4808         }
4810         return OK;
4813 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4814  * must be the last one! */
4815 static int
4816 load_repo_info(void)
4818         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4819                                "=", read_repo_info);
4822 static int
4823 read_properties(FILE *pipe, const char *separators,
4824                 int (*read_property)(char *, size_t, char *, size_t))
4826         char buffer[BUFSIZ];
4827         char *name;
4828         int state = OK;
4830         if (!pipe)
4831                 return ERR;
4833         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4834                 char *value;
4835                 size_t namelen;
4836                 size_t valuelen;
4838                 name = chomp_string(name);
4839                 namelen = strcspn(name, separators);
4841                 if (name[namelen]) {
4842                         name[namelen] = 0;
4843                         value = chomp_string(name + namelen + 1);
4844                         valuelen = strlen(value);
4846                 } else {
4847                         value = "";
4848                         valuelen = 0;
4849                 }
4851                 state = read_property(name, namelen, value, valuelen);
4852         }
4854         if (state != ERR && ferror(pipe))
4855                 state = ERR;
4857         pclose(pipe);
4859         return state;
4863 /*
4864  * Main
4865  */
4867 static void __NORETURN
4868 quit(int sig)
4870         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4871         if (cursed)
4872                 endwin();
4873         exit(0);
4876 static void __NORETURN
4877 die(const char *err, ...)
4879         va_list args;
4881         endwin();
4883         va_start(args, err);
4884         fputs("tig: ", stderr);
4885         vfprintf(stderr, err, args);
4886         fputs("\n", stderr);
4887         va_end(args);
4889         exit(1);
4892 int
4893 main(int argc, char *argv[])
4895         struct view *view;
4896         enum request request;
4897         size_t i;
4899         signal(SIGINT, quit);
4901         if (setlocale(LC_ALL, "")) {
4902                 char *codeset = nl_langinfo(CODESET);
4904                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4905         }
4907         if (load_repo_info() == ERR)
4908                 die("Failed to load repo info.");
4910         if (load_options() == ERR)
4911                 die("Failed to load user config.");
4913         /* Load the repo config file so options can be overwritten from
4914          * the command line. */
4915         if (load_repo_config() == ERR)
4916                 die("Failed to load repo config.");
4918         if (!parse_options(argc, argv))
4919                 return 0;
4921         /* Require a git repository unless when running in pager mode. */
4922         if (!opt_git_dir[0])
4923                 die("Not a git repository");
4925         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4926                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4927                 if (opt_iconv == ICONV_NONE)
4928                         die("Failed to initialize character set conversion");
4929         }
4931         if (load_refs() == ERR)
4932                 die("Failed to load refs.");
4934         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4935                 view->cmd_env = getenv(view->cmd_env);
4937         request = opt_request;
4939         init_display();
4941         while (view_driver(display[current_view], request)) {
4942                 int key;
4943                 int i;
4945                 foreach_view (view, i)
4946                         update_view(view);
4948                 /* Refresh, accept single keystroke of input */
4949                 key = wgetch(status_win);
4951                 /* wgetch() with nodelay() enabled returns ERR when there's no
4952                  * input. */
4953                 if (key == ERR) {
4954                         request = REQ_NONE;
4955                         continue;
4956                 }
4958                 request = get_keybinding(display[current_view]->keymap, key);
4960                 /* Some low-level request handling. This keeps access to
4961                  * status_win restricted. */
4962                 switch (request) {
4963                 case REQ_PROMPT:
4964                 {
4965                         char *cmd = read_prompt(":");
4967                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4968                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4969                                         opt_request = REQ_VIEW_DIFF;
4970                                 } else {
4971                                         opt_request = REQ_VIEW_PAGER;
4972                                 }
4973                                 break;
4974                         }
4976                         request = REQ_NONE;
4977                         break;
4978                 }
4979                 case REQ_SEARCH:
4980                 case REQ_SEARCH_BACK:
4981                 {
4982                         const char *prompt = request == REQ_SEARCH
4983                                            ? "/" : "?";
4984                         char *search = read_prompt(prompt);
4986                         if (search)
4987                                 string_ncopy(opt_search, search, strlen(search));
4988                         else
4989                                 request = REQ_NONE;
4990                         break;
4991                 }
4992                 case REQ_SCREEN_RESIZE:
4993                 {
4994                         int height, width;
4996                         getmaxyx(stdscr, height, width);
4998                         /* Resize the status view and let the view driver take
4999                          * care of resizing the displayed views. */
5000                         wresize(status_win, 1, width);
5001                         mvwin(status_win, height - 1, 0);
5002                         wrefresh(status_win);
5003                         break;
5004                 }
5005                 default:
5006                         break;
5007                 }
5008         }
5010         quit(0);
5012         return 0;