Code

0.11.git
[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 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
101 #define TABSIZE         8
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
105 #ifndef GIT_CONFIG
106 #define GIT_CONFIG "git config"
107 #endif
109 #define TIG_LS_REMOTE \
110         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
112 #define TIG_DIFF_CMD \
113         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
115 #define TIG_LOG_CMD     \
116         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
118 #define TIG_MAIN_CMD \
119         "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
121 #define TIG_TREE_CMD    \
122         "git ls-tree %s %s"
124 #define TIG_BLOB_CMD    \
125         "git cat-file blob %s"
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD    ""
129 #define TIG_PAGER_CMD   ""
130 #define TIG_STATUS_CMD  ""
131 #define TIG_STAGE_CMD   ""
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
134 #define KEY_TAB         '\t'
135 #define KEY_RETURN      '\r'
136 #define KEY_ESC         27
139 struct ref {
140         char *name;             /* Ref name; tag or head names are shortened. */
141         char id[SIZEOF_REV];    /* Commit SHA1 ID */
142         unsigned int tag:1;     /* Is it a tag? */
143         unsigned int remote:1;  /* Is it a remote ref? */
144         unsigned int next:1;    /* For ref lists: are there more refs? */
145 };
147 static struct ref **get_refs(char *id);
149 struct int_map {
150         const char *name;
151         int namelen;
152         int value;
153 };
155 static int
156 set_from_int_map(struct int_map *map, size_t map_size,
157                  int *value, const char *name, int namelen)
160         int i;
162         for (i = 0; i < map_size; i++)
163                 if (namelen == map[i].namelen &&
164                     !strncasecmp(name, map[i].name, namelen)) {
165                         *value = map[i].value;
166                         return OK;
167                 }
169         return ERR;
173 /*
174  * String helpers
175  */
177 static inline void
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
180         if (srclen > dstlen - 1)
181                 srclen = dstlen - 1;
183         strncpy(dst, src, srclen);
184         dst[srclen] = 0;
187 /* Shorthands for safely copying into a fixed buffer. */
189 #define string_copy(dst, src) \
190         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
192 #define string_ncopy(dst, src, srclen) \
193         string_ncopy_do(dst, sizeof(dst), src, srclen)
195 #define string_copy_rev(dst, src) \
196         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
198 #define string_add(dst, from, src) \
199         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
201 static char *
202 chomp_string(char *name)
204         int namelen;
206         while (isspace(*name))
207                 name++;
209         namelen = strlen(name) - 1;
210         while (namelen > 0 && isspace(name[namelen]))
211                 name[namelen--] = 0;
213         return name;
216 static bool
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
219         va_list args;
220         size_t pos = bufpos ? *bufpos : 0;
222         va_start(args, fmt);
223         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224         va_end(args);
226         if (bufpos)
227                 *bufpos = pos;
229         return pos >= bufsize ? FALSE : TRUE;
232 #define string_format(buf, fmt, args...) \
233         string_nformat(buf, sizeof(buf), NULL, fmt, args)
235 #define string_format_from(buf, from, fmt, args...) \
236         string_nformat(buf, sizeof(buf), from, fmt, args)
238 static int
239 string_enum_compare(const char *str1, const char *str2, int len)
241         size_t i;
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
245         /* Diff-Header == DIFF_HEADER */
246         for (i = 0; i < len; i++) {
247                 if (toupper(str1[i]) == toupper(str2[i]))
248                         continue;
250                 if (string_enum_sep(str1[i]) &&
251                     string_enum_sep(str2[i]))
252                         continue;
254                 return str1[i] - str2[i];
255         }
257         return 0;
260 /* Shell quoting
261  *
262  * NOTE: The following is a slightly modified copy of the git project's shell
263  * quoting routines found in the quote.c file.
264  *
265  * Help to copy the thing properly quoted for the shell safety.  any single
266  * quote is replaced with '\'', any exclamation point is replaced with '\!',
267  * and the whole thing is enclosed in a
268  *
269  * E.g.
270  *  original     sq_quote     result
271  *  name     ==> name      ==> 'name'
272  *  a b      ==> a b       ==> 'a b'
273  *  a'b      ==> a'\''b    ==> 'a'\''b'
274  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
275  */
277 static size_t
278 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
280         char c;
282 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
284         BUFPUT('\'');
285         while ((c = *src++)) {
286                 if (c == '\'' || c == '!') {
287                         BUFPUT('\'');
288                         BUFPUT('\\');
289                         BUFPUT(c);
290                         BUFPUT('\'');
291                 } else {
292                         BUFPUT(c);
293                 }
294         }
295         BUFPUT('\'');
297         if (bufsize < SIZEOF_STR)
298                 buf[bufsize] = 0;
300         return bufsize;
304 /*
305  * User requests
306  */
308 #define REQ_INFO \
309         /* XXX: Keep the view request first and in sync with views[]. */ \
310         REQ_GROUP("View switching") \
311         REQ_(VIEW_MAIN,         "Show main view"), \
312         REQ_(VIEW_DIFF,         "Show diff view"), \
313         REQ_(VIEW_LOG,          "Show log view"), \
314         REQ_(VIEW_TREE,         "Show tree view"), \
315         REQ_(VIEW_BLOB,         "Show blob view"), \
316         REQ_(VIEW_HELP,         "Show help page"), \
317         REQ_(VIEW_PAGER,        "Show pager view"), \
318         REQ_(VIEW_STATUS,       "Show status view"), \
319         REQ_(VIEW_STAGE,        "Show stage view"), \
320         \
321         REQ_GROUP("View manipulation") \
322         REQ_(ENTER,             "Enter current line and scroll"), \
323         REQ_(NEXT,              "Move to next"), \
324         REQ_(PREVIOUS,          "Move to previous"), \
325         REQ_(VIEW_NEXT,         "Move focus to next view"), \
326         REQ_(REFRESH,           "Reload and refresh"), \
327         REQ_(VIEW_CLOSE,        "Close the current view"), \
328         REQ_(QUIT,              "Close all views and quit"), \
329         \
330         REQ_GROUP("Cursor navigation") \
331         REQ_(MOVE_UP,           "Move cursor one line up"), \
332         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
333         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
334         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
335         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
336         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
337         \
338         REQ_GROUP("Scrolling") \
339         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
340         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
341         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
342         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
343         \
344         REQ_GROUP("Searching") \
345         REQ_(SEARCH,            "Search the view"), \
346         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
347         REQ_(FIND_NEXT,         "Find next search match"), \
348         REQ_(FIND_PREV,         "Find previous search match"), \
349         \
350         REQ_GROUP("Misc") \
351         REQ_(PROMPT,            "Bring up the prompt"), \
352         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
353         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
354         REQ_(SHOW_VERSION,      "Show version information"), \
355         REQ_(STOP_LOADING,      "Stop all loading views"), \
356         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
357         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
358         REQ_(STATUS_UPDATE,     "Update file status"), \
359         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
360         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
361         REQ_(EDIT,              "Open in editor"), \
362         REQ_(NONE,              "Do nothing")
365 /* User action requests. */
366 enum request {
367 #define REQ_GROUP(help)
368 #define REQ_(req, help) REQ_##req
370         /* Offset all requests to avoid conflicts with ncurses getch values. */
371         REQ_OFFSET = KEY_MAX + 1,
372         REQ_INFO
374 #undef  REQ_GROUP
375 #undef  REQ_
376 };
378 struct request_info {
379         enum request request;
380         char *name;
381         int namelen;
382         char *help;
383 };
385 static struct request_info req_info[] = {
386 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
387 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
388         REQ_INFO
389 #undef  REQ_GROUP
390 #undef  REQ_
391 };
393 static enum request
394 get_request(const char *name)
396         int namelen = strlen(name);
397         int i;
399         for (i = 0; i < ARRAY_SIZE(req_info); i++)
400                 if (req_info[i].namelen == namelen &&
401                     !string_enum_compare(req_info[i].name, name, namelen))
402                         return req_info[i].request;
404         return REQ_NONE;
408 /*
409  * Options
410  */
412 static const char usage[] =
413 "tig " TIG_VERSION " (" __DATE__ ")\n"
414 "\n"
415 "Usage: tig        [options] [revs] [--] [paths]\n"
416 "   or: tig show   [options] [revs] [--] [paths]\n"
417 "   or: tig status\n"
418 "   or: tig <      [git command output]\n"
419 "\n"
420 "Options:\n"
421 "  -v, --version   Show version and exit\n"
422 "  -h, --help      Show help message and exit\n";
424 /* Option and state variables. */
425 static bool opt_line_number             = FALSE;
426 static bool opt_rev_graph               = FALSE;
427 static int opt_num_interval             = NUMBER_INTERVAL;
428 static int opt_tab_size                 = TABSIZE;
429 static enum request opt_request         = REQ_VIEW_MAIN;
430 static char opt_cmd[SIZEOF_STR]         = "";
431 static char opt_path[SIZEOF_STR]        = "";
432 static FILE *opt_pipe                   = NULL;
433 static char opt_encoding[20]            = "UTF-8";
434 static bool opt_utf8                    = TRUE;
435 static char opt_codeset[20]             = "UTF-8";
436 static iconv_t opt_iconv                = ICONV_NONE;
437 static char opt_search[SIZEOF_STR]      = "";
438 static char opt_cdup[SIZEOF_STR]        = "";
439 static char opt_git_dir[SIZEOF_STR]     = "";
440 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
441 static char opt_editor[SIZEOF_STR]      = "";
443 enum option_type {
444         OPT_NONE,
445         OPT_INT,
446 };
448 static bool
449 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
451         va_list args;
452         char *value = "";
453         int *number;
455         if (opt[0] != '-')
456                 return FALSE;
458         if (opt[1] == '-') {
459                 int namelen = strlen(name);
461                 opt += 2;
463                 if (strncmp(opt, name, namelen))
464                         return FALSE;
466                 if (opt[namelen] == '=')
467                         value = opt + namelen + 1;
469         } else {
470                 if (!short_name || opt[1] != short_name)
471                         return FALSE;
472                 value = opt + 2;
473         }
475         va_start(args, type);
476         if (type == OPT_INT) {
477                 number = va_arg(args, int *);
478                 if (isdigit(*value))
479                         *number = atoi(value);
480         }
481         va_end(args);
483         return TRUE;
486 /* Returns the index of log or diff command or -1 to exit. */
487 static bool
488 parse_options(int argc, char *argv[])
490         char *altargv[1024];
491         int altargc = 0;
492         char *subcommand = NULL;
493         int i;
495         for (i = 1; i < argc; i++) {
496                 char *opt = argv[i];
498                 if (!strcmp(opt, "log") ||
499                     !strcmp(opt, "diff")) {
500                         subcommand = opt;
501                         opt_request = opt[0] == 'l'
502                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503                         warn("`tig %s' has been deprecated", opt);
504                         break;
505                 }
507                 if (!strcmp(opt, "show")) {
508                         subcommand = opt;
509                         opt_request = REQ_VIEW_DIFF;
510                         break;
511                 }
513                 if (!strcmp(opt, "status")) {
514                         subcommand = opt;
515                         opt_request = REQ_VIEW_STATUS;
516                         break;
517                 }
519                 if (opt[0] && opt[0] != '-')
520                         break;
522                 if (!strcmp(opt, "--")) {
523                         i++;
524                         break;
525                 }
527                 if (check_option(opt, 'v', "version", OPT_NONE)) {
528                         printf("tig version %s\n", TIG_VERSION);
529                         return FALSE;
530                 }
532                 if (check_option(opt, 'h', "help", OPT_NONE)) {
533                         printf(usage);
534                         return FALSE;
535                 }
537                 if (!strcmp(opt, "-S")) {
538                         warn("`%s' has been deprecated; use `tig status' instead", opt);
539                         opt_request = REQ_VIEW_STATUS;
540                         continue;
541                 }
543                 if (!strcmp(opt, "-l")) {
544                         opt_request = REQ_VIEW_LOG;
545                 } else if (!strcmp(opt, "-d")) {
546                         opt_request = REQ_VIEW_DIFF;
547                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
548                         opt_line_number = TRUE;
549                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
550                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
551                 } else {
552                         if (altargc >= ARRAY_SIZE(altargv))
553                                 die("maximum number of arguments exceeded");
554                         altargv[altargc++] = opt;
555                         continue;
556                 }
558                 warn("`%s' has been deprecated", opt);
559         }
561         /* Check that no 'alt' arguments occured before a subcommand. */
562         if (subcommand && i < argc && altargc > 0)
563                 die("unknown arguments before `%s'", argv[i]);
565         if (!isatty(STDIN_FILENO)) {
566                 opt_request = REQ_VIEW_PAGER;
567                 opt_pipe = stdin;
569         } else if (opt_request == REQ_VIEW_STATUS) {
570                 if (argc - i > 1)
571                         warn("ignoring arguments after `%s'", argv[i]);
573         } else if (i < argc || altargc > 0) {
574                 int alti = 0;
575                 size_t buf_size;
577                 if (opt_request == REQ_VIEW_MAIN)
578                         /* XXX: This is vulnerable to the user overriding
579                          * options required for the main view parser. */
580                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
581                 else
582                         string_copy(opt_cmd, "git");
583                 buf_size = strlen(opt_cmd);
585                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
586                         opt_cmd[buf_size++] = ' ';
587                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
588                 }
590                 while (buf_size < sizeof(opt_cmd) && i < argc) {
591                         opt_cmd[buf_size++] = ' ';
592                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
593                 }
595                 if (buf_size >= sizeof(opt_cmd))
596                         die("command too long");
598                 opt_cmd[buf_size] = 0;
599         }
601         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
602                 opt_utf8 = FALSE;
604         return TRUE;
608 /*
609  * Line-oriented content detection.
610  */
612 #define LINE_INFO \
613 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
614 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
616 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
617 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
618 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
619 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
620 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
621 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
622 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
623 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
624 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
625 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
626 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
627 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
628 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
629 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
630 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
631 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
632 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
633 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
634 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
635 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
636 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
637 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
638 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
639 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
640 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
641 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
642 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
643 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
644 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
645 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
646 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
647 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
648 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
649 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
650 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
651 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
652 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
653 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
654 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
655 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
656 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
657 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
658 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
659 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
660 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
662 enum line_type {
663 #define LINE(type, line, fg, bg, attr) \
664         LINE_##type
665         LINE_INFO
666 #undef  LINE
667 };
669 struct line_info {
670         const char *name;       /* Option name. */
671         int namelen;            /* Size of option name. */
672         const char *line;       /* The start of line to match. */
673         int linelen;            /* Size of string to match. */
674         int fg, bg, attr;       /* Color and text attributes for the lines. */
675 };
677 static struct line_info line_info[] = {
678 #define LINE(type, line, fg, bg, attr) \
679         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
680         LINE_INFO
681 #undef  LINE
682 };
684 static enum line_type
685 get_line_type(char *line)
687         int linelen = strlen(line);
688         enum line_type type;
690         for (type = 0; type < ARRAY_SIZE(line_info); type++)
691                 /* Case insensitive search matches Signed-off-by lines better. */
692                 if (linelen >= line_info[type].linelen &&
693                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
694                         return type;
696         return LINE_DEFAULT;
699 static inline int
700 get_line_attr(enum line_type type)
702         assert(type < ARRAY_SIZE(line_info));
703         return COLOR_PAIR(type) | line_info[type].attr;
706 static struct line_info *
707 get_line_info(char *name, int namelen)
709         enum line_type type;
711         for (type = 0; type < ARRAY_SIZE(line_info); type++)
712                 if (namelen == line_info[type].namelen &&
713                     !string_enum_compare(line_info[type].name, name, namelen))
714                         return &line_info[type];
716         return NULL;
719 static void
720 init_colors(void)
722         int default_bg = line_info[LINE_DEFAULT].bg;
723         int default_fg = line_info[LINE_DEFAULT].fg;
724         enum line_type type;
726         start_color();
728         if (assume_default_colors(default_fg, default_bg) == ERR) {
729                 default_bg = COLOR_BLACK;
730                 default_fg = COLOR_WHITE;
731         }
733         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
734                 struct line_info *info = &line_info[type];
735                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
736                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
738                 init_pair(type, fg, bg);
739         }
742 struct line {
743         enum line_type type;
745         /* State flags */
746         unsigned int selected:1;
748         void *data;             /* User data */
749 };
752 /*
753  * Keys
754  */
756 struct keybinding {
757         int alias;
758         enum request request;
759         struct keybinding *next;
760 };
762 static struct keybinding default_keybindings[] = {
763         /* View switching */
764         { 'm',          REQ_VIEW_MAIN },
765         { 'd',          REQ_VIEW_DIFF },
766         { 'l',          REQ_VIEW_LOG },
767         { 't',          REQ_VIEW_TREE },
768         { 'f',          REQ_VIEW_BLOB },
769         { 'p',          REQ_VIEW_PAGER },
770         { 'h',          REQ_VIEW_HELP },
771         { 'S',          REQ_VIEW_STATUS },
772         { 'c',          REQ_VIEW_STAGE },
774         /* View manipulation */
775         { 'q',          REQ_VIEW_CLOSE },
776         { KEY_TAB,      REQ_VIEW_NEXT },
777         { KEY_RETURN,   REQ_ENTER },
778         { KEY_UP,       REQ_PREVIOUS },
779         { KEY_DOWN,     REQ_NEXT },
780         { 'R',          REQ_REFRESH },
782         /* Cursor navigation */
783         { 'k',          REQ_MOVE_UP },
784         { 'j',          REQ_MOVE_DOWN },
785         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
786         { KEY_END,      REQ_MOVE_LAST_LINE },
787         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
788         { ' ',          REQ_MOVE_PAGE_DOWN },
789         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
790         { 'b',          REQ_MOVE_PAGE_UP },
791         { '-',          REQ_MOVE_PAGE_UP },
793         /* Scrolling */
794         { KEY_IC,       REQ_SCROLL_LINE_UP },
795         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
796         { 'w',          REQ_SCROLL_PAGE_UP },
797         { 's',          REQ_SCROLL_PAGE_DOWN },
799         /* Searching */
800         { '/',          REQ_SEARCH },
801         { '?',          REQ_SEARCH_BACK },
802         { 'n',          REQ_FIND_NEXT },
803         { 'N',          REQ_FIND_PREV },
805         /* Misc */
806         { 'Q',          REQ_QUIT },
807         { 'z',          REQ_STOP_LOADING },
808         { 'v',          REQ_SHOW_VERSION },
809         { 'r',          REQ_SCREEN_REDRAW },
810         { '.',          REQ_TOGGLE_LINENO },
811         { 'g',          REQ_TOGGLE_REV_GRAPH },
812         { ':',          REQ_PROMPT },
813         { 'u',          REQ_STATUS_UPDATE },
814         { 'M',          REQ_STATUS_MERGE },
815         { ',',          REQ_TREE_PARENT },
816         { 'e',          REQ_EDIT },
818         /* Using the ncurses SIGWINCH handler. */
819         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
820 };
822 #define KEYMAP_INFO \
823         KEYMAP_(GENERIC), \
824         KEYMAP_(MAIN), \
825         KEYMAP_(DIFF), \
826         KEYMAP_(LOG), \
827         KEYMAP_(TREE), \
828         KEYMAP_(BLOB), \
829         KEYMAP_(PAGER), \
830         KEYMAP_(HELP), \
831         KEYMAP_(STATUS), \
832         KEYMAP_(STAGE)
834 enum keymap {
835 #define KEYMAP_(name) KEYMAP_##name
836         KEYMAP_INFO
837 #undef  KEYMAP_
838 };
840 static struct int_map keymap_table[] = {
841 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
842         KEYMAP_INFO
843 #undef  KEYMAP_
844 };
846 #define set_keymap(map, name) \
847         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
849 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
851 static void
852 add_keybinding(enum keymap keymap, enum request request, int key)
854         struct keybinding *keybinding;
856         keybinding = calloc(1, sizeof(*keybinding));
857         if (!keybinding)
858                 die("Failed to allocate keybinding");
860         keybinding->alias = key;
861         keybinding->request = request;
862         keybinding->next = keybindings[keymap];
863         keybindings[keymap] = keybinding;
866 /* Looks for a key binding first in the given map, then in the generic map, and
867  * lastly in the default keybindings. */
868 static enum request
869 get_keybinding(enum keymap keymap, int key)
871         struct keybinding *kbd;
872         int i;
874         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
875                 if (kbd->alias == key)
876                         return kbd->request;
878         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
879                 if (kbd->alias == key)
880                         return kbd->request;
882         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
883                 if (default_keybindings[i].alias == key)
884                         return default_keybindings[i].request;
886         return (enum request) key;
890 struct key {
891         char *name;
892         int value;
893 };
895 static struct key key_table[] = {
896         { "Enter",      KEY_RETURN },
897         { "Space",      ' ' },
898         { "Backspace",  KEY_BACKSPACE },
899         { "Tab",        KEY_TAB },
900         { "Escape",     KEY_ESC },
901         { "Left",       KEY_LEFT },
902         { "Right",      KEY_RIGHT },
903         { "Up",         KEY_UP },
904         { "Down",       KEY_DOWN },
905         { "Insert",     KEY_IC },
906         { "Delete",     KEY_DC },
907         { "Hash",       '#' },
908         { "Home",       KEY_HOME },
909         { "End",        KEY_END },
910         { "PageUp",     KEY_PPAGE },
911         { "PageDown",   KEY_NPAGE },
912         { "F1",         KEY_F(1) },
913         { "F2",         KEY_F(2) },
914         { "F3",         KEY_F(3) },
915         { "F4",         KEY_F(4) },
916         { "F5",         KEY_F(5) },
917         { "F6",         KEY_F(6) },
918         { "F7",         KEY_F(7) },
919         { "F8",         KEY_F(8) },
920         { "F9",         KEY_F(9) },
921         { "F10",        KEY_F(10) },
922         { "F11",        KEY_F(11) },
923         { "F12",        KEY_F(12) },
924 };
926 static int
927 get_key_value(const char *name)
929         int i;
931         for (i = 0; i < ARRAY_SIZE(key_table); i++)
932                 if (!strcasecmp(key_table[i].name, name))
933                         return key_table[i].value;
935         if (strlen(name) == 1 && isprint(*name))
936                 return (int) *name;
938         return ERR;
941 static char *
942 get_key_name(int key_value)
944         static char key_char[] = "'X'";
945         char *seq = NULL;
946         int key;
948         for (key = 0; key < ARRAY_SIZE(key_table); key++)
949                 if (key_table[key].value == key_value)
950                         seq = key_table[key].name;
952         if (seq == NULL &&
953             key_value < 127 &&
954             isprint(key_value)) {
955                 key_char[1] = (char) key_value;
956                 seq = key_char;
957         }
959         return seq ? seq : "'?'";
962 static char *
963 get_key(enum request request)
965         static char buf[BUFSIZ];
966         size_t pos = 0;
967         char *sep = "";
968         int i;
970         buf[pos] = 0;
972         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
973                 struct keybinding *keybinding = &default_keybindings[i];
975                 if (keybinding->request != request)
976                         continue;
978                 if (!string_format_from(buf, &pos, "%s%s", sep,
979                                         get_key_name(keybinding->alias)))
980                         return "Too many keybindings!";
981                 sep = ", ";
982         }
984         return buf;
987 struct run_request {
988         enum keymap keymap;
989         int key;
990         char cmd[SIZEOF_STR];
991 };
993 static struct run_request *run_request;
994 static size_t run_requests;
996 static enum request
997 add_run_request(enum keymap keymap, int key, int argc, char **argv)
999         struct run_request *tmp;
1000         struct run_request req = { keymap, key };
1001         size_t bufpos;
1003         for (bufpos = 0; argc > 0; argc--, argv++)
1004                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1005                         return REQ_NONE;
1007         req.cmd[bufpos - 1] = 0;
1009         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1010         if (!tmp)
1011                 return REQ_NONE;
1013         run_request = tmp;
1014         run_request[run_requests++] = req;
1016         return REQ_NONE + run_requests;
1019 static struct run_request *
1020 get_run_request(enum request request)
1022         if (request <= REQ_NONE)
1023                 return NULL;
1024         return &run_request[request - REQ_NONE - 1];
1027 static void
1028 add_builtin_run_requests(void)
1030         struct {
1031                 enum keymap keymap;
1032                 int key;
1033                 char *argv[1];
1034         } reqs[] = {
1035                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1036                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1037         };
1038         int i;
1040         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1041                 enum request req;
1043                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1044                 if (req != REQ_NONE)
1045                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1046         }
1049 /*
1050  * User config file handling.
1051  */
1053 static struct int_map color_map[] = {
1054 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1055         COLOR_MAP(DEFAULT),
1056         COLOR_MAP(BLACK),
1057         COLOR_MAP(BLUE),
1058         COLOR_MAP(CYAN),
1059         COLOR_MAP(GREEN),
1060         COLOR_MAP(MAGENTA),
1061         COLOR_MAP(RED),
1062         COLOR_MAP(WHITE),
1063         COLOR_MAP(YELLOW),
1064 };
1066 #define set_color(color, name) \
1067         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1069 static struct int_map attr_map[] = {
1070 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1071         ATTR_MAP(NORMAL),
1072         ATTR_MAP(BLINK),
1073         ATTR_MAP(BOLD),
1074         ATTR_MAP(DIM),
1075         ATTR_MAP(REVERSE),
1076         ATTR_MAP(STANDOUT),
1077         ATTR_MAP(UNDERLINE),
1078 };
1080 #define set_attribute(attr, name) \
1081         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1083 static int   config_lineno;
1084 static bool  config_errors;
1085 static char *config_msg;
1087 /* Wants: object fgcolor bgcolor [attr] */
1088 static int
1089 option_color_command(int argc, char *argv[])
1091         struct line_info *info;
1093         if (argc != 3 && argc != 4) {
1094                 config_msg = "Wrong number of arguments given to color command";
1095                 return ERR;
1096         }
1098         info = get_line_info(argv[0], strlen(argv[0]));
1099         if (!info) {
1100                 config_msg = "Unknown color name";
1101                 return ERR;
1102         }
1104         if (set_color(&info->fg, argv[1]) == ERR ||
1105             set_color(&info->bg, argv[2]) == ERR) {
1106                 config_msg = "Unknown color";
1107                 return ERR;
1108         }
1110         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1111                 config_msg = "Unknown attribute";
1112                 return ERR;
1113         }
1115         return OK;
1118 /* Wants: name = value */
1119 static int
1120 option_set_command(int argc, char *argv[])
1122         if (argc != 3) {
1123                 config_msg = "Wrong number of arguments given to set command";
1124                 return ERR;
1125         }
1127         if (strcmp(argv[1], "=")) {
1128                 config_msg = "No value assigned";
1129                 return ERR;
1130         }
1132         if (!strcmp(argv[0], "show-rev-graph")) {
1133                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1134                                  !strcmp(argv[2], "true") ||
1135                                  !strcmp(argv[2], "yes"));
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "line-number-interval")) {
1140                 opt_num_interval = atoi(argv[2]);
1141                 return OK;
1142         }
1144         if (!strcmp(argv[0], "tab-size")) {
1145                 opt_tab_size = atoi(argv[2]);
1146                 return OK;
1147         }
1149         if (!strcmp(argv[0], "commit-encoding")) {
1150                 char *arg = argv[2];
1151                 int delimiter = *arg;
1152                 int i;
1154                 switch (delimiter) {
1155                 case '"':
1156                 case '\'':
1157                         for (arg++, i = 0; arg[i]; i++)
1158                                 if (arg[i] == delimiter) {
1159                                         arg[i] = 0;
1160                                         break;
1161                                 }
1162                 default:
1163                         string_ncopy(opt_encoding, arg, strlen(arg));
1164                         return OK;
1165                 }
1166         }
1168         config_msg = "Unknown variable name";
1169         return ERR;
1172 /* Wants: mode request key */
1173 static int
1174 option_bind_command(int argc, char *argv[])
1176         enum request request;
1177         int keymap;
1178         int key;
1180         if (argc < 3) {
1181                 config_msg = "Wrong number of arguments given to bind command";
1182                 return ERR;
1183         }
1185         if (set_keymap(&keymap, argv[0]) == ERR) {
1186                 config_msg = "Unknown key map";
1187                 return ERR;
1188         }
1190         key = get_key_value(argv[1]);
1191         if (key == ERR) {
1192                 config_msg = "Unknown key";
1193                 return ERR;
1194         }
1196         request = get_request(argv[2]);
1197         if (request == REQ_NONE) {
1198                 const char *obsolete[] = { "cherry-pick" };
1199                 size_t namelen = strlen(argv[2]);
1200                 int i;
1202                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1203                         if (namelen == strlen(obsolete[i]) &&
1204                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1205                                 config_msg = "Obsolete request name";
1206                                 return ERR;
1207                         }
1208                 }
1209         }
1210         if (request == REQ_NONE && *argv[2]++ == '!')
1211                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1212         if (request == REQ_NONE) {
1213                 config_msg = "Unknown request name";
1214                 return ERR;
1215         }
1217         add_keybinding(keymap, request, key);
1219         return OK;
1222 static int
1223 set_option(char *opt, char *value)
1225         char *argv[16];
1226         int valuelen;
1227         int argc = 0;
1229         /* Tokenize */
1230         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1231                 argv[argc++] = value;
1232                 value += valuelen;
1234                 /* Nothing more to tokenize or last available token. */
1235                 if (!*value || argc >= ARRAY_SIZE(argv))
1236                         break;
1238                 *value++ = 0;
1239                 while (isspace(*value))
1240                         value++;
1241         }
1243         if (!strcmp(opt, "color"))
1244                 return option_color_command(argc, argv);
1246         if (!strcmp(opt, "set"))
1247                 return option_set_command(argc, argv);
1249         if (!strcmp(opt, "bind"))
1250                 return option_bind_command(argc, argv);
1252         config_msg = "Unknown option command";
1253         return ERR;
1256 static int
1257 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1259         int status = OK;
1261         config_lineno++;
1262         config_msg = "Internal error";
1264         /* Check for comment markers, since read_properties() will
1265          * only ensure opt and value are split at first " \t". */
1266         optlen = strcspn(opt, "#");
1267         if (optlen == 0)
1268                 return OK;
1270         if (opt[optlen] != 0) {
1271                 config_msg = "No option value";
1272                 status = ERR;
1274         }  else {
1275                 /* Look for comment endings in the value. */
1276                 size_t len = strcspn(value, "#");
1278                 if (len < valuelen) {
1279                         valuelen = len;
1280                         value[valuelen] = 0;
1281                 }
1283                 status = set_option(opt, value);
1284         }
1286         if (status == ERR) {
1287                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1288                         config_lineno, (int) optlen, opt, config_msg);
1289                 config_errors = TRUE;
1290         }
1292         /* Always keep going if errors are encountered. */
1293         return OK;
1296 static void
1297 load_option_file(const char *path)
1299         FILE *file;
1301         /* It's ok that the file doesn't exist. */
1302         file = fopen(path, "r");
1303         if (!file)
1304                 return;
1306         config_lineno = 0;
1307         config_errors = FALSE;
1309         if (read_properties(file, " \t", read_option) == ERR ||
1310             config_errors == TRUE)
1311                 fprintf(stderr, "Errors while loading %s.\n", path);
1314 static int
1315 load_options(void)
1317         char *home = getenv("HOME");
1318         char *tigrc_user = getenv("TIGRC_USER");
1319         char *tigrc_system = getenv("TIGRC_SYSTEM");
1320         char buf[SIZEOF_STR];
1322         add_builtin_run_requests();
1324         if (!tigrc_system) {
1325                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1326                         return ERR;
1327                 tigrc_system = buf;
1328         }
1329         load_option_file(tigrc_system);
1331         if (!tigrc_user) {
1332                 if (!home || !string_format(buf, "%s/.tigrc", home))
1333                         return ERR;
1334                 tigrc_user = buf;
1335         }
1336         load_option_file(tigrc_user);
1338         return OK;
1342 /*
1343  * The viewer
1344  */
1346 struct view;
1347 struct view_ops;
1349 /* The display array of active views and the index of the current view. */
1350 static struct view *display[2];
1351 static unsigned int current_view;
1353 /* Reading from the prompt? */
1354 static bool input_mode = FALSE;
1356 #define foreach_displayed_view(view, i) \
1357         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1359 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1361 /* Current head and commit ID */
1362 static char ref_blob[SIZEOF_REF]        = "";
1363 static char ref_commit[SIZEOF_REF]      = "HEAD";
1364 static char ref_head[SIZEOF_REF]        = "HEAD";
1366 struct view {
1367         const char *name;       /* View name */
1368         const char *cmd_fmt;    /* Default command line format */
1369         const char *cmd_env;    /* Command line set via environment */
1370         const char *id;         /* Points to either of ref_{head,commit,blob} */
1372         struct view_ops *ops;   /* View operations */
1374         enum keymap keymap;     /* What keymap does this view have */
1376         char cmd[SIZEOF_STR];   /* Command buffer */
1377         char ref[SIZEOF_REF];   /* Hovered commit reference */
1378         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1380         int height, width;      /* The width and height of the main window */
1381         WINDOW *win;            /* The main window */
1382         WINDOW *title;          /* The title window living below the main window */
1384         /* Navigation */
1385         unsigned long offset;   /* Offset of the window top */
1386         unsigned long lineno;   /* Current line number */
1388         /* Searching */
1389         char grep[SIZEOF_STR];  /* Search string */
1390         regex_t *regex;         /* Pre-compiled regex */
1392         /* If non-NULL, points to the view that opened this view. If this view
1393          * is closed tig will switch back to the parent view. */
1394         struct view *parent;
1396         /* Buffering */
1397         unsigned long lines;    /* Total number of lines */
1398         struct line *line;      /* Line index */
1399         unsigned long line_size;/* Total number of allocated lines */
1400         unsigned int digits;    /* Number of digits in the lines member. */
1402         /* Loading */
1403         FILE *pipe;
1404         time_t start_time;
1405 };
1407 struct view_ops {
1408         /* What type of content being displayed. Used in the title bar. */
1409         const char *type;
1410         /* Open and reads in all view content. */
1411         bool (*open)(struct view *view);
1412         /* Read one line; updates view->line. */
1413         bool (*read)(struct view *view, char *data);
1414         /* Draw one line; @lineno must be < view->height. */
1415         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1416         /* Depending on view handle a special requests. */
1417         enum request (*request)(struct view *view, enum request request, struct line *line);
1418         /* Search for regex in a line. */
1419         bool (*grep)(struct view *view, struct line *line);
1420         /* Select line */
1421         void (*select)(struct view *view, struct line *line);
1422 };
1424 static struct view_ops pager_ops;
1425 static struct view_ops main_ops;
1426 static struct view_ops tree_ops;
1427 static struct view_ops blob_ops;
1428 static struct view_ops help_ops;
1429 static struct view_ops status_ops;
1430 static struct view_ops stage_ops;
1432 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1433         { name, cmd, #env, ref, ops, map}
1435 #define VIEW_(id, name, ops, ref) \
1436         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1439 static struct view views[] = {
1440         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1441         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1442         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1443         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1444         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1445         VIEW_(HELP,   "help",   &help_ops,   ""),
1446         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1447         VIEW_(STATUS, "status", &status_ops, ""),
1448         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1449 };
1451 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1453 #define foreach_view(view, i) \
1454         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1456 #define view_is_displayed(view) \
1457         (view == display[0] || view == display[1])
1459 static int
1460 draw_text(struct view *view, const char *string, int max_len, int col,
1461           bool use_tilde, int tilde_attr)
1463         int n;
1465         n = 0;
1466         if (max_len > 0) {
1467                 int len;
1468                 int trimmed = FALSE;
1470                 if (opt_utf8) {
1471                         int pad = 0;
1473                         len = utf8_length(string, max_len, &pad, &trimmed);
1474                         if (trimmed && use_tilde) {
1475                                 max_len -= 1;
1476                                 len = utf8_length(
1477                                         string, max_len, &pad, &trimmed);
1478                         }
1479                         n = len;
1480                 } else {
1481                         len = strlen(string);
1482                         if (len > max_len) {
1483                                 if (use_tilde) {
1484                                         max_len -= 1;
1485                                 }
1486                                 len = max_len;
1487                                 trimmed = TRUE;
1488                         }
1489                         n = len;
1490                 }
1491                 waddnstr(view->win, string, n);
1492                 if (trimmed && use_tilde) {
1493                         if (tilde_attr != -1)
1494                                 wattrset(view->win, tilde_attr);
1495                         waddch(view->win, '~');
1496                         n++;
1497                 }
1498         }
1500         return n;
1503 static bool
1504 draw_view_line(struct view *view, unsigned int lineno)
1506         struct line *line;
1507         bool selected = (view->offset + lineno == view->lineno);
1508         bool draw_ok;
1510         assert(view_is_displayed(view));
1512         if (view->offset + lineno >= view->lines)
1513                 return FALSE;
1515         line = &view->line[view->offset + lineno];
1517         if (selected) {
1518                 line->selected = TRUE;
1519                 view->ops->select(view, line);
1520         } else if (line->selected) {
1521                 line->selected = FALSE;
1522                 wmove(view->win, lineno, 0);
1523                 wclrtoeol(view->win);
1524         }
1526         scrollok(view->win, FALSE);
1527         draw_ok = view->ops->draw(view, line, lineno, selected);
1528         scrollok(view->win, TRUE);
1530         return draw_ok;
1533 static void
1534 redraw_view_from(struct view *view, int lineno)
1536         assert(0 <= lineno && lineno < view->height);
1538         for (; lineno < view->height; lineno++) {
1539                 if (!draw_view_line(view, lineno))
1540                         break;
1541         }
1543         redrawwin(view->win);
1544         if (input_mode)
1545                 wnoutrefresh(view->win);
1546         else
1547                 wrefresh(view->win);
1550 static void
1551 redraw_view(struct view *view)
1553         wclear(view->win);
1554         redraw_view_from(view, 0);
1558 static void
1559 update_view_title(struct view *view)
1561         char buf[SIZEOF_STR];
1562         char state[SIZEOF_STR];
1563         size_t bufpos = 0, statelen = 0;
1565         assert(view_is_displayed(view));
1567         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1568                 unsigned int view_lines = view->offset + view->height;
1569                 unsigned int lines = view->lines
1570                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1571                                    : 0;
1573                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1574                                    view->ops->type,
1575                                    view->lineno + 1,
1576                                    view->lines,
1577                                    lines);
1579                 if (view->pipe) {
1580                         time_t secs = time(NULL) - view->start_time;
1582                         /* Three git seconds are a long time ... */
1583                         if (secs > 2)
1584                                 string_format_from(state, &statelen, " %lds", secs);
1585                 }
1586         }
1588         string_format_from(buf, &bufpos, "[%s]", view->name);
1589         if (*view->ref && bufpos < view->width) {
1590                 size_t refsize = strlen(view->ref);
1591                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1593                 if (minsize < view->width)
1594                         refsize = view->width - minsize + 7;
1595                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1596         }
1598         if (statelen && bufpos < view->width) {
1599                 string_format_from(buf, &bufpos, " %s", state);
1600         }
1602         if (view == display[current_view])
1603                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1604         else
1605                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1607         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1608         wclrtoeol(view->title);
1609         wmove(view->title, 0, view->width - 1);
1611         if (input_mode)
1612                 wnoutrefresh(view->title);
1613         else
1614                 wrefresh(view->title);
1617 static void
1618 resize_display(void)
1620         int offset, i;
1621         struct view *base = display[0];
1622         struct view *view = display[1] ? display[1] : display[0];
1624         /* Setup window dimensions */
1626         getmaxyx(stdscr, base->height, base->width);
1628         /* Make room for the status window. */
1629         base->height -= 1;
1631         if (view != base) {
1632                 /* Horizontal split. */
1633                 view->width   = base->width;
1634                 view->height  = SCALE_SPLIT_VIEW(base->height);
1635                 base->height -= view->height;
1637                 /* Make room for the title bar. */
1638                 view->height -= 1;
1639         }
1641         /* Make room for the title bar. */
1642         base->height -= 1;
1644         offset = 0;
1646         foreach_displayed_view (view, i) {
1647                 if (!view->win) {
1648                         view->win = newwin(view->height, 0, offset, 0);
1649                         if (!view->win)
1650                                 die("Failed to create %s view", view->name);
1652                         scrollok(view->win, TRUE);
1654                         view->title = newwin(1, 0, offset + view->height, 0);
1655                         if (!view->title)
1656                                 die("Failed to create title window");
1658                 } else {
1659                         wresize(view->win, view->height, view->width);
1660                         mvwin(view->win,   offset, 0);
1661                         mvwin(view->title, offset + view->height, 0);
1662                 }
1664                 offset += view->height + 1;
1665         }
1668 static void
1669 redraw_display(void)
1671         struct view *view;
1672         int i;
1674         foreach_displayed_view (view, i) {
1675                 redraw_view(view);
1676                 update_view_title(view);
1677         }
1680 static void
1681 update_display_cursor(struct view *view)
1683         /* Move the cursor to the right-most column of the cursor line.
1684          *
1685          * XXX: This could turn out to be a bit expensive, but it ensures that
1686          * the cursor does not jump around. */
1687         if (view->lines) {
1688                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1689                 wrefresh(view->win);
1690         }
1693 /*
1694  * Navigation
1695  */
1697 /* Scrolling backend */
1698 static void
1699 do_scroll_view(struct view *view, int lines)
1701         bool redraw_current_line = FALSE;
1703         /* The rendering expects the new offset. */
1704         view->offset += lines;
1706         assert(0 <= view->offset && view->offset < view->lines);
1707         assert(lines);
1709         /* Move current line into the view. */
1710         if (view->lineno < view->offset) {
1711                 view->lineno = view->offset;
1712                 redraw_current_line = TRUE;
1713         } else if (view->lineno >= view->offset + view->height) {
1714                 view->lineno = view->offset + view->height - 1;
1715                 redraw_current_line = TRUE;
1716         }
1718         assert(view->offset <= view->lineno && view->lineno < view->lines);
1720         /* Redraw the whole screen if scrolling is pointless. */
1721         if (view->height < ABS(lines)) {
1722                 redraw_view(view);
1724         } else {
1725                 int line = lines > 0 ? view->height - lines : 0;
1726                 int end = line + ABS(lines);
1728                 wscrl(view->win, lines);
1730                 for (; line < end; line++) {
1731                         if (!draw_view_line(view, line))
1732                                 break;
1733                 }
1735                 if (redraw_current_line)
1736                         draw_view_line(view, view->lineno - view->offset);
1737         }
1739         redrawwin(view->win);
1740         wrefresh(view->win);
1741         report("");
1744 /* Scroll frontend */
1745 static void
1746 scroll_view(struct view *view, enum request request)
1748         int lines = 1;
1750         assert(view_is_displayed(view));
1752         switch (request) {
1753         case REQ_SCROLL_PAGE_DOWN:
1754                 lines = view->height;
1755         case REQ_SCROLL_LINE_DOWN:
1756                 if (view->offset + lines > view->lines)
1757                         lines = view->lines - view->offset;
1759                 if (lines == 0 || view->offset + view->height >= view->lines) {
1760                         report("Cannot scroll beyond the last line");
1761                         return;
1762                 }
1763                 break;
1765         case REQ_SCROLL_PAGE_UP:
1766                 lines = view->height;
1767         case REQ_SCROLL_LINE_UP:
1768                 if (lines > view->offset)
1769                         lines = view->offset;
1771                 if (lines == 0) {
1772                         report("Cannot scroll beyond the first line");
1773                         return;
1774                 }
1776                 lines = -lines;
1777                 break;
1779         default:
1780                 die("request %d not handled in switch", request);
1781         }
1783         do_scroll_view(view, lines);
1786 /* Cursor moving */
1787 static void
1788 move_view(struct view *view, enum request request)
1790         int scroll_steps = 0;
1791         int steps;
1793         switch (request) {
1794         case REQ_MOVE_FIRST_LINE:
1795                 steps = -view->lineno;
1796                 break;
1798         case REQ_MOVE_LAST_LINE:
1799                 steps = view->lines - view->lineno - 1;
1800                 break;
1802         case REQ_MOVE_PAGE_UP:
1803                 steps = view->height > view->lineno
1804                       ? -view->lineno : -view->height;
1805                 break;
1807         case REQ_MOVE_PAGE_DOWN:
1808                 steps = view->lineno + view->height >= view->lines
1809                       ? view->lines - view->lineno - 1 : view->height;
1810                 break;
1812         case REQ_MOVE_UP:
1813                 steps = -1;
1814                 break;
1816         case REQ_MOVE_DOWN:
1817                 steps = 1;
1818                 break;
1820         default:
1821                 die("request %d not handled in switch", request);
1822         }
1824         if (steps <= 0 && view->lineno == 0) {
1825                 report("Cannot move beyond the first line");
1826                 return;
1828         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1829                 report("Cannot move beyond the last line");
1830                 return;
1831         }
1833         /* Move the current line */
1834         view->lineno += steps;
1835         assert(0 <= view->lineno && view->lineno < view->lines);
1837         /* Check whether the view needs to be scrolled */
1838         if (view->lineno < view->offset ||
1839             view->lineno >= view->offset + view->height) {
1840                 scroll_steps = steps;
1841                 if (steps < 0 && -steps > view->offset) {
1842                         scroll_steps = -view->offset;
1844                 } else if (steps > 0) {
1845                         if (view->lineno == view->lines - 1 &&
1846                             view->lines > view->height) {
1847                                 scroll_steps = view->lines - view->offset - 1;
1848                                 if (scroll_steps >= view->height)
1849                                         scroll_steps -= view->height - 1;
1850                         }
1851                 }
1852         }
1854         if (!view_is_displayed(view)) {
1855                 view->offset += scroll_steps;
1856                 assert(0 <= view->offset && view->offset < view->lines);
1857                 view->ops->select(view, &view->line[view->lineno]);
1858                 return;
1859         }
1861         /* Repaint the old "current" line if we be scrolling */
1862         if (ABS(steps) < view->height)
1863                 draw_view_line(view, view->lineno - steps - view->offset);
1865         if (scroll_steps) {
1866                 do_scroll_view(view, scroll_steps);
1867                 return;
1868         }
1870         /* Draw the current line */
1871         draw_view_line(view, view->lineno - view->offset);
1873         redrawwin(view->win);
1874         wrefresh(view->win);
1875         report("");
1879 /*
1880  * Searching
1881  */
1883 static void search_view(struct view *view, enum request request);
1885 static bool
1886 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1888         assert(view_is_displayed(view));
1890         if (!view->ops->grep(view, line))
1891                 return FALSE;
1893         if (lineno - view->offset >= view->height) {
1894                 view->offset = lineno;
1895                 view->lineno = lineno;
1896                 redraw_view(view);
1898         } else {
1899                 unsigned long old_lineno = view->lineno - view->offset;
1901                 view->lineno = lineno;
1902                 draw_view_line(view, old_lineno);
1904                 draw_view_line(view, view->lineno - view->offset);
1905                 redrawwin(view->win);
1906                 wrefresh(view->win);
1907         }
1909         report("Line %ld matches '%s'", lineno + 1, view->grep);
1910         return TRUE;
1913 static void
1914 find_next(struct view *view, enum request request)
1916         unsigned long lineno = view->lineno;
1917         int direction;
1919         if (!*view->grep) {
1920                 if (!*opt_search)
1921                         report("No previous search");
1922                 else
1923                         search_view(view, request);
1924                 return;
1925         }
1927         switch (request) {
1928         case REQ_SEARCH:
1929         case REQ_FIND_NEXT:
1930                 direction = 1;
1931                 break;
1933         case REQ_SEARCH_BACK:
1934         case REQ_FIND_PREV:
1935                 direction = -1;
1936                 break;
1938         default:
1939                 return;
1940         }
1942         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1943                 lineno += direction;
1945         /* Note, lineno is unsigned long so will wrap around in which case it
1946          * will become bigger than view->lines. */
1947         for (; lineno < view->lines; lineno += direction) {
1948                 struct line *line = &view->line[lineno];
1950                 if (find_next_line(view, lineno, line))
1951                         return;
1952         }
1954         report("No match found for '%s'", view->grep);
1957 static void
1958 search_view(struct view *view, enum request request)
1960         int regex_err;
1962         if (view->regex) {
1963                 regfree(view->regex);
1964                 *view->grep = 0;
1965         } else {
1966                 view->regex = calloc(1, sizeof(*view->regex));
1967                 if (!view->regex)
1968                         return;
1969         }
1971         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1972         if (regex_err != 0) {
1973                 char buf[SIZEOF_STR] = "unknown error";
1975                 regerror(regex_err, view->regex, buf, sizeof(buf));
1976                 report("Search failed: %s", buf);
1977                 return;
1978         }
1980         string_copy(view->grep, opt_search);
1982         find_next(view, request);
1985 /*
1986  * Incremental updating
1987  */
1989 static void
1990 end_update(struct view *view)
1992         if (!view->pipe)
1993                 return;
1994         set_nonblocking_input(FALSE);
1995         if (view->pipe == stdin)
1996                 fclose(view->pipe);
1997         else
1998                 pclose(view->pipe);
1999         view->pipe = NULL;
2002 static bool
2003 begin_update(struct view *view)
2005         if (view->pipe)
2006                 end_update(view);
2008         if (opt_cmd[0]) {
2009                 string_copy(view->cmd, opt_cmd);
2010                 opt_cmd[0] = 0;
2011                 /* When running random commands, initially show the
2012                  * command in the title. However, it maybe later be
2013                  * overwritten if a commit line is selected. */
2014                 if (view == VIEW(REQ_VIEW_PAGER))
2015                         string_copy(view->ref, view->cmd);
2016                 else
2017                         view->ref[0] = 0;
2019         } else if (view == VIEW(REQ_VIEW_TREE)) {
2020                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2021                 char path[SIZEOF_STR];
2023                 if (strcmp(view->vid, view->id))
2024                         opt_path[0] = path[0] = 0;
2025                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2026                         return FALSE;
2028                 if (!string_format(view->cmd, format, view->id, path))
2029                         return FALSE;
2031         } else {
2032                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2033                 const char *id = view->id;
2035                 if (!string_format(view->cmd, format, id, id, id, id, id))
2036                         return FALSE;
2038                 /* Put the current ref_* value to the view title ref
2039                  * member. This is needed by the blob view. Most other
2040                  * views sets it automatically after loading because the
2041                  * first line is a commit line. */
2042                 string_copy_rev(view->ref, view->id);
2043         }
2045         /* Special case for the pager view. */
2046         if (opt_pipe) {
2047                 view->pipe = opt_pipe;
2048                 opt_pipe = NULL;
2049         } else {
2050                 view->pipe = popen(view->cmd, "r");
2051         }
2053         if (!view->pipe)
2054                 return FALSE;
2056         set_nonblocking_input(TRUE);
2058         view->offset = 0;
2059         view->lines  = 0;
2060         view->lineno = 0;
2061         string_copy_rev(view->vid, view->id);
2063         if (view->line) {
2064                 int i;
2066                 for (i = 0; i < view->lines; i++)
2067                         if (view->line[i].data)
2068                                 free(view->line[i].data);
2070                 free(view->line);
2071                 view->line = NULL;
2072         }
2074         view->start_time = time(NULL);
2076         return TRUE;
2079 static struct line *
2080 realloc_lines(struct view *view, size_t line_size)
2082         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2084         if (!tmp)
2085                 return NULL;
2087         view->line = tmp;
2088         view->line_size = line_size;
2089         return view->line;
2092 static bool
2093 update_view(struct view *view)
2095         char in_buffer[BUFSIZ];
2096         char out_buffer[BUFSIZ * 2];
2097         char *line;
2098         /* The number of lines to read. If too low it will cause too much
2099          * redrawing (and possible flickering), if too high responsiveness
2100          * will suffer. */
2101         unsigned long lines = view->height;
2102         int redraw_from = -1;
2104         if (!view->pipe)
2105                 return TRUE;
2107         /* Only redraw if lines are visible. */
2108         if (view->offset + view->height >= view->lines)
2109                 redraw_from = view->lines - view->offset;
2111         /* FIXME: This is probably not perfect for backgrounded views. */
2112         if (!realloc_lines(view, view->lines + lines))
2113                 goto alloc_error;
2115         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2116                 size_t linelen = strlen(line);
2118                 if (linelen)
2119                         line[linelen - 1] = 0;
2121                 if (opt_iconv != ICONV_NONE) {
2122                         ICONV_CONST char *inbuf = line;
2123                         size_t inlen = linelen;
2125                         char *outbuf = out_buffer;
2126                         size_t outlen = sizeof(out_buffer);
2128                         size_t ret;
2130                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2131                         if (ret != (size_t) -1) {
2132                                 line = out_buffer;
2133                                 linelen = strlen(out_buffer);
2134                         }
2135                 }
2137                 if (!view->ops->read(view, line))
2138                         goto alloc_error;
2140                 if (lines-- == 1)
2141                         break;
2142         }
2144         {
2145                 int digits;
2147                 lines = view->lines;
2148                 for (digits = 0; lines; digits++)
2149                         lines /= 10;
2151                 /* Keep the displayed view in sync with line number scaling. */
2152                 if (digits != view->digits) {
2153                         view->digits = digits;
2154                         redraw_from = 0;
2155                 }
2156         }
2158         if (!view_is_displayed(view))
2159                 goto check_pipe;
2161         if (view == VIEW(REQ_VIEW_TREE)) {
2162                 /* Clear the view and redraw everything since the tree sorting
2163                  * might have rearranged things. */
2164                 redraw_view(view);
2166         } else if (redraw_from >= 0) {
2167                 /* If this is an incremental update, redraw the previous line
2168                  * since for commits some members could have changed when
2169                  * loading the main view. */
2170                 if (redraw_from > 0)
2171                         redraw_from--;
2173                 /* Since revision graph visualization requires knowledge
2174                  * about the parent commit, it causes a further one-off
2175                  * needed to be redrawn for incremental updates. */
2176                 if (redraw_from > 0 && opt_rev_graph)
2177                         redraw_from--;
2179                 /* Incrementally draw avoids flickering. */
2180                 redraw_view_from(view, redraw_from);
2181         }
2183         /* Update the title _after_ the redraw so that if the redraw picks up a
2184          * commit reference in view->ref it'll be available here. */
2185         update_view_title(view);
2187 check_pipe:
2188         if (ferror(view->pipe)) {
2189                 report("Failed to read: %s", strerror(errno));
2190                 goto end;
2192         } else if (feof(view->pipe)) {
2193                 report("");
2194                 goto end;
2195         }
2197         return TRUE;
2199 alloc_error:
2200         report("Allocation failure");
2202 end:
2203         view->ops->read(view, NULL);
2204         end_update(view);
2205         return FALSE;
2208 static struct line *
2209 add_line_data(struct view *view, void *data, enum line_type type)
2211         struct line *line = &view->line[view->lines++];
2213         memset(line, 0, sizeof(*line));
2214         line->type = type;
2215         line->data = data;
2217         return line;
2220 static struct line *
2221 add_line_text(struct view *view, char *data, enum line_type type)
2223         if (data)
2224                 data = strdup(data);
2226         return data ? add_line_data(view, data, type) : NULL;
2230 /*
2231  * View opening
2232  */
2234 enum open_flags {
2235         OPEN_DEFAULT = 0,       /* Use default view switching. */
2236         OPEN_SPLIT = 1,         /* Split current view. */
2237         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2238         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2239 };
2241 static void
2242 open_view(struct view *prev, enum request request, enum open_flags flags)
2244         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2245         bool split = !!(flags & OPEN_SPLIT);
2246         bool reload = !!(flags & OPEN_RELOAD);
2247         struct view *view = VIEW(request);
2248         int nviews = displayed_views();
2249         struct view *base_view = display[0];
2251         if (view == prev && nviews == 1 && !reload) {
2252                 report("Already in %s view", view->name);
2253                 return;
2254         }
2256         if (view->ops->open) {
2257                 if (!view->ops->open(view)) {
2258                         report("Failed to load %s view", view->name);
2259                         return;
2260                 }
2262         } else if ((reload || strcmp(view->vid, view->id)) &&
2263                    !begin_update(view)) {
2264                 report("Failed to load %s view", view->name);
2265                 return;
2266         }
2268         if (split) {
2269                 display[1] = view;
2270                 if (!backgrounded)
2271                         current_view = 1;
2272         } else {
2273                 /* Maximize the current view. */
2274                 memset(display, 0, sizeof(display));
2275                 current_view = 0;
2276                 display[current_view] = view;
2277         }
2279         /* Resize the view when switching between split- and full-screen,
2280          * or when switching between two different full-screen views. */
2281         if (nviews != displayed_views() ||
2282             (nviews == 1 && base_view != display[0]))
2283                 resize_display();
2285         if (split && prev->lineno - prev->offset >= prev->height) {
2286                 /* Take the title line into account. */
2287                 int lines = prev->lineno - prev->offset - prev->height + 1;
2289                 /* Scroll the view that was split if the current line is
2290                  * outside the new limited view. */
2291                 do_scroll_view(prev, lines);
2292         }
2294         if (prev && view != prev) {
2295                 if (split && !backgrounded) {
2296                         /* "Blur" the previous view. */
2297                         update_view_title(prev);
2298                 }
2300                 view->parent = prev;
2301         }
2303         if (view->pipe && view->lines == 0) {
2304                 /* Clear the old view and let the incremental updating refill
2305                  * the screen. */
2306                 wclear(view->win);
2307                 report("");
2308         } else {
2309                 redraw_view(view);
2310                 report("");
2311         }
2313         /* If the view is backgrounded the above calls to report()
2314          * won't redraw the view title. */
2315         if (backgrounded)
2316                 update_view_title(view);
2319 static void
2320 open_external_viewer(const char *cmd)
2322         def_prog_mode();           /* save current tty modes */
2323         endwin();                  /* restore original tty modes */
2324         system(cmd);
2325         fprintf(stderr, "Press Enter to continue");
2326         getc(stdin);
2327         reset_prog_mode();
2328         redraw_display();
2331 static void
2332 open_mergetool(const char *file)
2334         char cmd[SIZEOF_STR];
2335         char file_sq[SIZEOF_STR];
2337         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2338             string_format(cmd, "git mergetool %s", file_sq)) {
2339                 open_external_viewer(cmd);
2340         }
2343 static void
2344 open_editor(bool from_root, const char *file)
2346         char cmd[SIZEOF_STR];
2347         char file_sq[SIZEOF_STR];
2348         char *editor;
2349         char *prefix = from_root ? opt_cdup : "";
2351         editor = getenv("GIT_EDITOR");
2352         if (!editor && *opt_editor)
2353                 editor = opt_editor;
2354         if (!editor)
2355                 editor = getenv("VISUAL");
2356         if (!editor)
2357                 editor = getenv("EDITOR");
2358         if (!editor)
2359                 editor = "vi";
2361         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2362             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2363                 open_external_viewer(cmd);
2364         }
2367 static void
2368 open_run_request(enum request request)
2370         struct run_request *req = get_run_request(request);
2371         char buf[SIZEOF_STR * 2];
2372         size_t bufpos;
2373         char *cmd;
2375         if (!req) {
2376                 report("Unknown run request");
2377                 return;
2378         }
2380         bufpos = 0;
2381         cmd = req->cmd;
2383         while (cmd) {
2384                 char *next = strstr(cmd, "%(");
2385                 int len = next - cmd;
2386                 char *value;
2388                 if (!next) {
2389                         len = strlen(cmd);
2390                         value = "";
2392                 } else if (!strncmp(next, "%(head)", 7)) {
2393                         value = ref_head;
2395                 } else if (!strncmp(next, "%(commit)", 9)) {
2396                         value = ref_commit;
2398                 } else if (!strncmp(next, "%(blob)", 7)) {
2399                         value = ref_blob;
2401                 } else {
2402                         report("Unknown replacement in run request: `%s`", req->cmd);
2403                         return;
2404                 }
2406                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2407                         return;
2409                 if (next)
2410                         next = strchr(next, ')') + 1;
2411                 cmd = next;
2412         }
2414         open_external_viewer(buf);
2417 /*
2418  * User request switch noodle
2419  */
2421 static int
2422 view_driver(struct view *view, enum request request)
2424         int i;
2426         if (request == REQ_NONE) {
2427                 doupdate();
2428                 return TRUE;
2429         }
2431         if (request > REQ_NONE) {
2432                 open_run_request(request);
2433                 return TRUE;
2434         }
2436         if (view && view->lines) {
2437                 request = view->ops->request(view, request, &view->line[view->lineno]);
2438                 if (request == REQ_NONE)
2439                         return TRUE;
2440         }
2442         switch (request) {
2443         case REQ_MOVE_UP:
2444         case REQ_MOVE_DOWN:
2445         case REQ_MOVE_PAGE_UP:
2446         case REQ_MOVE_PAGE_DOWN:
2447         case REQ_MOVE_FIRST_LINE:
2448         case REQ_MOVE_LAST_LINE:
2449                 move_view(view, request);
2450                 break;
2452         case REQ_SCROLL_LINE_DOWN:
2453         case REQ_SCROLL_LINE_UP:
2454         case REQ_SCROLL_PAGE_DOWN:
2455         case REQ_SCROLL_PAGE_UP:
2456                 scroll_view(view, request);
2457                 break;
2459         case REQ_VIEW_BLOB:
2460                 if (!ref_blob[0]) {
2461                         report("No file chosen, press %s to open tree view",
2462                                get_key(REQ_VIEW_TREE));
2463                         break;
2464                 }
2465                 open_view(view, request, OPEN_DEFAULT);
2466                 break;
2468         case REQ_VIEW_PAGER:
2469                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2470                         report("No pager content, press %s to run command from prompt",
2471                                get_key(REQ_PROMPT));
2472                         break;
2473                 }
2474                 open_view(view, request, OPEN_DEFAULT);
2475                 break;
2477         case REQ_VIEW_STAGE:
2478                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2479                         report("No stage content, press %s to open the status view and choose file",
2480                                get_key(REQ_VIEW_STATUS));
2481                         break;
2482                 }
2483                 open_view(view, request, OPEN_DEFAULT);
2484                 break;
2486         case REQ_VIEW_STATUS:
2487                 if (opt_is_inside_work_tree == FALSE) {
2488                         report("The status view requires a working tree");
2489                         break;
2490                 }
2491                 open_view(view, request, OPEN_DEFAULT);
2492                 break;
2494         case REQ_VIEW_MAIN:
2495         case REQ_VIEW_DIFF:
2496         case REQ_VIEW_LOG:
2497         case REQ_VIEW_TREE:
2498         case REQ_VIEW_HELP:
2499                 open_view(view, request, OPEN_DEFAULT);
2500                 break;
2502         case REQ_NEXT:
2503         case REQ_PREVIOUS:
2504                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2506                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2507                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2508                    (view == VIEW(REQ_VIEW_STAGE) &&
2509                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2510                    (view == VIEW(REQ_VIEW_BLOB) &&
2511                      view->parent == VIEW(REQ_VIEW_TREE))) {
2512                         int line;
2514                         view = view->parent;
2515                         line = view->lineno;
2516                         move_view(view, request);
2517                         if (view_is_displayed(view))
2518                                 update_view_title(view);
2519                         if (line != view->lineno)
2520                                 view->ops->request(view, REQ_ENTER,
2521                                                    &view->line[view->lineno]);
2523                 } else {
2524                         move_view(view, request);
2525                 }
2526                 break;
2528         case REQ_VIEW_NEXT:
2529         {
2530                 int nviews = displayed_views();
2531                 int next_view = (current_view + 1) % nviews;
2533                 if (next_view == current_view) {
2534                         report("Only one view is displayed");
2535                         break;
2536                 }
2538                 current_view = next_view;
2539                 /* Blur out the title of the previous view. */
2540                 update_view_title(view);
2541                 report("");
2542                 break;
2543         }
2544         case REQ_REFRESH:
2545                 report("Refreshing is not yet supported for the %s view", view->name);
2546                 break;
2548         case REQ_TOGGLE_LINENO:
2549                 opt_line_number = !opt_line_number;
2550                 redraw_display();
2551                 break;
2553         case REQ_TOGGLE_REV_GRAPH:
2554                 opt_rev_graph = !opt_rev_graph;
2555                 redraw_display();
2556                 break;
2558         case REQ_PROMPT:
2559                 /* Always reload^Wrerun commands from the prompt. */
2560                 open_view(view, opt_request, OPEN_RELOAD);
2561                 break;
2563         case REQ_SEARCH:
2564         case REQ_SEARCH_BACK:
2565                 search_view(view, request);
2566                 break;
2568         case REQ_FIND_NEXT:
2569         case REQ_FIND_PREV:
2570                 find_next(view, request);
2571                 break;
2573         case REQ_STOP_LOADING:
2574                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2575                         view = &views[i];
2576                         if (view->pipe)
2577                                 report("Stopped loading the %s view", view->name),
2578                         end_update(view);
2579                 }
2580                 break;
2582         case REQ_SHOW_VERSION:
2583                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2584                 return TRUE;
2586         case REQ_SCREEN_RESIZE:
2587                 resize_display();
2588                 /* Fall-through */
2589         case REQ_SCREEN_REDRAW:
2590                 redraw_display();
2591                 break;
2593         case REQ_EDIT:
2594                 report("Nothing to edit");
2595                 break;
2598         case REQ_ENTER:
2599                 report("Nothing to enter");
2600                 break;
2603         case REQ_VIEW_CLOSE:
2604                 /* XXX: Mark closed views by letting view->parent point to the
2605                  * view itself. Parents to closed view should never be
2606                  * followed. */
2607                 if (view->parent &&
2608                     view->parent->parent != view->parent) {
2609                         memset(display, 0, sizeof(display));
2610                         current_view = 0;
2611                         display[current_view] = view->parent;
2612                         view->parent = view;
2613                         resize_display();
2614                         redraw_display();
2615                         break;
2616                 }
2617                 /* Fall-through */
2618         case REQ_QUIT:
2619                 return FALSE;
2621         default:
2622                 /* An unknown key will show most commonly used commands. */
2623                 report("Unknown key, press 'h' for help");
2624                 return TRUE;
2625         }
2627         return TRUE;
2631 /*
2632  * Pager backend
2633  */
2635 static bool
2636 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2638         char *text = line->data;
2639         enum line_type type = line->type;
2640         int attr;
2642         wmove(view->win, lineno, 0);
2644         if (selected) {
2645                 type = LINE_CURSOR;
2646                 wchgat(view->win, -1, 0, type, NULL);
2647         }
2649         attr = get_line_attr(type);
2650         wattrset(view->win, attr);
2652         if (opt_line_number || opt_tab_size < TABSIZE) {
2653                 static char spaces[] = "                    ";
2654                 int col_offset = 0, col = 0;
2656                 if (opt_line_number) {
2657                         unsigned long real_lineno = view->offset + lineno + 1;
2659                         if (real_lineno == 1 ||
2660                             (real_lineno % opt_num_interval) == 0) {
2661                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2663                         } else {
2664                                 waddnstr(view->win, spaces,
2665                                          MIN(view->digits, STRING_SIZE(spaces)));
2666                         }
2667                         waddstr(view->win, ": ");
2668                         col_offset = view->digits + 2;
2669                 }
2671                 while (text && col_offset + col < view->width) {
2672                         int cols_max = view->width - col_offset - col;
2673                         char *pos = text;
2674                         int cols;
2676                         if (*text == '\t') {
2677                                 text++;
2678                                 assert(sizeof(spaces) > TABSIZE);
2679                                 pos = spaces;
2680                                 cols = opt_tab_size - (col % opt_tab_size);
2682                         } else {
2683                                 text = strchr(text, '\t');
2684                                 cols = line ? text - pos : strlen(pos);
2685                         }
2687                         waddnstr(view->win, pos, MIN(cols, cols_max));
2688                         col += cols;
2689                 }
2691         } else {
2692                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2694                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2695         }
2697         return TRUE;
2700 static bool
2701 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2703         char refbuf[SIZEOF_STR];
2704         char *ref = NULL;
2705         FILE *pipe;
2707         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2708                 return TRUE;
2710         pipe = popen(refbuf, "r");
2711         if (!pipe)
2712                 return TRUE;
2714         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2715                 ref = chomp_string(ref);
2716         pclose(pipe);
2718         if (!ref || !*ref)
2719                 return TRUE;
2721         /* This is the only fatal call, since it can "corrupt" the buffer. */
2722         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2723                 return FALSE;
2725         return TRUE;
2728 static void
2729 add_pager_refs(struct view *view, struct line *line)
2731         char buf[SIZEOF_STR];
2732         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2733         struct ref **refs;
2734         size_t bufpos = 0, refpos = 0;
2735         const char *sep = "Refs: ";
2736         bool is_tag = FALSE;
2738         assert(line->type == LINE_COMMIT);
2740         refs = get_refs(commit_id);
2741         if (!refs) {
2742                 if (view == VIEW(REQ_VIEW_DIFF))
2743                         goto try_add_describe_ref;
2744                 return;
2745         }
2747         do {
2748                 struct ref *ref = refs[refpos];
2749                 char *fmt = ref->tag    ? "%s[%s]" :
2750                             ref->remote ? "%s<%s>" : "%s%s";
2752                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2753                         return;
2754                 sep = ", ";
2755                 if (ref->tag)
2756                         is_tag = TRUE;
2757         } while (refs[refpos++]->next);
2759         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2760 try_add_describe_ref:
2761                 /* Add <tag>-g<commit_id> "fake" reference. */
2762                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2763                         return;
2764         }
2766         if (bufpos == 0)
2767                 return;
2769         if (!realloc_lines(view, view->line_size + 1))
2770                 return;
2772         add_line_text(view, buf, LINE_PP_REFS);
2775 static bool
2776 pager_read(struct view *view, char *data)
2778         struct line *line;
2780         if (!data)
2781                 return TRUE;
2783         line = add_line_text(view, data, get_line_type(data));
2784         if (!line)
2785                 return FALSE;
2787         if (line->type == LINE_COMMIT &&
2788             (view == VIEW(REQ_VIEW_DIFF) ||
2789              view == VIEW(REQ_VIEW_LOG)))
2790                 add_pager_refs(view, line);
2792         return TRUE;
2795 static enum request
2796 pager_request(struct view *view, enum request request, struct line *line)
2798         int split = 0;
2800         if (request != REQ_ENTER)
2801                 return request;
2803         if (line->type == LINE_COMMIT &&
2804            (view == VIEW(REQ_VIEW_LOG) ||
2805             view == VIEW(REQ_VIEW_PAGER))) {
2806                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2807                 split = 1;
2808         }
2810         /* Always scroll the view even if it was split. That way
2811          * you can use Enter to scroll through the log view and
2812          * split open each commit diff. */
2813         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2815         /* FIXME: A minor workaround. Scrolling the view will call report("")
2816          * but if we are scrolling a non-current view this won't properly
2817          * update the view title. */
2818         if (split)
2819                 update_view_title(view);
2821         return REQ_NONE;
2824 static bool
2825 pager_grep(struct view *view, struct line *line)
2827         regmatch_t pmatch;
2828         char *text = line->data;
2830         if (!*text)
2831                 return FALSE;
2833         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2834                 return FALSE;
2836         return TRUE;
2839 static void
2840 pager_select(struct view *view, struct line *line)
2842         if (line->type == LINE_COMMIT) {
2843                 char *text = (char *)line->data + STRING_SIZE("commit ");
2845                 if (view != VIEW(REQ_VIEW_PAGER))
2846                         string_copy_rev(view->ref, text);
2847                 string_copy_rev(ref_commit, text);
2848         }
2851 static struct view_ops pager_ops = {
2852         "line",
2853         NULL,
2854         pager_read,
2855         pager_draw,
2856         pager_request,
2857         pager_grep,
2858         pager_select,
2859 };
2862 /*
2863  * Help backend
2864  */
2866 static bool
2867 help_open(struct view *view)
2869         char buf[BUFSIZ];
2870         int lines = ARRAY_SIZE(req_info) + 2;
2871         int i;
2873         if (view->lines > 0)
2874                 return TRUE;
2876         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2877                 if (!req_info[i].request)
2878                         lines++;
2880         lines += run_requests + 1;
2882         view->line = calloc(lines, sizeof(*view->line));
2883         if (!view->line)
2884                 return FALSE;
2886         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2888         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2889                 char *key;
2891                 if (req_info[i].request == REQ_NONE)
2892                         continue;
2894                 if (!req_info[i].request) {
2895                         add_line_text(view, "", LINE_DEFAULT);
2896                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2897                         continue;
2898                 }
2900                 key = get_key(req_info[i].request);
2901                 if (!*key)
2902                         key = "(no key defined)";
2904                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2905                         continue;
2907                 add_line_text(view, buf, LINE_DEFAULT);
2908         }
2910         if (run_requests) {
2911                 add_line_text(view, "", LINE_DEFAULT);
2912                 add_line_text(view, "External commands:", LINE_DEFAULT);
2913         }
2915         for (i = 0; i < run_requests; i++) {
2916                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2917                 char *key;
2919                 if (!req)
2920                         continue;
2922                 key = get_key_name(req->key);
2923                 if (!*key)
2924                         key = "(no key defined)";
2926                 if (!string_format(buf, "    %-10s %-14s `%s`",
2927                                    keymap_table[req->keymap].name,
2928                                    key, req->cmd))
2929                         continue;
2931                 add_line_text(view, buf, LINE_DEFAULT);
2932         }
2934         return TRUE;
2937 static struct view_ops help_ops = {
2938         "line",
2939         help_open,
2940         NULL,
2941         pager_draw,
2942         pager_request,
2943         pager_grep,
2944         pager_select,
2945 };
2948 /*
2949  * Tree backend
2950  */
2952 struct tree_stack_entry {
2953         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2954         unsigned long lineno;           /* Line number to restore */
2955         char *name;                     /* Position of name in opt_path */
2956 };
2958 /* The top of the path stack. */
2959 static struct tree_stack_entry *tree_stack = NULL;
2960 unsigned long tree_lineno = 0;
2962 static void
2963 pop_tree_stack_entry(void)
2965         struct tree_stack_entry *entry = tree_stack;
2967         tree_lineno = entry->lineno;
2968         entry->name[0] = 0;
2969         tree_stack = entry->prev;
2970         free(entry);
2973 static void
2974 push_tree_stack_entry(char *name, unsigned long lineno)
2976         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2977         size_t pathlen = strlen(opt_path);
2979         if (!entry)
2980                 return;
2982         entry->prev = tree_stack;
2983         entry->name = opt_path + pathlen;
2984         tree_stack = entry;
2986         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2987                 pop_tree_stack_entry();
2988                 return;
2989         }
2991         /* Move the current line to the first tree entry. */
2992         tree_lineno = 1;
2993         entry->lineno = lineno;
2996 /* Parse output from git-ls-tree(1):
2997  *
2998  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2999  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3000  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3001  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3002  */
3004 #define SIZEOF_TREE_ATTR \
3005         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3007 #define TREE_UP_FORMAT "040000 tree %s\t.."
3009 static int
3010 tree_compare_entry(enum line_type type1, char *name1,
3011                    enum line_type type2, char *name2)
3013         if (type1 != type2) {
3014                 if (type1 == LINE_TREE_DIR)
3015                         return -1;
3016                 return 1;
3017         }
3019         return strcmp(name1, name2);
3022 static bool
3023 tree_read(struct view *view, char *text)
3025         size_t textlen = text ? strlen(text) : 0;
3026         char buf[SIZEOF_STR];
3027         unsigned long pos;
3028         enum line_type type;
3029         bool first_read = view->lines == 0;
3031         if (textlen <= SIZEOF_TREE_ATTR)
3032                 return FALSE;
3034         type = text[STRING_SIZE("100644 ")] == 't'
3035              ? LINE_TREE_DIR : LINE_TREE_FILE;
3037         if (first_read) {
3038                 /* Add path info line */
3039                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3040                     !realloc_lines(view, view->line_size + 1) ||
3041                     !add_line_text(view, buf, LINE_DEFAULT))
3042                         return FALSE;
3044                 /* Insert "link" to parent directory. */
3045                 if (*opt_path) {
3046                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3047                             !realloc_lines(view, view->line_size + 1) ||
3048                             !add_line_text(view, buf, LINE_TREE_DIR))
3049                                 return FALSE;
3050                 }
3051         }
3053         /* Strip the path part ... */
3054         if (*opt_path) {
3055                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3056                 size_t striplen = strlen(opt_path);
3057                 char *path = text + SIZEOF_TREE_ATTR;
3059                 if (pathlen > striplen)
3060                         memmove(path, path + striplen,
3061                                 pathlen - striplen + 1);
3062         }
3064         /* Skip "Directory ..." and ".." line. */
3065         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3066                 struct line *line = &view->line[pos];
3067                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3068                 char *path2 = text + SIZEOF_TREE_ATTR;
3069                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3071                 if (cmp <= 0)
3072                         continue;
3074                 text = strdup(text);
3075                 if (!text)
3076                         return FALSE;
3078                 if (view->lines > pos)
3079                         memmove(&view->line[pos + 1], &view->line[pos],
3080                                 (view->lines - pos) * sizeof(*line));
3082                 line = &view->line[pos];
3083                 line->data = text;
3084                 line->type = type;
3085                 view->lines++;
3086                 return TRUE;
3087         }
3089         if (!add_line_text(view, text, type))
3090                 return FALSE;
3092         if (tree_lineno > view->lineno) {
3093                 view->lineno = tree_lineno;
3094                 tree_lineno = 0;
3095         }
3097         return TRUE;
3100 static enum request
3101 tree_request(struct view *view, enum request request, struct line *line)
3103         enum open_flags flags;
3105         if (request == REQ_TREE_PARENT) {
3106                 if (*opt_path) {
3107                         /* fake 'cd  ..' */
3108                         request = REQ_ENTER;
3109                         line = &view->line[1];
3110                 } else {
3111                         /* quit view if at top of tree */
3112                         return REQ_VIEW_CLOSE;
3113                 }
3114         }
3115         if (request != REQ_ENTER)
3116                 return request;
3118         /* Cleanup the stack if the tree view is at a different tree. */
3119         while (!*opt_path && tree_stack)
3120                 pop_tree_stack_entry();
3122         switch (line->type) {
3123         case LINE_TREE_DIR:
3124                 /* Depending on whether it is a subdir or parent (updir?) link
3125                  * mangle the path buffer. */
3126                 if (line == &view->line[1] && *opt_path) {
3127                         pop_tree_stack_entry();
3129                 } else {
3130                         char *data = line->data;
3131                         char *basename = data + SIZEOF_TREE_ATTR;
3133                         push_tree_stack_entry(basename, view->lineno);
3134                 }
3136                 /* Trees and subtrees share the same ID, so they are not not
3137                  * unique like blobs. */
3138                 flags = OPEN_RELOAD;
3139                 request = REQ_VIEW_TREE;
3140                 break;
3142         case LINE_TREE_FILE:
3143                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3144                 request = REQ_VIEW_BLOB;
3145                 break;
3147         default:
3148                 return TRUE;
3149         }
3151         open_view(view, request, flags);
3152         if (request == REQ_VIEW_TREE) {
3153                 view->lineno = tree_lineno;
3154         }
3156         return REQ_NONE;
3159 static void
3160 tree_select(struct view *view, struct line *line)
3162         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3164         if (line->type == LINE_TREE_FILE) {
3165                 string_copy_rev(ref_blob, text);
3167         } else if (line->type != LINE_TREE_DIR) {
3168                 return;
3169         }
3171         string_copy_rev(view->ref, text);
3174 static struct view_ops tree_ops = {
3175         "file",
3176         NULL,
3177         tree_read,
3178         pager_draw,
3179         tree_request,
3180         pager_grep,
3181         tree_select,
3182 };
3184 static bool
3185 blob_read(struct view *view, char *line)
3187         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3190 static struct view_ops blob_ops = {
3191         "line",
3192         NULL,
3193         blob_read,
3194         pager_draw,
3195         pager_request,
3196         pager_grep,
3197         pager_select,
3198 };
3201 /*
3202  * Status backend
3203  */
3205 struct status {
3206         char status;
3207         struct {
3208                 mode_t mode;
3209                 char rev[SIZEOF_REV];
3210                 char name[SIZEOF_STR];
3211         } old;
3212         struct {
3213                 mode_t mode;
3214                 char rev[SIZEOF_REV];
3215                 char name[SIZEOF_STR];
3216         } new;
3217 };
3219 static struct status stage_status;
3220 static enum line_type stage_line_type;
3222 /* Get fields from the diff line:
3223  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3224  */
3225 static inline bool
3226 status_get_diff(struct status *file, char *buf, size_t bufsize)
3228         char *old_mode = buf +  1;
3229         char *new_mode = buf +  8;
3230         char *old_rev  = buf + 15;
3231         char *new_rev  = buf + 56;
3232         char *status   = buf + 97;
3234         if (bufsize < 99 ||
3235             old_mode[-1] != ':' ||
3236             new_mode[-1] != ' ' ||
3237             old_rev[-1]  != ' ' ||
3238             new_rev[-1]  != ' ' ||
3239             status[-1]   != ' ')
3240                 return FALSE;
3242         file->status = *status;
3244         string_copy_rev(file->old.rev, old_rev);
3245         string_copy_rev(file->new.rev, new_rev);
3247         file->old.mode = strtoul(old_mode, NULL, 8);
3248         file->new.mode = strtoul(new_mode, NULL, 8);
3250         file->old.name[0] = file->new.name[0] = 0;
3252         return TRUE;
3255 static bool
3256 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3258         struct status *file = NULL;
3259         struct status *unmerged = NULL;
3260         char buf[SIZEOF_STR * 4];
3261         size_t bufsize = 0;
3262         FILE *pipe;
3264         pipe = popen(cmd, "r");
3265         if (!pipe)
3266                 return FALSE;
3268         add_line_data(view, NULL, type);
3270         while (!feof(pipe) && !ferror(pipe)) {
3271                 char *sep;
3272                 size_t readsize;
3274                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3275                 if (!readsize)
3276                         break;
3277                 bufsize += readsize;
3279                 /* Process while we have NUL chars. */
3280                 while ((sep = memchr(buf, 0, bufsize))) {
3281                         size_t sepsize = sep - buf + 1;
3283                         if (!file) {
3284                                 if (!realloc_lines(view, view->line_size + 1))
3285                                         goto error_out;
3287                                 file = calloc(1, sizeof(*file));
3288                                 if (!file)
3289                                         goto error_out;
3291                                 add_line_data(view, file, type);
3292                         }
3294                         /* Parse diff info part. */
3295                         if (!diff) {
3296                                 file->status = '?';
3298                         } else if (!file->status) {
3299                                 if (!status_get_diff(file, buf, sepsize))
3300                                         goto error_out;
3302                                 bufsize -= sepsize;
3303                                 memmove(buf, sep + 1, bufsize);
3305                                 sep = memchr(buf, 0, bufsize);
3306                                 if (!sep)
3307                                         break;
3308                                 sepsize = sep - buf + 1;
3310                                 /* Collapse all 'M'odified entries that
3311                                  * follow a associated 'U'nmerged entry.
3312                                  */
3313                                 if (file->status == 'U') {
3314                                         unmerged = file;
3316                                 } else if (unmerged) {
3317                                         int collapse = !strcmp(buf, unmerged->new.name);
3319                                         unmerged = NULL;
3320                                         if (collapse) {
3321                                                 free(file);
3322                                                 view->lines--;
3323                                                 continue;
3324                                         }
3325                                 }
3326                         }
3328                         /* Grab the old name for rename/copy. */
3329                         if (!*file->old.name &&
3330                             (file->status == 'R' || file->status == 'C')) {
3331                                 sepsize = sep - buf + 1;
3332                                 string_ncopy(file->old.name, buf, sepsize);
3333                                 bufsize -= sepsize;
3334                                 memmove(buf, sep + 1, bufsize);
3336                                 sep = memchr(buf, 0, bufsize);
3337                                 if (!sep)
3338                                         break;
3339                                 sepsize = sep - buf + 1;
3340                         }
3342                         /* git-ls-files just delivers a NUL separated
3343                          * list of file names similar to the second half
3344                          * of the git-diff-* output. */
3345                         string_ncopy(file->new.name, buf, sepsize);
3346                         if (!*file->old.name)
3347                                 string_copy(file->old.name, file->new.name);
3348                         bufsize -= sepsize;
3349                         memmove(buf, sep + 1, bufsize);
3350                         file = NULL;
3351                 }
3352         }
3354         if (ferror(pipe)) {
3355 error_out:
3356                 pclose(pipe);
3357                 return FALSE;
3358         }
3360         if (!view->line[view->lines - 1].data)
3361                 add_line_data(view, NULL, LINE_STAT_NONE);
3363         pclose(pipe);
3364         return TRUE;
3367 /* Don't show unmerged entries in the staged section. */
3368 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3369 #define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
3370 #define STATUS_LIST_OTHER_CMD \
3371         "git ls-files -z --others --exclude-per-directory=.gitignore"
3373 #define STATUS_DIFF_INDEX_SHOW_CMD \
3374         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3376 #define STATUS_DIFF_FILES_SHOW_CMD \
3377         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3379 /* First parse staged info using git-diff-index(1), then parse unstaged
3380  * info using git-diff-files(1), and finally untracked files using
3381  * git-ls-files(1). */
3382 static bool
3383 status_open(struct view *view)
3385         struct stat statbuf;
3386         char exclude[SIZEOF_STR];
3387         char cmd[SIZEOF_STR];
3388         unsigned long prev_lineno = view->lineno;
3389         size_t i;
3391         for (i = 0; i < view->lines; i++)
3392                 free(view->line[i].data);
3393         free(view->line);
3394         view->lines = view->line_size = view->lineno = 0;
3395         view->line = NULL;
3397         if (!realloc_lines(view, view->line_size + 6))
3398                 return FALSE;
3400         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3401                 return FALSE;
3403         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3405         if (stat(exclude, &statbuf) >= 0) {
3406                 size_t cmdsize = strlen(cmd);
3408                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3409                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3410                         return FALSE;
3411         }
3413         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3414             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3415             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3416                 return FALSE;
3418         /* If all went well restore the previous line number to stay in
3419          * the context. */
3420         if (prev_lineno < view->lines)
3421                 view->lineno = prev_lineno;
3422         else
3423                 view->lineno = view->lines - 1;
3425         return TRUE;
3428 static bool
3429 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3431         struct status *status = line->data;
3432         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3434         wmove(view->win, lineno, 0);
3436         if (selected) {
3437                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3438                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3439                 tilde_attr = -1;
3441         } else if (!status && line->type != LINE_STAT_NONE) {
3442                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3443                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3445         } else {
3446                 wattrset(view->win, get_line_attr(line->type));
3447         }
3449         if (!status) {
3450                 char *text;
3452                 switch (line->type) {
3453                 case LINE_STAT_STAGED:
3454                         text = "Changes to be committed:";
3455                         break;
3457                 case LINE_STAT_UNSTAGED:
3458                         text = "Changed but not updated:";
3459                         break;
3461                 case LINE_STAT_UNTRACKED:
3462                         text = "Untracked files:";
3463                         break;
3465                 case LINE_STAT_NONE:
3466                         text = "    (no files)";
3467                         break;
3469                 default:
3470                         return FALSE;
3471                 }
3473                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3474                 return TRUE;
3475         }
3477         waddch(view->win, status->status);
3478         if (!selected)
3479                 wattrset(view->win, A_NORMAL);
3480         wmove(view->win, lineno, 4);
3481         if (view->width < 5)
3482                 return TRUE;
3484         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3485         return TRUE;
3488 static enum request
3489 status_enter(struct view *view, struct line *line)
3491         struct status *status = line->data;
3492         char oldpath[SIZEOF_STR] = "";
3493         char newpath[SIZEOF_STR] = "";
3494         char *info;
3495         size_t cmdsize = 0;
3497         if (line->type == LINE_STAT_NONE ||
3498             (!status && line[1].type == LINE_STAT_NONE)) {
3499                 report("No file to diff");
3500                 return REQ_NONE;
3501         }
3503         if (status) {
3504                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3505                         return REQ_QUIT;
3506                 /* Diffs for unmerged entries are empty when pasing the
3507                  * new path, so leave it empty. */
3508                 if (status->status != 'U' &&
3509                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3510                         return REQ_QUIT;
3511         }
3513         if (opt_cdup[0] &&
3514             line->type != LINE_STAT_UNTRACKED &&
3515             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3516                 return REQ_QUIT;
3518         switch (line->type) {
3519         case LINE_STAT_STAGED:
3520                 if (!string_format_from(opt_cmd, &cmdsize,
3521                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3522                         return REQ_QUIT;
3523                 if (status)
3524                         info = "Staged changes to %s";
3525                 else
3526                         info = "Staged changes";
3527                 break;
3529         case LINE_STAT_UNSTAGED:
3530                 if (!string_format_from(opt_cmd, &cmdsize,
3531                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3532                         return REQ_QUIT;
3533                 if (status)
3534                         info = "Unstaged changes to %s";
3535                 else
3536                         info = "Unstaged changes";
3537                 break;
3539         case LINE_STAT_UNTRACKED:
3540                 if (opt_pipe)
3541                         return REQ_QUIT;
3544                 if (!status) {
3545                         report("No file to show");
3546                         return REQ_NONE;
3547                 }
3549                 opt_pipe = fopen(status->new.name, "r");
3550                 info = "Untracked file %s";
3551                 break;
3553         default:
3554                 die("line type %d not handled in switch", line->type);
3555         }
3557         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3558         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3559                 if (status) {
3560                         stage_status = *status;
3561                 } else {
3562                         memset(&stage_status, 0, sizeof(stage_status));
3563                 }
3565                 stage_line_type = line->type;
3566                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3567         }
3569         return REQ_NONE;
3573 static bool
3574 status_update_file(struct view *view, struct status *status, enum line_type type)
3576         char cmd[SIZEOF_STR];
3577         char buf[SIZEOF_STR];
3578         size_t cmdsize = 0;
3579         size_t bufsize = 0;
3580         size_t written = 0;
3581         FILE *pipe;
3583         if (opt_cdup[0] &&
3584             type != LINE_STAT_UNTRACKED &&
3585             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3586                 return FALSE;
3588         switch (type) {
3589         case LINE_STAT_STAGED:
3590                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3591                                         status->old.mode,
3592                                         status->old.rev,
3593                                         status->old.name, 0))
3594                         return FALSE;
3596                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3597                 break;
3599         case LINE_STAT_UNSTAGED:
3600         case LINE_STAT_UNTRACKED:
3601                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3602                         return FALSE;
3604                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3605                 break;
3607         default:
3608                 die("line type %d not handled in switch", type);
3609         }
3611         pipe = popen(cmd, "w");
3612         if (!pipe)
3613                 return FALSE;
3615         while (!ferror(pipe) && written < bufsize) {
3616                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3617         }
3619         pclose(pipe);
3621         if (written != bufsize)
3622                 return FALSE;
3624         return TRUE;
3627 static void
3628 status_update(struct view *view)
3630         struct line *line = &view->line[view->lineno];
3632         assert(view->lines);
3634         if (!line->data) {
3635                 while (++line < view->line + view->lines && line->data) {
3636                         if (!status_update_file(view, line->data, line->type))
3637                                 report("Failed to update file status");
3638                 }
3640                 if (!line[-1].data) {
3641                         report("Nothing to update");
3642                         return;
3643                 }
3645         } else if (!status_update_file(view, line->data, line->type)) {
3646                 report("Failed to update file status");
3647         }
3650 static enum request
3651 status_request(struct view *view, enum request request, struct line *line)
3653         struct status *status = line->data;
3655         switch (request) {
3656         case REQ_STATUS_UPDATE:
3657                 status_update(view);
3658                 break;
3660         case REQ_STATUS_MERGE:
3661                 if (!status || status->status != 'U') {
3662                         report("Merging only possible for files with unmerged status ('U').");
3663                         return REQ_NONE;
3664                 }
3665                 open_mergetool(status->new.name);
3666                 break;
3668         case REQ_EDIT:
3669                 if (!status)
3670                         return request;
3672                 open_editor(status->status != '?', status->new.name);
3673                 break;
3675         case REQ_ENTER:
3676                 /* After returning the status view has been split to
3677                  * show the stage view. No further reloading is
3678                  * necessary. */
3679                 status_enter(view, line);
3680                 return REQ_NONE;
3682         case REQ_REFRESH:
3683                 /* Simply reload the view. */
3684                 break;
3686         default:
3687                 return request;
3688         }
3690         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3692         return REQ_NONE;
3695 static void
3696 status_select(struct view *view, struct line *line)
3698         struct status *status = line->data;
3699         char file[SIZEOF_STR] = "all files";
3700         char *text;
3701         char *key;
3703         if (status && !string_format(file, "'%s'", status->new.name))
3704                 return;
3706         if (!status && line[1].type == LINE_STAT_NONE)
3707                 line++;
3709         switch (line->type) {
3710         case LINE_STAT_STAGED:
3711                 text = "Press %s to unstage %s for commit";
3712                 break;
3714         case LINE_STAT_UNSTAGED:
3715                 text = "Press %s to stage %s for commit";
3716                 break;
3718         case LINE_STAT_UNTRACKED:
3719                 text = "Press %s to stage %s for addition";
3720                 break;
3722         case LINE_STAT_NONE:
3723                 text = "Nothing to update";
3724                 break;
3726         default:
3727                 die("line type %d not handled in switch", line->type);
3728         }
3730         if (status && status->status == 'U') {
3731                 text = "Press %s to resolve conflict in %s";
3732                 key = get_key(REQ_STATUS_MERGE);
3734         } else {
3735                 key = get_key(REQ_STATUS_UPDATE);
3736         }
3738         string_format(view->ref, text, key, file);
3741 static bool
3742 status_grep(struct view *view, struct line *line)
3744         struct status *status = line->data;
3745         enum { S_STATUS, S_NAME, S_END } state;
3746         char buf[2] = "?";
3747         regmatch_t pmatch;
3749         if (!status)
3750                 return FALSE;
3752         for (state = S_STATUS; state < S_END; state++) {
3753                 char *text;
3755                 switch (state) {
3756                 case S_NAME:    text = status->new.name;        break;
3757                 case S_STATUS:
3758                         buf[0] = status->status;
3759                         text = buf;
3760                         break;
3762                 default:
3763                         return FALSE;
3764                 }
3766                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3767                         return TRUE;
3768         }
3770         return FALSE;
3773 static struct view_ops status_ops = {
3774         "file",
3775         status_open,
3776         NULL,
3777         status_draw,
3778         status_request,
3779         status_grep,
3780         status_select,
3781 };
3784 static bool
3785 stage_diff_line(FILE *pipe, struct line *line)
3787         char *buf = line->data;
3788         size_t bufsize = strlen(buf);
3789         size_t written = 0;
3791         while (!ferror(pipe) && written < bufsize) {
3792                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3793         }
3795         fputc('\n', pipe);
3797         return written == bufsize;
3800 static struct line *
3801 stage_diff_hdr(struct view *view, struct line *line)
3803         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3804         struct line *diff_hdr;
3806         if (line->type == LINE_DIFF_CHUNK)
3807                 diff_hdr = line - 1;
3808         else
3809                 diff_hdr = view->line + 1;
3811         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3812                 if (diff_hdr->type == LINE_DIFF_HEADER)
3813                         return diff_hdr;
3815                 diff_hdr += diff_hdr_dir;
3816         }
3818         return NULL;
3821 static bool
3822 stage_update_chunk(struct view *view, struct line *line)
3824         char cmd[SIZEOF_STR];
3825         size_t cmdsize = 0;
3826         struct line *diff_hdr, *diff_chunk, *diff_end;
3827         FILE *pipe;
3829         diff_hdr = stage_diff_hdr(view, line);
3830         if (!diff_hdr)
3831                 return FALSE;
3833         if (opt_cdup[0] &&
3834             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3835                 return FALSE;
3837         if (!string_format_from(cmd, &cmdsize,
3838                                 "git apply --cached %s - && "
3839                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3840                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3841                 return FALSE;
3843         pipe = popen(cmd, "w");
3844         if (!pipe)
3845                 return FALSE;
3847         diff_end = view->line + view->lines;
3848         if (line->type != LINE_DIFF_CHUNK) {
3849                 diff_chunk = diff_hdr;
3851         } else {
3852                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3853                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3854                             diff_chunk->type == LINE_DIFF_HEADER)
3855                                 diff_end = diff_chunk;
3857                 diff_chunk = line;
3859                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3860                         switch (diff_hdr->type) {
3861                         case LINE_DIFF_HEADER:
3862                         case LINE_DIFF_INDEX:
3863                         case LINE_DIFF_ADD:
3864                         case LINE_DIFF_DEL:
3865                                 break;
3867                         default:
3868                                 diff_hdr++;
3869                                 continue;
3870                         }
3872                         if (!stage_diff_line(pipe, diff_hdr++)) {
3873                                 pclose(pipe);
3874                                 return FALSE;
3875                         }
3876                 }
3877         }
3879         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3880                 diff_chunk++;
3882         pclose(pipe);
3884         if (diff_chunk != diff_end)
3885                 return FALSE;
3887         return TRUE;
3890 static void
3891 stage_update(struct view *view, struct line *line)
3893         if (stage_line_type != LINE_STAT_UNTRACKED &&
3894             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3895                 if (!stage_update_chunk(view, line)) {
3896                         report("Failed to apply chunk");
3897                         return;
3898                 }
3900         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3901                 report("Failed to update file");
3902                 return;
3903         }
3905         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3907         view = VIEW(REQ_VIEW_STATUS);
3908         if (view_is_displayed(view))
3909                 status_enter(view, &view->line[view->lineno]);
3912 static enum request
3913 stage_request(struct view *view, enum request request, struct line *line)
3915         switch (request) {
3916         case REQ_STATUS_UPDATE:
3917                 stage_update(view, line);
3918                 break;
3920         case REQ_EDIT:
3921                 if (!stage_status.new.name[0])
3922                         return request;
3924                 open_editor(stage_status.status != '?', stage_status.new.name);
3925                 break;
3927         case REQ_ENTER:
3928                 pager_request(view, request, line);
3929                 break;
3931         default:
3932                 return request;
3933         }
3935         return REQ_NONE;
3938 static struct view_ops stage_ops = {
3939         "line",
3940         NULL,
3941         pager_read,
3942         pager_draw,
3943         stage_request,
3944         pager_grep,
3945         pager_select,
3946 };
3949 /*
3950  * Revision graph
3951  */
3953 struct commit {
3954         char id[SIZEOF_REV];            /* SHA1 ID. */
3955         char title[128];                /* First line of the commit message. */
3956         char author[75];                /* Author of the commit. */
3957         struct tm time;                 /* Date from the author ident. */
3958         struct ref **refs;              /* Repository references. */
3959         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3960         size_t graph_size;              /* The width of the graph array. */
3961 };
3963 /* Size of rev graph with no  "padding" columns */
3964 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3966 struct rev_graph {
3967         struct rev_graph *prev, *next, *parents;
3968         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3969         size_t size;
3970         struct commit *commit;
3971         size_t pos;
3972         unsigned int boundary:1;
3973 };
3975 /* Parents of the commit being visualized. */
3976 static struct rev_graph graph_parents[4];
3978 /* The current stack of revisions on the graph. */
3979 static struct rev_graph graph_stacks[4] = {
3980         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3981         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3982         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3983         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3984 };
3986 static inline bool
3987 graph_parent_is_merge(struct rev_graph *graph)
3989         return graph->parents->size > 1;
3992 static inline void
3993 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3995         struct commit *commit = graph->commit;
3997         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3998                 commit->graph[commit->graph_size++] = symbol;
4001 static void
4002 done_rev_graph(struct rev_graph *graph)
4004         if (graph_parent_is_merge(graph) &&
4005             graph->pos < graph->size - 1 &&
4006             graph->next->size == graph->size + graph->parents->size - 1) {
4007                 size_t i = graph->pos + graph->parents->size - 1;
4009                 graph->commit->graph_size = i * 2;
4010                 while (i < graph->next->size - 1) {
4011                         append_to_rev_graph(graph, ' ');
4012                         append_to_rev_graph(graph, '\\');
4013                         i++;
4014                 }
4015         }
4017         graph->size = graph->pos = 0;
4018         graph->commit = NULL;
4019         memset(graph->parents, 0, sizeof(*graph->parents));
4022 static void
4023 push_rev_graph(struct rev_graph *graph, char *parent)
4025         int i;
4027         /* "Collapse" duplicate parents lines.
4028          *
4029          * FIXME: This needs to also update update the drawn graph but
4030          * for now it just serves as a method for pruning graph lines. */
4031         for (i = 0; i < graph->size; i++)
4032                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4033                         return;
4035         if (graph->size < SIZEOF_REVITEMS) {
4036                 string_copy_rev(graph->rev[graph->size++], parent);
4037         }
4040 static chtype
4041 get_rev_graph_symbol(struct rev_graph *graph)
4043         chtype symbol;
4045         if (graph->boundary)
4046                 symbol = REVGRAPH_BOUND;
4047         else if (graph->parents->size == 0)
4048                 symbol = REVGRAPH_INIT;
4049         else if (graph_parent_is_merge(graph))
4050                 symbol = REVGRAPH_MERGE;
4051         else if (graph->pos >= graph->size)
4052                 symbol = REVGRAPH_BRANCH;
4053         else
4054                 symbol = REVGRAPH_COMMIT;
4056         return symbol;
4059 static void
4060 draw_rev_graph(struct rev_graph *graph)
4062         struct rev_filler {
4063                 chtype separator, line;
4064         };
4065         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4066         static struct rev_filler fillers[] = {
4067                 { ' ',  REVGRAPH_LINE },
4068                 { '`',  '.' },
4069                 { '\'', ' ' },
4070                 { '/',  ' ' },
4071         };
4072         chtype symbol = get_rev_graph_symbol(graph);
4073         struct rev_filler *filler;
4074         size_t i;
4076         filler = &fillers[DEFAULT];
4078         for (i = 0; i < graph->pos; i++) {
4079                 append_to_rev_graph(graph, filler->line);
4080                 if (graph_parent_is_merge(graph->prev) &&
4081                     graph->prev->pos == i)
4082                         filler = &fillers[RSHARP];
4084                 append_to_rev_graph(graph, filler->separator);
4085         }
4087         /* Place the symbol for this revision. */
4088         append_to_rev_graph(graph, symbol);
4090         if (graph->prev->size > graph->size)
4091                 filler = &fillers[RDIAG];
4092         else
4093                 filler = &fillers[DEFAULT];
4095         i++;
4097         for (; i < graph->size; i++) {
4098                 append_to_rev_graph(graph, filler->separator);
4099                 append_to_rev_graph(graph, filler->line);
4100                 if (graph_parent_is_merge(graph->prev) &&
4101                     i < graph->prev->pos + graph->parents->size)
4102                         filler = &fillers[RSHARP];
4103                 if (graph->prev->size > graph->size)
4104                         filler = &fillers[LDIAG];
4105         }
4107         if (graph->prev->size > graph->size) {
4108                 append_to_rev_graph(graph, filler->separator);
4109                 if (filler->line != ' ')
4110                         append_to_rev_graph(graph, filler->line);
4111         }
4114 /* Prepare the next rev graph */
4115 static void
4116 prepare_rev_graph(struct rev_graph *graph)
4118         size_t i;
4120         /* First, traverse all lines of revisions up to the active one. */
4121         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4122                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4123                         break;
4125                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4126         }
4128         /* Interleave the new revision parent(s). */
4129         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4130                 push_rev_graph(graph->next, graph->parents->rev[i]);
4132         /* Lastly, put any remaining revisions. */
4133         for (i = graph->pos + 1; i < graph->size; i++)
4134                 push_rev_graph(graph->next, graph->rev[i]);
4137 static void
4138 update_rev_graph(struct rev_graph *graph)
4140         /* If this is the finalizing update ... */
4141         if (graph->commit)
4142                 prepare_rev_graph(graph);
4144         /* Graph visualization needs a one rev look-ahead,
4145          * so the first update doesn't visualize anything. */
4146         if (!graph->prev->commit)
4147                 return;
4149         draw_rev_graph(graph->prev);
4150         done_rev_graph(graph->prev->prev);
4154 /*
4155  * Main view backend
4156  */
4158 static bool
4159 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4161         char buf[DATE_COLS + 1];
4162         struct commit *commit = line->data;
4163         enum line_type type;
4164         int col = 0;
4165         size_t timelen;
4166         int tilde_attr;
4167         int space;
4169         if (!*commit->author)
4170                 return FALSE;
4172         space = view->width;
4173         wmove(view->win, lineno, col);
4175         if (selected) {
4176                 type = LINE_CURSOR;
4177                 wattrset(view->win, get_line_attr(type));
4178                 wchgat(view->win, -1, 0, type, NULL);
4179                 tilde_attr = -1;
4180         } else {
4181                 type = LINE_MAIN_COMMIT;
4182                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4183                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4184         }
4186         {
4187                 int n;
4189                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4190                 n = draw_text(
4191                         view, buf, view->width - col, col, FALSE, tilde_attr);
4192                 draw_text(
4193                         view, " ", view->width - col - n, col + n, FALSE,
4194                         tilde_attr);
4196                 col += DATE_COLS;
4197                 wmove(view->win, lineno, col);
4198                 if (col >= view->width)
4199                         return TRUE;
4200         }
4201         if (type != LINE_CURSOR)
4202                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4204         {
4205                 int max_len;
4207                 max_len = view->width - col;
4208                 if (max_len > AUTHOR_COLS - 1)
4209                         max_len = AUTHOR_COLS - 1;
4210                 draw_text(
4211                         view, commit->author, max_len, col, TRUE, tilde_attr);
4212                 col += AUTHOR_COLS;
4213                 if (col >= view->width)
4214                         return TRUE;
4215         }
4217         if (opt_rev_graph && commit->graph_size) {
4218                 size_t graph_size = view->width - col;
4219                 size_t i;
4221                 if (type != LINE_CURSOR)
4222                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4223                 wmove(view->win, lineno, col);
4224                 if (graph_size > commit->graph_size)
4225                         graph_size = commit->graph_size;
4226                 /* Using waddch() instead of waddnstr() ensures that
4227                  * they'll be rendered correctly for the cursor line. */
4228                 for (i = 0; i < graph_size; i++)
4229                         waddch(view->win, commit->graph[i]);
4231                 col += commit->graph_size + 1;
4232                 if (col >= view->width)
4233                         return TRUE;
4234                 waddch(view->win, ' ');
4235         }
4236         if (type != LINE_CURSOR)
4237                 wattrset(view->win, A_NORMAL);
4239         wmove(view->win, lineno, col);
4241         if (commit->refs) {
4242                 size_t i = 0;
4244                 do {
4245                         if (type == LINE_CURSOR)
4246                                 ;
4247                         else if (commit->refs[i]->tag)
4248                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4249                         else if (commit->refs[i]->remote)
4250                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4251                         else
4252                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4254                         col += draw_text(
4255                                 view, "[", view->width - col, col, TRUE,
4256                                 tilde_attr);
4257                         col += draw_text(
4258                                 view, commit->refs[i]->name, view->width - col,
4259                                 col, TRUE, tilde_attr);
4260                         col += draw_text(
4261                                 view, "]", view->width - col, col, TRUE,
4262                                 tilde_attr);
4263                         if (type != LINE_CURSOR)
4264                                 wattrset(view->win, A_NORMAL);
4265                         col += draw_text(
4266                                 view, " ", view->width - col, col, TRUE,
4267                                 tilde_attr);
4268                         if (col >= view->width)
4269                                 return TRUE;
4270                 } while (commit->refs[i++]->next);
4271         }
4273         if (type != LINE_CURSOR)
4274                 wattrset(view->win, get_line_attr(type));
4276         col += draw_text(
4277                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4279         return TRUE;
4282 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4283 static bool
4284 main_read(struct view *view, char *line)
4286         static struct rev_graph *graph = graph_stacks;
4287         enum line_type type;
4288         struct commit *commit;
4290         if (!line) {
4291                 update_rev_graph(graph);
4292                 return TRUE;
4293         }
4295         type = get_line_type(line);
4296         if (type == LINE_COMMIT) {
4297                 commit = calloc(1, sizeof(struct commit));
4298                 if (!commit)
4299                         return FALSE;
4301                 line += STRING_SIZE("commit ");
4302                 if (*line == '-') {
4303                         graph->boundary = 1;
4304                         line++;
4305                 }
4307                 string_copy_rev(commit->id, line);
4308                 commit->refs = get_refs(commit->id);
4309                 graph->commit = commit;
4310                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4311                 return TRUE;
4312         }
4314         if (!view->lines)
4315                 return TRUE;
4316         commit = view->line[view->lines - 1].data;
4318         switch (type) {
4319         case LINE_PARENT:
4320                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4321                 break;
4323         case LINE_AUTHOR:
4324         {
4325                 /* Parse author lines where the name may be empty:
4326                  *      author  <email@address.tld> 1138474660 +0100
4327                  */
4328                 char *ident = line + STRING_SIZE("author ");
4329                 char *nameend = strchr(ident, '<');
4330                 char *emailend = strchr(ident, '>');
4332                 if (!nameend || !emailend)
4333                         break;
4335                 update_rev_graph(graph);
4336                 graph = graph->next;
4338                 *nameend = *emailend = 0;
4339                 ident = chomp_string(ident);
4340                 if (!*ident) {
4341                         ident = chomp_string(nameend + 1);
4342                         if (!*ident)
4343                                 ident = "Unknown";
4344                 }
4346                 string_ncopy(commit->author, ident, strlen(ident));
4348                 /* Parse epoch and timezone */
4349                 if (emailend[1] == ' ') {
4350                         char *secs = emailend + 2;
4351                         char *zone = strchr(secs, ' ');
4352                         time_t time = (time_t) atol(secs);
4354                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4355                                 long tz;
4357                                 zone++;
4358                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4359                                 tz += ('0' - zone[2]) * 60 * 60;
4360                                 tz += ('0' - zone[3]) * 60;
4361                                 tz += ('0' - zone[4]) * 60;
4363                                 if (zone[0] == '-')
4364                                         tz = -tz;
4366                                 time -= tz;
4367                         }
4369                         gmtime_r(&time, &commit->time);
4370                 }
4371                 break;
4372         }
4373         default:
4374                 /* Fill in the commit title if it has not already been set. */
4375                 if (commit->title[0])
4376                         break;
4378                 /* Require titles to start with a non-space character at the
4379                  * offset used by git log. */
4380                 if (strncmp(line, "    ", 4))
4381                         break;
4382                 line += 4;
4383                 /* Well, if the title starts with a whitespace character,
4384                  * try to be forgiving.  Otherwise we end up with no title. */
4385                 while (isspace(*line))
4386                         line++;
4387                 if (*line == '\0')
4388                         break;
4389                 /* FIXME: More graceful handling of titles; append "..." to
4390                  * shortened titles, etc. */
4392                 string_ncopy(commit->title, line, strlen(line));
4393         }
4395         return TRUE;
4398 static enum request
4399 main_request(struct view *view, enum request request, struct line *line)
4401         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4403         if (request == REQ_ENTER)
4404                 open_view(view, REQ_VIEW_DIFF, flags);
4405         else
4406                 return request;
4408         return REQ_NONE;
4411 static bool
4412 main_grep(struct view *view, struct line *line)
4414         struct commit *commit = line->data;
4415         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4416         char buf[DATE_COLS + 1];
4417         regmatch_t pmatch;
4419         for (state = S_TITLE; state < S_END; state++) {
4420                 char *text;
4422                 switch (state) {
4423                 case S_TITLE:   text = commit->title;   break;
4424                 case S_AUTHOR:  text = commit->author;  break;
4425                 case S_DATE:
4426                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4427                                 continue;
4428                         text = buf;
4429                         break;
4431                 default:
4432                         return FALSE;
4433                 }
4435                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4436                         return TRUE;
4437         }
4439         return FALSE;
4442 static void
4443 main_select(struct view *view, struct line *line)
4445         struct commit *commit = line->data;
4447         string_copy_rev(view->ref, commit->id);
4448         string_copy_rev(ref_commit, view->ref);
4451 static struct view_ops main_ops = {
4452         "commit",
4453         NULL,
4454         main_read,
4455         main_draw,
4456         main_request,
4457         main_grep,
4458         main_select,
4459 };
4462 /*
4463  * Unicode / UTF-8 handling
4464  *
4465  * NOTE: Much of the following code for dealing with unicode is derived from
4466  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4467  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4468  */
4470 /* I've (over)annotated a lot of code snippets because I am not entirely
4471  * confident that the approach taken by this small UTF-8 interface is correct.
4472  * --jonas */
4474 static inline int
4475 unicode_width(unsigned long c)
4477         if (c >= 0x1100 &&
4478            (c <= 0x115f                         /* Hangul Jamo */
4479             || c == 0x2329
4480             || c == 0x232a
4481             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4482                                                 /* CJK ... Yi */
4483             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4484             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4485             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4486             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4487             || (c >= 0xffe0  && c <= 0xffe6)
4488             || (c >= 0x20000 && c <= 0x2fffd)
4489             || (c >= 0x30000 && c <= 0x3fffd)))
4490                 return 2;
4492         if (c == '\t')
4493                 return opt_tab_size;
4495         return 1;
4498 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4499  * Illegal bytes are set one. */
4500 static const unsigned char utf8_bytes[256] = {
4501         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,
4502         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,
4503         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,
4504         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,
4505         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,
4506         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,
4507         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,
4508         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,
4509 };
4511 /* Decode UTF-8 multi-byte representation into a unicode character. */
4512 static inline unsigned long
4513 utf8_to_unicode(const char *string, size_t length)
4515         unsigned long unicode;
4517         switch (length) {
4518         case 1:
4519                 unicode  =   string[0];
4520                 break;
4521         case 2:
4522                 unicode  =  (string[0] & 0x1f) << 6;
4523                 unicode +=  (string[1] & 0x3f);
4524                 break;
4525         case 3:
4526                 unicode  =  (string[0] & 0x0f) << 12;
4527                 unicode += ((string[1] & 0x3f) << 6);
4528                 unicode +=  (string[2] & 0x3f);
4529                 break;
4530         case 4:
4531                 unicode  =  (string[0] & 0x0f) << 18;
4532                 unicode += ((string[1] & 0x3f) << 12);
4533                 unicode += ((string[2] & 0x3f) << 6);
4534                 unicode +=  (string[3] & 0x3f);
4535                 break;
4536         case 5:
4537                 unicode  =  (string[0] & 0x0f) << 24;
4538                 unicode += ((string[1] & 0x3f) << 18);
4539                 unicode += ((string[2] & 0x3f) << 12);
4540                 unicode += ((string[3] & 0x3f) << 6);
4541                 unicode +=  (string[4] & 0x3f);
4542                 break;
4543         case 6:
4544                 unicode  =  (string[0] & 0x01) << 30;
4545                 unicode += ((string[1] & 0x3f) << 24);
4546                 unicode += ((string[2] & 0x3f) << 18);
4547                 unicode += ((string[3] & 0x3f) << 12);
4548                 unicode += ((string[4] & 0x3f) << 6);
4549                 unicode +=  (string[5] & 0x3f);
4550                 break;
4551         default:
4552                 die("Invalid unicode length");
4553         }
4555         /* Invalid characters could return the special 0xfffd value but NUL
4556          * should be just as good. */
4557         return unicode > 0xffff ? 0 : unicode;
4560 /* Calculates how much of string can be shown within the given maximum width
4561  * and sets trimmed parameter to non-zero value if all of string could not be
4562  * shown.
4563  *
4564  * Additionally, adds to coloffset how many many columns to move to align with
4565  * the expected position. Takes into account how multi-byte and double-width
4566  * characters will effect the cursor position.
4567  *
4568  * Returns the number of bytes to output from string to satisfy max_width. */
4569 static size_t
4570 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4572         const char *start = string;
4573         const char *end = strchr(string, '\0');
4574         size_t mbwidth = 0;
4575         size_t width = 0;
4577         *trimmed = 0;
4579         while (string < end) {
4580                 int c = *(unsigned char *) string;
4581                 unsigned char bytes = utf8_bytes[c];
4582                 size_t ucwidth;
4583                 unsigned long unicode;
4585                 if (string + bytes > end)
4586                         break;
4588                 /* Change representation to figure out whether
4589                  * it is a single- or double-width character. */
4591                 unicode = utf8_to_unicode(string, bytes);
4592                 /* FIXME: Graceful handling of invalid unicode character. */
4593                 if (!unicode)
4594                         break;
4596                 ucwidth = unicode_width(unicode);
4597                 width  += ucwidth;
4598                 if (width > max_width) {
4599                         *trimmed = 1;
4600                         break;
4601                 }
4603                 /* The column offset collects the differences between the
4604                  * number of bytes encoding a character and the number of
4605                  * columns will be used for rendering said character.
4606                  *
4607                  * So if some character A is encoded in 2 bytes, but will be
4608                  * represented on the screen using only 1 byte this will and up
4609                  * adding 1 to the multi-byte column offset.
4610                  *
4611                  * Assumes that no double-width character can be encoding in
4612                  * less than two bytes. */
4613                 if (bytes > ucwidth)
4614                         mbwidth += bytes - ucwidth;
4616                 string  += bytes;
4617         }
4619         *coloffset += mbwidth;
4621         return string - start;
4625 /*
4626  * Status management
4627  */
4629 /* Whether or not the curses interface has been initialized. */
4630 static bool cursed = FALSE;
4632 /* The status window is used for polling keystrokes. */
4633 static WINDOW *status_win;
4635 static bool status_empty = TRUE;
4637 /* Update status and title window. */
4638 static void
4639 report(const char *msg, ...)
4641         struct view *view = display[current_view];
4643         if (input_mode)
4644                 return;
4646         if (!view) {
4647                 char buf[SIZEOF_STR];
4648                 va_list args;
4650                 va_start(args, msg);
4651                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4652                         buf[sizeof(buf) - 1] = 0;
4653                         buf[sizeof(buf) - 2] = '.';
4654                         buf[sizeof(buf) - 3] = '.';
4655                         buf[sizeof(buf) - 4] = '.';
4656                 }
4657                 va_end(args);
4658                 die("%s", buf);
4659         }
4661         if (!status_empty || *msg) {
4662                 va_list args;
4664                 va_start(args, msg);
4666                 wmove(status_win, 0, 0);
4667                 if (*msg) {
4668                         vwprintw(status_win, msg, args);
4669                         status_empty = FALSE;
4670                 } else {
4671                         status_empty = TRUE;
4672                 }
4673                 wclrtoeol(status_win);
4674                 wrefresh(status_win);
4676                 va_end(args);
4677         }
4679         update_view_title(view);
4680         update_display_cursor(view);
4683 /* Controls when nodelay should be in effect when polling user input. */
4684 static void
4685 set_nonblocking_input(bool loading)
4687         static unsigned int loading_views;
4689         if ((loading == FALSE && loading_views-- == 1) ||
4690             (loading == TRUE  && loading_views++ == 0))
4691                 nodelay(status_win, loading);
4694 static void
4695 init_display(void)
4697         int x, y;
4699         /* Initialize the curses library */
4700         if (isatty(STDIN_FILENO)) {
4701                 cursed = !!initscr();
4702         } else {
4703                 /* Leave stdin and stdout alone when acting as a pager. */
4704                 FILE *io = fopen("/dev/tty", "r+");
4706                 if (!io)
4707                         die("Failed to open /dev/tty");
4708                 cursed = !!newterm(NULL, io, io);
4709         }
4711         if (!cursed)
4712                 die("Failed to initialize curses");
4714         nonl();         /* Tell curses not to do NL->CR/NL on output */
4715         cbreak();       /* Take input chars one at a time, no wait for \n */
4716         noecho();       /* Don't echo input */
4717         leaveok(stdscr, TRUE);
4719         if (has_colors())
4720                 init_colors();
4722         getmaxyx(stdscr, y, x);
4723         status_win = newwin(1, 0, y - 1, 0);
4724         if (!status_win)
4725                 die("Failed to create status window");
4727         /* Enable keyboard mapping */
4728         keypad(status_win, TRUE);
4729         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4732 static char *
4733 read_prompt(const char *prompt)
4735         enum { READING, STOP, CANCEL } status = READING;
4736         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4737         int pos = 0;
4739         while (status == READING) {
4740                 struct view *view;
4741                 int i, key;
4743                 input_mode = TRUE;
4745                 foreach_view (view, i)
4746                         update_view(view);
4748                 input_mode = FALSE;
4750                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4751                 wclrtoeol(status_win);
4753                 /* Refresh, accept single keystroke of input */
4754                 key = wgetch(status_win);
4755                 switch (key) {
4756                 case KEY_RETURN:
4757                 case KEY_ENTER:
4758                 case '\n':
4759                         status = pos ? STOP : CANCEL;
4760                         break;
4762                 case KEY_BACKSPACE:
4763                         if (pos > 0)
4764                                 pos--;
4765                         else
4766                                 status = CANCEL;
4767                         break;
4769                 case KEY_ESC:
4770                         status = CANCEL;
4771                         break;
4773                 case ERR:
4774                         break;
4776                 default:
4777                         if (pos >= sizeof(buf)) {
4778                                 report("Input string too long");
4779                                 return NULL;
4780                         }
4782                         if (isprint(key))
4783                                 buf[pos++] = (char) key;
4784                 }
4785         }
4787         /* Clear the status window */
4788         status_empty = FALSE;
4789         report("");
4791         if (status == CANCEL)
4792                 return NULL;
4794         buf[pos++] = 0;
4796         return buf;
4799 /*
4800  * Repository references
4801  */
4803 static struct ref *refs;
4804 static size_t refs_size;
4806 /* Id <-> ref store */
4807 static struct ref ***id_refs;
4808 static size_t id_refs_size;
4810 static struct ref **
4811 get_refs(char *id)
4813         struct ref ***tmp_id_refs;
4814         struct ref **ref_list = NULL;
4815         size_t ref_list_size = 0;
4816         size_t i;
4818         for (i = 0; i < id_refs_size; i++)
4819                 if (!strcmp(id, id_refs[i][0]->id))
4820                         return id_refs[i];
4822         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4823         if (!tmp_id_refs)
4824                 return NULL;
4826         id_refs = tmp_id_refs;
4828         for (i = 0; i < refs_size; i++) {
4829                 struct ref **tmp;
4831                 if (strcmp(id, refs[i].id))
4832                         continue;
4834                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4835                 if (!tmp) {
4836                         if (ref_list)
4837                                 free(ref_list);
4838                         return NULL;
4839                 }
4841                 ref_list = tmp;
4842                 if (ref_list_size > 0)
4843                         ref_list[ref_list_size - 1]->next = 1;
4844                 ref_list[ref_list_size] = &refs[i];
4846                 /* XXX: The properties of the commit chains ensures that we can
4847                  * safely modify the shared ref. The repo references will
4848                  * always be similar for the same id. */
4849                 ref_list[ref_list_size]->next = 0;
4850                 ref_list_size++;
4851         }
4853         if (ref_list)
4854                 id_refs[id_refs_size++] = ref_list;
4856         return ref_list;
4859 static int
4860 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4862         struct ref *ref;
4863         bool tag = FALSE;
4864         bool remote = FALSE;
4866         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4867                 /* Commits referenced by tags has "^{}" appended. */
4868                 if (name[namelen - 1] != '}')
4869                         return OK;
4871                 while (namelen > 0 && name[namelen] != '^')
4872                         namelen--;
4874                 tag = TRUE;
4875                 namelen -= STRING_SIZE("refs/tags/");
4876                 name    += STRING_SIZE("refs/tags/");
4878         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4879                 remote = TRUE;
4880                 namelen -= STRING_SIZE("refs/remotes/");
4881                 name    += STRING_SIZE("refs/remotes/");
4883         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4884                 namelen -= STRING_SIZE("refs/heads/");
4885                 name    += STRING_SIZE("refs/heads/");
4887         } else if (!strcmp(name, "HEAD")) {
4888                 return OK;
4889         }
4891         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4892         if (!refs)
4893                 return ERR;
4895         ref = &refs[refs_size++];
4896         ref->name = malloc(namelen + 1);
4897         if (!ref->name)
4898                 return ERR;
4900         strncpy(ref->name, name, namelen);
4901         ref->name[namelen] = 0;
4902         ref->tag = tag;
4903         ref->remote = remote;
4904         string_copy_rev(ref->id, id);
4906         return OK;
4909 static int
4910 load_refs(void)
4912         const char *cmd_env = getenv("TIG_LS_REMOTE");
4913         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4915         return read_properties(popen(cmd, "r"), "\t", read_ref);
4918 static int
4919 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4921         if (!strcmp(name, "i18n.commitencoding"))
4922                 string_ncopy(opt_encoding, value, valuelen);
4924         if (!strcmp(name, "core.editor"))
4925                 string_ncopy(opt_editor, value, valuelen);
4927         return OK;
4930 static int
4931 load_repo_config(void)
4933         return read_properties(popen(GIT_CONFIG " --list", "r"),
4934                                "=", read_repo_config_option);
4937 static int
4938 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4940         if (!opt_git_dir[0]) {
4941                 string_ncopy(opt_git_dir, name, namelen);
4943         } else if (opt_is_inside_work_tree == -1) {
4944                 /* This can be 3 different values depending on the
4945                  * version of git being used. If git-rev-parse does not
4946                  * understand --is-inside-work-tree it will simply echo
4947                  * the option else either "true" or "false" is printed.
4948                  * Default to true for the unknown case. */
4949                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4951         } else {
4952                 string_ncopy(opt_cdup, name, namelen);
4953         }
4955         return OK;
4958 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4959  * must be the last one! */
4960 static int
4961 load_repo_info(void)
4963         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4964                                "=", read_repo_info);
4967 static int
4968 read_properties(FILE *pipe, const char *separators,
4969                 int (*read_property)(char *, size_t, char *, size_t))
4971         char buffer[BUFSIZ];
4972         char *name;
4973         int state = OK;
4975         if (!pipe)
4976                 return ERR;
4978         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4979                 char *value;
4980                 size_t namelen;
4981                 size_t valuelen;
4983                 name = chomp_string(name);
4984                 namelen = strcspn(name, separators);
4986                 if (name[namelen]) {
4987                         name[namelen] = 0;
4988                         value = chomp_string(name + namelen + 1);
4989                         valuelen = strlen(value);
4991                 } else {
4992                         value = "";
4993                         valuelen = 0;
4994                 }
4996                 state = read_property(name, namelen, value, valuelen);
4997         }
4999         if (state != ERR && ferror(pipe))
5000                 state = ERR;
5002         pclose(pipe);
5004         return state;
5008 /*
5009  * Main
5010  */
5012 static void __NORETURN
5013 quit(int sig)
5015         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5016         if (cursed)
5017                 endwin();
5018         exit(0);
5021 static void __NORETURN
5022 die(const char *err, ...)
5024         va_list args;
5026         endwin();
5028         va_start(args, err);
5029         fputs("tig: ", stderr);
5030         vfprintf(stderr, err, args);
5031         fputs("\n", stderr);
5032         va_end(args);
5034         exit(1);
5037 static void
5038 warn(const char *msg, ...)
5040         va_list args;
5042         va_start(args, msg);
5043         fputs("tig warning: ", stderr);
5044         vfprintf(stderr, msg, args);
5045         fputs("\n", stderr);
5046         va_end(args);
5049 int
5050 main(int argc, char *argv[])
5052         struct view *view;
5053         enum request request;
5054         size_t i;
5056         signal(SIGINT, quit);
5058         if (setlocale(LC_ALL, "")) {
5059                 char *codeset = nl_langinfo(CODESET);
5061                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5062         }
5064         if (load_repo_info() == ERR)
5065                 die("Failed to load repo info.");
5067         if (load_options() == ERR)
5068                 die("Failed to load user config.");
5070         /* Load the repo config file so options can be overwritten from
5071          * the command line. */
5072         if (load_repo_config() == ERR)
5073                 die("Failed to load repo config.");
5075         if (!parse_options(argc, argv))
5076                 return 0;
5078         /* Require a git repository unless when running in pager mode. */
5079         if (!opt_git_dir[0])
5080                 die("Not a git repository");
5082         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5083                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5084                 if (opt_iconv == ICONV_NONE)
5085                         die("Failed to initialize character set conversion");
5086         }
5088         if (load_refs() == ERR)
5089                 die("Failed to load refs.");
5091         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5092                 view->cmd_env = getenv(view->cmd_env);
5094         request = opt_request;
5096         init_display();
5098         while (view_driver(display[current_view], request)) {
5099                 int key;
5100                 int i;
5102                 foreach_view (view, i)
5103                         update_view(view);
5105                 /* Refresh, accept single keystroke of input */
5106                 key = wgetch(status_win);
5108                 /* wgetch() with nodelay() enabled returns ERR when there's no
5109                  * input. */
5110                 if (key == ERR) {
5111                         request = REQ_NONE;
5112                         continue;
5113                 }
5115                 request = get_keybinding(display[current_view]->keymap, key);
5117                 /* Some low-level request handling. This keeps access to
5118                  * status_win restricted. */
5119                 switch (request) {
5120                 case REQ_PROMPT:
5121                 {
5122                         char *cmd = read_prompt(":");
5124                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5125                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5126                                         opt_request = REQ_VIEW_DIFF;
5127                                 } else {
5128                                         opt_request = REQ_VIEW_PAGER;
5129                                 }
5130                                 break;
5131                         }
5133                         request = REQ_NONE;
5134                         break;
5135                 }
5136                 case REQ_SEARCH:
5137                 case REQ_SEARCH_BACK:
5138                 {
5139                         const char *prompt = request == REQ_SEARCH
5140                                            ? "/" : "?";
5141                         char *search = read_prompt(prompt);
5143                         if (search)
5144                                 string_ncopy(opt_search, search, strlen(search));
5145                         else
5146                                 request = REQ_NONE;
5147                         break;
5148                 }
5149                 case REQ_SCREEN_RESIZE:
5150                 {
5151                         int height, width;
5153                         getmaxyx(stdscr, height, width);
5155                         /* Resize the status view and let the view driver take
5156                          * care of resizing the displayed views. */
5157                         wresize(status_win, 1, width);
5158                         mvwin(status_win, height - 1, 0);
5159                         wrefresh(status_win);
5160                         break;
5161                 }
5162                 default:
5163                         break;
5164                 }
5165         }
5167         quit(0);
5169         return 0;