Code

73aa9913c7a3a248975565850ac6ec1a0512f262
[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 *trimmed, bool reserve);
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 len = 0;
1464         int trimmed = FALSE;
1466         if (max_len <= 0)
1467                 return 0;
1469         if (opt_utf8) {
1470                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1471         } else {
1472                 len = strlen(string);
1473                 if (len > max_len) {
1474                         if (use_tilde) {
1475                                 max_len -= 1;
1476                         }
1477                         len = max_len;
1478                         trimmed = TRUE;
1479                 }
1480         }
1482         waddnstr(view->win, string, len);
1483         if (trimmed && use_tilde) {
1484                 if (tilde_attr != -1)
1485                         wattrset(view->win, tilde_attr);
1486                 waddch(view->win, '~');
1487                 len++;
1488         }
1490         return len;
1493 static bool
1494 draw_view_line(struct view *view, unsigned int lineno)
1496         struct line *line;
1497         bool selected = (view->offset + lineno == view->lineno);
1498         bool draw_ok;
1500         assert(view_is_displayed(view));
1502         if (view->offset + lineno >= view->lines)
1503                 return FALSE;
1505         line = &view->line[view->offset + lineno];
1507         if (selected) {
1508                 line->selected = TRUE;
1509                 view->ops->select(view, line);
1510         } else if (line->selected) {
1511                 line->selected = FALSE;
1512                 wmove(view->win, lineno, 0);
1513                 wclrtoeol(view->win);
1514         }
1516         scrollok(view->win, FALSE);
1517         draw_ok = view->ops->draw(view, line, lineno, selected);
1518         scrollok(view->win, TRUE);
1520         return draw_ok;
1523 static void
1524 redraw_view_from(struct view *view, int lineno)
1526         assert(0 <= lineno && lineno < view->height);
1528         for (; lineno < view->height; lineno++) {
1529                 if (!draw_view_line(view, lineno))
1530                         break;
1531         }
1533         redrawwin(view->win);
1534         if (input_mode)
1535                 wnoutrefresh(view->win);
1536         else
1537                 wrefresh(view->win);
1540 static void
1541 redraw_view(struct view *view)
1543         wclear(view->win);
1544         redraw_view_from(view, 0);
1548 static void
1549 update_view_title(struct view *view)
1551         char buf[SIZEOF_STR];
1552         char state[SIZEOF_STR];
1553         size_t bufpos = 0, statelen = 0;
1555         assert(view_is_displayed(view));
1557         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1558                 unsigned int view_lines = view->offset + view->height;
1559                 unsigned int lines = view->lines
1560                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1561                                    : 0;
1563                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1564                                    view->ops->type,
1565                                    view->lineno + 1,
1566                                    view->lines,
1567                                    lines);
1569                 if (view->pipe) {
1570                         time_t secs = time(NULL) - view->start_time;
1572                         /* Three git seconds are a long time ... */
1573                         if (secs > 2)
1574                                 string_format_from(state, &statelen, " %lds", secs);
1575                 }
1576         }
1578         string_format_from(buf, &bufpos, "[%s]", view->name);
1579         if (*view->ref && bufpos < view->width) {
1580                 size_t refsize = strlen(view->ref);
1581                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1583                 if (minsize < view->width)
1584                         refsize = view->width - minsize + 7;
1585                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1586         }
1588         if (statelen && bufpos < view->width) {
1589                 string_format_from(buf, &bufpos, " %s", state);
1590         }
1592         if (view == display[current_view])
1593                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1594         else
1595                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1597         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1598         wclrtoeol(view->title);
1599         wmove(view->title, 0, view->width - 1);
1601         if (input_mode)
1602                 wnoutrefresh(view->title);
1603         else
1604                 wrefresh(view->title);
1607 static void
1608 resize_display(void)
1610         int offset, i;
1611         struct view *base = display[0];
1612         struct view *view = display[1] ? display[1] : display[0];
1614         /* Setup window dimensions */
1616         getmaxyx(stdscr, base->height, base->width);
1618         /* Make room for the status window. */
1619         base->height -= 1;
1621         if (view != base) {
1622                 /* Horizontal split. */
1623                 view->width   = base->width;
1624                 view->height  = SCALE_SPLIT_VIEW(base->height);
1625                 base->height -= view->height;
1627                 /* Make room for the title bar. */
1628                 view->height -= 1;
1629         }
1631         /* Make room for the title bar. */
1632         base->height -= 1;
1634         offset = 0;
1636         foreach_displayed_view (view, i) {
1637                 if (!view->win) {
1638                         view->win = newwin(view->height, 0, offset, 0);
1639                         if (!view->win)
1640                                 die("Failed to create %s view", view->name);
1642                         scrollok(view->win, TRUE);
1644                         view->title = newwin(1, 0, offset + view->height, 0);
1645                         if (!view->title)
1646                                 die("Failed to create title window");
1648                 } else {
1649                         wresize(view->win, view->height, view->width);
1650                         mvwin(view->win,   offset, 0);
1651                         mvwin(view->title, offset + view->height, 0);
1652                 }
1654                 offset += view->height + 1;
1655         }
1658 static void
1659 redraw_display(void)
1661         struct view *view;
1662         int i;
1664         foreach_displayed_view (view, i) {
1665                 redraw_view(view);
1666                 update_view_title(view);
1667         }
1670 static void
1671 update_display_cursor(struct view *view)
1673         /* Move the cursor to the right-most column of the cursor line.
1674          *
1675          * XXX: This could turn out to be a bit expensive, but it ensures that
1676          * the cursor does not jump around. */
1677         if (view->lines) {
1678                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1679                 wrefresh(view->win);
1680         }
1683 /*
1684  * Navigation
1685  */
1687 /* Scrolling backend */
1688 static void
1689 do_scroll_view(struct view *view, int lines)
1691         bool redraw_current_line = FALSE;
1693         /* The rendering expects the new offset. */
1694         view->offset += lines;
1696         assert(0 <= view->offset && view->offset < view->lines);
1697         assert(lines);
1699         /* Move current line into the view. */
1700         if (view->lineno < view->offset) {
1701                 view->lineno = view->offset;
1702                 redraw_current_line = TRUE;
1703         } else if (view->lineno >= view->offset + view->height) {
1704                 view->lineno = view->offset + view->height - 1;
1705                 redraw_current_line = TRUE;
1706         }
1708         assert(view->offset <= view->lineno && view->lineno < view->lines);
1710         /* Redraw the whole screen if scrolling is pointless. */
1711         if (view->height < ABS(lines)) {
1712                 redraw_view(view);
1714         } else {
1715                 int line = lines > 0 ? view->height - lines : 0;
1716                 int end = line + ABS(lines);
1718                 wscrl(view->win, lines);
1720                 for (; line < end; line++) {
1721                         if (!draw_view_line(view, line))
1722                                 break;
1723                 }
1725                 if (redraw_current_line)
1726                         draw_view_line(view, view->lineno - view->offset);
1727         }
1729         redrawwin(view->win);
1730         wrefresh(view->win);
1731         report("");
1734 /* Scroll frontend */
1735 static void
1736 scroll_view(struct view *view, enum request request)
1738         int lines = 1;
1740         assert(view_is_displayed(view));
1742         switch (request) {
1743         case REQ_SCROLL_PAGE_DOWN:
1744                 lines = view->height;
1745         case REQ_SCROLL_LINE_DOWN:
1746                 if (view->offset + lines > view->lines)
1747                         lines = view->lines - view->offset;
1749                 if (lines == 0 || view->offset + view->height >= view->lines) {
1750                         report("Cannot scroll beyond the last line");
1751                         return;
1752                 }
1753                 break;
1755         case REQ_SCROLL_PAGE_UP:
1756                 lines = view->height;
1757         case REQ_SCROLL_LINE_UP:
1758                 if (lines > view->offset)
1759                         lines = view->offset;
1761                 if (lines == 0) {
1762                         report("Cannot scroll beyond the first line");
1763                         return;
1764                 }
1766                 lines = -lines;
1767                 break;
1769         default:
1770                 die("request %d not handled in switch", request);
1771         }
1773         do_scroll_view(view, lines);
1776 /* Cursor moving */
1777 static void
1778 move_view(struct view *view, enum request request)
1780         int scroll_steps = 0;
1781         int steps;
1783         switch (request) {
1784         case REQ_MOVE_FIRST_LINE:
1785                 steps = -view->lineno;
1786                 break;
1788         case REQ_MOVE_LAST_LINE:
1789                 steps = view->lines - view->lineno - 1;
1790                 break;
1792         case REQ_MOVE_PAGE_UP:
1793                 steps = view->height > view->lineno
1794                       ? -view->lineno : -view->height;
1795                 break;
1797         case REQ_MOVE_PAGE_DOWN:
1798                 steps = view->lineno + view->height >= view->lines
1799                       ? view->lines - view->lineno - 1 : view->height;
1800                 break;
1802         case REQ_MOVE_UP:
1803                 steps = -1;
1804                 break;
1806         case REQ_MOVE_DOWN:
1807                 steps = 1;
1808                 break;
1810         default:
1811                 die("request %d not handled in switch", request);
1812         }
1814         if (steps <= 0 && view->lineno == 0) {
1815                 report("Cannot move beyond the first line");
1816                 return;
1818         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1819                 report("Cannot move beyond the last line");
1820                 return;
1821         }
1823         /* Move the current line */
1824         view->lineno += steps;
1825         assert(0 <= view->lineno && view->lineno < view->lines);
1827         /* Check whether the view needs to be scrolled */
1828         if (view->lineno < view->offset ||
1829             view->lineno >= view->offset + view->height) {
1830                 scroll_steps = steps;
1831                 if (steps < 0 && -steps > view->offset) {
1832                         scroll_steps = -view->offset;
1834                 } else if (steps > 0) {
1835                         if (view->lineno == view->lines - 1 &&
1836                             view->lines > view->height) {
1837                                 scroll_steps = view->lines - view->offset - 1;
1838                                 if (scroll_steps >= view->height)
1839                                         scroll_steps -= view->height - 1;
1840                         }
1841                 }
1842         }
1844         if (!view_is_displayed(view)) {
1845                 view->offset += scroll_steps;
1846                 assert(0 <= view->offset && view->offset < view->lines);
1847                 view->ops->select(view, &view->line[view->lineno]);
1848                 return;
1849         }
1851         /* Repaint the old "current" line if we be scrolling */
1852         if (ABS(steps) < view->height)
1853                 draw_view_line(view, view->lineno - steps - view->offset);
1855         if (scroll_steps) {
1856                 do_scroll_view(view, scroll_steps);
1857                 return;
1858         }
1860         /* Draw the current line */
1861         draw_view_line(view, view->lineno - view->offset);
1863         redrawwin(view->win);
1864         wrefresh(view->win);
1865         report("");
1869 /*
1870  * Searching
1871  */
1873 static void search_view(struct view *view, enum request request);
1875 static bool
1876 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1878         assert(view_is_displayed(view));
1880         if (!view->ops->grep(view, line))
1881                 return FALSE;
1883         if (lineno - view->offset >= view->height) {
1884                 view->offset = lineno;
1885                 view->lineno = lineno;
1886                 redraw_view(view);
1888         } else {
1889                 unsigned long old_lineno = view->lineno - view->offset;
1891                 view->lineno = lineno;
1892                 draw_view_line(view, old_lineno);
1894                 draw_view_line(view, view->lineno - view->offset);
1895                 redrawwin(view->win);
1896                 wrefresh(view->win);
1897         }
1899         report("Line %ld matches '%s'", lineno + 1, view->grep);
1900         return TRUE;
1903 static void
1904 find_next(struct view *view, enum request request)
1906         unsigned long lineno = view->lineno;
1907         int direction;
1909         if (!*view->grep) {
1910                 if (!*opt_search)
1911                         report("No previous search");
1912                 else
1913                         search_view(view, request);
1914                 return;
1915         }
1917         switch (request) {
1918         case REQ_SEARCH:
1919         case REQ_FIND_NEXT:
1920                 direction = 1;
1921                 break;
1923         case REQ_SEARCH_BACK:
1924         case REQ_FIND_PREV:
1925                 direction = -1;
1926                 break;
1928         default:
1929                 return;
1930         }
1932         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1933                 lineno += direction;
1935         /* Note, lineno is unsigned long so will wrap around in which case it
1936          * will become bigger than view->lines. */
1937         for (; lineno < view->lines; lineno += direction) {
1938                 struct line *line = &view->line[lineno];
1940                 if (find_next_line(view, lineno, line))
1941                         return;
1942         }
1944         report("No match found for '%s'", view->grep);
1947 static void
1948 search_view(struct view *view, enum request request)
1950         int regex_err;
1952         if (view->regex) {
1953                 regfree(view->regex);
1954                 *view->grep = 0;
1955         } else {
1956                 view->regex = calloc(1, sizeof(*view->regex));
1957                 if (!view->regex)
1958                         return;
1959         }
1961         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1962         if (regex_err != 0) {
1963                 char buf[SIZEOF_STR] = "unknown error";
1965                 regerror(regex_err, view->regex, buf, sizeof(buf));
1966                 report("Search failed: %s", buf);
1967                 return;
1968         }
1970         string_copy(view->grep, opt_search);
1972         find_next(view, request);
1975 /*
1976  * Incremental updating
1977  */
1979 static void
1980 end_update(struct view *view)
1982         if (!view->pipe)
1983                 return;
1984         set_nonblocking_input(FALSE);
1985         if (view->pipe == stdin)
1986                 fclose(view->pipe);
1987         else
1988                 pclose(view->pipe);
1989         view->pipe = NULL;
1992 static bool
1993 begin_update(struct view *view)
1995         if (view->pipe)
1996                 end_update(view);
1998         if (opt_cmd[0]) {
1999                 string_copy(view->cmd, opt_cmd);
2000                 opt_cmd[0] = 0;
2001                 /* When running random commands, initially show the
2002                  * command in the title. However, it maybe later be
2003                  * overwritten if a commit line is selected. */
2004                 if (view == VIEW(REQ_VIEW_PAGER))
2005                         string_copy(view->ref, view->cmd);
2006                 else
2007                         view->ref[0] = 0;
2009         } else if (view == VIEW(REQ_VIEW_TREE)) {
2010                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2011                 char path[SIZEOF_STR];
2013                 if (strcmp(view->vid, view->id))
2014                         opt_path[0] = path[0] = 0;
2015                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2016                         return FALSE;
2018                 if (!string_format(view->cmd, format, view->id, path))
2019                         return FALSE;
2021         } else {
2022                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2023                 const char *id = view->id;
2025                 if (!string_format(view->cmd, format, id, id, id, id, id))
2026                         return FALSE;
2028                 /* Put the current ref_* value to the view title ref
2029                  * member. This is needed by the blob view. Most other
2030                  * views sets it automatically after loading because the
2031                  * first line is a commit line. */
2032                 string_copy_rev(view->ref, view->id);
2033         }
2035         /* Special case for the pager view. */
2036         if (opt_pipe) {
2037                 view->pipe = opt_pipe;
2038                 opt_pipe = NULL;
2039         } else {
2040                 view->pipe = popen(view->cmd, "r");
2041         }
2043         if (!view->pipe)
2044                 return FALSE;
2046         set_nonblocking_input(TRUE);
2048         view->offset = 0;
2049         view->lines  = 0;
2050         view->lineno = 0;
2051         string_copy_rev(view->vid, view->id);
2053         if (view->line) {
2054                 int i;
2056                 for (i = 0; i < view->lines; i++)
2057                         if (view->line[i].data)
2058                                 free(view->line[i].data);
2060                 free(view->line);
2061                 view->line = NULL;
2062         }
2064         view->start_time = time(NULL);
2066         return TRUE;
2069 static struct line *
2070 realloc_lines(struct view *view, size_t line_size)
2072         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2074         if (!tmp)
2075                 return NULL;
2077         view->line = tmp;
2078         view->line_size = line_size;
2079         return view->line;
2082 static bool
2083 update_view(struct view *view)
2085         char in_buffer[BUFSIZ];
2086         char out_buffer[BUFSIZ * 2];
2087         char *line;
2088         /* The number of lines to read. If too low it will cause too much
2089          * redrawing (and possible flickering), if too high responsiveness
2090          * will suffer. */
2091         unsigned long lines = view->height;
2092         int redraw_from = -1;
2094         if (!view->pipe)
2095                 return TRUE;
2097         /* Only redraw if lines are visible. */
2098         if (view->offset + view->height >= view->lines)
2099                 redraw_from = view->lines - view->offset;
2101         /* FIXME: This is probably not perfect for backgrounded views. */
2102         if (!realloc_lines(view, view->lines + lines))
2103                 goto alloc_error;
2105         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2106                 size_t linelen = strlen(line);
2108                 if (linelen)
2109                         line[linelen - 1] = 0;
2111                 if (opt_iconv != ICONV_NONE) {
2112                         ICONV_CONST char *inbuf = line;
2113                         size_t inlen = linelen;
2115                         char *outbuf = out_buffer;
2116                         size_t outlen = sizeof(out_buffer);
2118                         size_t ret;
2120                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2121                         if (ret != (size_t) -1) {
2122                                 line = out_buffer;
2123                                 linelen = strlen(out_buffer);
2124                         }
2125                 }
2127                 if (!view->ops->read(view, line))
2128                         goto alloc_error;
2130                 if (lines-- == 1)
2131                         break;
2132         }
2134         {
2135                 int digits;
2137                 lines = view->lines;
2138                 for (digits = 0; lines; digits++)
2139                         lines /= 10;
2141                 /* Keep the displayed view in sync with line number scaling. */
2142                 if (digits != view->digits) {
2143                         view->digits = digits;
2144                         redraw_from = 0;
2145                 }
2146         }
2148         if (!view_is_displayed(view))
2149                 goto check_pipe;
2151         if (view == VIEW(REQ_VIEW_TREE)) {
2152                 /* Clear the view and redraw everything since the tree sorting
2153                  * might have rearranged things. */
2154                 redraw_view(view);
2156         } else if (redraw_from >= 0) {
2157                 /* If this is an incremental update, redraw the previous line
2158                  * since for commits some members could have changed when
2159                  * loading the main view. */
2160                 if (redraw_from > 0)
2161                         redraw_from--;
2163                 /* Since revision graph visualization requires knowledge
2164                  * about the parent commit, it causes a further one-off
2165                  * needed to be redrawn for incremental updates. */
2166                 if (redraw_from > 0 && opt_rev_graph)
2167                         redraw_from--;
2169                 /* Incrementally draw avoids flickering. */
2170                 redraw_view_from(view, redraw_from);
2171         }
2173         /* Update the title _after_ the redraw so that if the redraw picks up a
2174          * commit reference in view->ref it'll be available here. */
2175         update_view_title(view);
2177 check_pipe:
2178         if (ferror(view->pipe)) {
2179                 report("Failed to read: %s", strerror(errno));
2180                 goto end;
2182         } else if (feof(view->pipe)) {
2183                 report("");
2184                 goto end;
2185         }
2187         return TRUE;
2189 alloc_error:
2190         report("Allocation failure");
2192 end:
2193         view->ops->read(view, NULL);
2194         end_update(view);
2195         return FALSE;
2198 static struct line *
2199 add_line_data(struct view *view, void *data, enum line_type type)
2201         struct line *line = &view->line[view->lines++];
2203         memset(line, 0, sizeof(*line));
2204         line->type = type;
2205         line->data = data;
2207         return line;
2210 static struct line *
2211 add_line_text(struct view *view, char *data, enum line_type type)
2213         if (data)
2214                 data = strdup(data);
2216         return data ? add_line_data(view, data, type) : NULL;
2220 /*
2221  * View opening
2222  */
2224 enum open_flags {
2225         OPEN_DEFAULT = 0,       /* Use default view switching. */
2226         OPEN_SPLIT = 1,         /* Split current view. */
2227         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2228         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2229 };
2231 static void
2232 open_view(struct view *prev, enum request request, enum open_flags flags)
2234         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2235         bool split = !!(flags & OPEN_SPLIT);
2236         bool reload = !!(flags & OPEN_RELOAD);
2237         struct view *view = VIEW(request);
2238         int nviews = displayed_views();
2239         struct view *base_view = display[0];
2241         if (view == prev && nviews == 1 && !reload) {
2242                 report("Already in %s view", view->name);
2243                 return;
2244         }
2246         if (view->ops->open) {
2247                 if (!view->ops->open(view)) {
2248                         report("Failed to load %s view", view->name);
2249                         return;
2250                 }
2252         } else if ((reload || strcmp(view->vid, view->id)) &&
2253                    !begin_update(view)) {
2254                 report("Failed to load %s view", view->name);
2255                 return;
2256         }
2258         if (split) {
2259                 display[1] = view;
2260                 if (!backgrounded)
2261                         current_view = 1;
2262         } else {
2263                 /* Maximize the current view. */
2264                 memset(display, 0, sizeof(display));
2265                 current_view = 0;
2266                 display[current_view] = view;
2267         }
2269         /* Resize the view when switching between split- and full-screen,
2270          * or when switching between two different full-screen views. */
2271         if (nviews != displayed_views() ||
2272             (nviews == 1 && base_view != display[0]))
2273                 resize_display();
2275         if (split && prev->lineno - prev->offset >= prev->height) {
2276                 /* Take the title line into account. */
2277                 int lines = prev->lineno - prev->offset - prev->height + 1;
2279                 /* Scroll the view that was split if the current line is
2280                  * outside the new limited view. */
2281                 do_scroll_view(prev, lines);
2282         }
2284         if (prev && view != prev) {
2285                 if (split && !backgrounded) {
2286                         /* "Blur" the previous view. */
2287                         update_view_title(prev);
2288                 }
2290                 view->parent = prev;
2291         }
2293         if (view->pipe && view->lines == 0) {
2294                 /* Clear the old view and let the incremental updating refill
2295                  * the screen. */
2296                 wclear(view->win);
2297                 report("");
2298         } else {
2299                 redraw_view(view);
2300                 report("");
2301         }
2303         /* If the view is backgrounded the above calls to report()
2304          * won't redraw the view title. */
2305         if (backgrounded)
2306                 update_view_title(view);
2309 static void
2310 open_external_viewer(const char *cmd)
2312         def_prog_mode();           /* save current tty modes */
2313         endwin();                  /* restore original tty modes */
2314         system(cmd);
2315         fprintf(stderr, "Press Enter to continue");
2316         getc(stdin);
2317         reset_prog_mode();
2318         redraw_display();
2321 static void
2322 open_mergetool(const char *file)
2324         char cmd[SIZEOF_STR];
2325         char file_sq[SIZEOF_STR];
2327         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2328             string_format(cmd, "git mergetool %s", file_sq)) {
2329                 open_external_viewer(cmd);
2330         }
2333 static void
2334 open_editor(bool from_root, const char *file)
2336         char cmd[SIZEOF_STR];
2337         char file_sq[SIZEOF_STR];
2338         char *editor;
2339         char *prefix = from_root ? opt_cdup : "";
2341         editor = getenv("GIT_EDITOR");
2342         if (!editor && *opt_editor)
2343                 editor = opt_editor;
2344         if (!editor)
2345                 editor = getenv("VISUAL");
2346         if (!editor)
2347                 editor = getenv("EDITOR");
2348         if (!editor)
2349                 editor = "vi";
2351         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2352             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2353                 open_external_viewer(cmd);
2354         }
2357 static void
2358 open_run_request(enum request request)
2360         struct run_request *req = get_run_request(request);
2361         char buf[SIZEOF_STR * 2];
2362         size_t bufpos;
2363         char *cmd;
2365         if (!req) {
2366                 report("Unknown run request");
2367                 return;
2368         }
2370         bufpos = 0;
2371         cmd = req->cmd;
2373         while (cmd) {
2374                 char *next = strstr(cmd, "%(");
2375                 int len = next - cmd;
2376                 char *value;
2378                 if (!next) {
2379                         len = strlen(cmd);
2380                         value = "";
2382                 } else if (!strncmp(next, "%(head)", 7)) {
2383                         value = ref_head;
2385                 } else if (!strncmp(next, "%(commit)", 9)) {
2386                         value = ref_commit;
2388                 } else if (!strncmp(next, "%(blob)", 7)) {
2389                         value = ref_blob;
2391                 } else {
2392                         report("Unknown replacement in run request: `%s`", req->cmd);
2393                         return;
2394                 }
2396                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2397                         return;
2399                 if (next)
2400                         next = strchr(next, ')') + 1;
2401                 cmd = next;
2402         }
2404         open_external_viewer(buf);
2407 /*
2408  * User request switch noodle
2409  */
2411 static int
2412 view_driver(struct view *view, enum request request)
2414         int i;
2416         if (request == REQ_NONE) {
2417                 doupdate();
2418                 return TRUE;
2419         }
2421         if (request > REQ_NONE) {
2422                 open_run_request(request);
2423                 return TRUE;
2424         }
2426         if (view && view->lines) {
2427                 request = view->ops->request(view, request, &view->line[view->lineno]);
2428                 if (request == REQ_NONE)
2429                         return TRUE;
2430         }
2432         switch (request) {
2433         case REQ_MOVE_UP:
2434         case REQ_MOVE_DOWN:
2435         case REQ_MOVE_PAGE_UP:
2436         case REQ_MOVE_PAGE_DOWN:
2437         case REQ_MOVE_FIRST_LINE:
2438         case REQ_MOVE_LAST_LINE:
2439                 move_view(view, request);
2440                 break;
2442         case REQ_SCROLL_LINE_DOWN:
2443         case REQ_SCROLL_LINE_UP:
2444         case REQ_SCROLL_PAGE_DOWN:
2445         case REQ_SCROLL_PAGE_UP:
2446                 scroll_view(view, request);
2447                 break;
2449         case REQ_VIEW_BLOB:
2450                 if (!ref_blob[0]) {
2451                         report("No file chosen, press %s to open tree view",
2452                                get_key(REQ_VIEW_TREE));
2453                         break;
2454                 }
2455                 open_view(view, request, OPEN_DEFAULT);
2456                 break;
2458         case REQ_VIEW_PAGER:
2459                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2460                         report("No pager content, press %s to run command from prompt",
2461                                get_key(REQ_PROMPT));
2462                         break;
2463                 }
2464                 open_view(view, request, OPEN_DEFAULT);
2465                 break;
2467         case REQ_VIEW_STAGE:
2468                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2469                         report("No stage content, press %s to open the status view and choose file",
2470                                get_key(REQ_VIEW_STATUS));
2471                         break;
2472                 }
2473                 open_view(view, request, OPEN_DEFAULT);
2474                 break;
2476         case REQ_VIEW_STATUS:
2477                 if (opt_is_inside_work_tree == FALSE) {
2478                         report("The status view requires a working tree");
2479                         break;
2480                 }
2481                 open_view(view, request, OPEN_DEFAULT);
2482                 break;
2484         case REQ_VIEW_MAIN:
2485         case REQ_VIEW_DIFF:
2486         case REQ_VIEW_LOG:
2487         case REQ_VIEW_TREE:
2488         case REQ_VIEW_HELP:
2489                 open_view(view, request, OPEN_DEFAULT);
2490                 break;
2492         case REQ_NEXT:
2493         case REQ_PREVIOUS:
2494                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2496                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2497                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2498                    (view == VIEW(REQ_VIEW_STAGE) &&
2499                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2500                    (view == VIEW(REQ_VIEW_BLOB) &&
2501                      view->parent == VIEW(REQ_VIEW_TREE))) {
2502                         int line;
2504                         view = view->parent;
2505                         line = view->lineno;
2506                         move_view(view, request);
2507                         if (view_is_displayed(view))
2508                                 update_view_title(view);
2509                         if (line != view->lineno)
2510                                 view->ops->request(view, REQ_ENTER,
2511                                                    &view->line[view->lineno]);
2513                 } else {
2514                         move_view(view, request);
2515                 }
2516                 break;
2518         case REQ_VIEW_NEXT:
2519         {
2520                 int nviews = displayed_views();
2521                 int next_view = (current_view + 1) % nviews;
2523                 if (next_view == current_view) {
2524                         report("Only one view is displayed");
2525                         break;
2526                 }
2528                 current_view = next_view;
2529                 /* Blur out the title of the previous view. */
2530                 update_view_title(view);
2531                 report("");
2532                 break;
2533         }
2534         case REQ_REFRESH:
2535                 report("Refreshing is not yet supported for the %s view", view->name);
2536                 break;
2538         case REQ_TOGGLE_LINENO:
2539                 opt_line_number = !opt_line_number;
2540                 redraw_display();
2541                 break;
2543         case REQ_TOGGLE_REV_GRAPH:
2544                 opt_rev_graph = !opt_rev_graph;
2545                 redraw_display();
2546                 break;
2548         case REQ_PROMPT:
2549                 /* Always reload^Wrerun commands from the prompt. */
2550                 open_view(view, opt_request, OPEN_RELOAD);
2551                 break;
2553         case REQ_SEARCH:
2554         case REQ_SEARCH_BACK:
2555                 search_view(view, request);
2556                 break;
2558         case REQ_FIND_NEXT:
2559         case REQ_FIND_PREV:
2560                 find_next(view, request);
2561                 break;
2563         case REQ_STOP_LOADING:
2564                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2565                         view = &views[i];
2566                         if (view->pipe)
2567                                 report("Stopped loading the %s view", view->name),
2568                         end_update(view);
2569                 }
2570                 break;
2572         case REQ_SHOW_VERSION:
2573                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2574                 return TRUE;
2576         case REQ_SCREEN_RESIZE:
2577                 resize_display();
2578                 /* Fall-through */
2579         case REQ_SCREEN_REDRAW:
2580                 redraw_display();
2581                 break;
2583         case REQ_EDIT:
2584                 report("Nothing to edit");
2585                 break;
2588         case REQ_ENTER:
2589                 report("Nothing to enter");
2590                 break;
2593         case REQ_VIEW_CLOSE:
2594                 /* XXX: Mark closed views by letting view->parent point to the
2595                  * view itself. Parents to closed view should never be
2596                  * followed. */
2597                 if (view->parent &&
2598                     view->parent->parent != view->parent) {
2599                         memset(display, 0, sizeof(display));
2600                         current_view = 0;
2601                         display[current_view] = view->parent;
2602                         view->parent = view;
2603                         resize_display();
2604                         redraw_display();
2605                         break;
2606                 }
2607                 /* Fall-through */
2608         case REQ_QUIT:
2609                 return FALSE;
2611         default:
2612                 /* An unknown key will show most commonly used commands. */
2613                 report("Unknown key, press 'h' for help");
2614                 return TRUE;
2615         }
2617         return TRUE;
2621 /*
2622  * Pager backend
2623  */
2625 static bool
2626 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2628         char *text = line->data;
2629         enum line_type type = line->type;
2630         int attr;
2632         wmove(view->win, lineno, 0);
2634         if (selected) {
2635                 type = LINE_CURSOR;
2636                 wchgat(view->win, -1, 0, type, NULL);
2637         }
2639         attr = get_line_attr(type);
2640         wattrset(view->win, attr);
2642         if (opt_line_number || opt_tab_size < TABSIZE) {
2643                 static char spaces[] = "                    ";
2644                 int col_offset = 0, col = 0;
2646                 if (opt_line_number) {
2647                         unsigned long real_lineno = view->offset + lineno + 1;
2649                         if (real_lineno == 1 ||
2650                             (real_lineno % opt_num_interval) == 0) {
2651                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2653                         } else {
2654                                 waddnstr(view->win, spaces,
2655                                          MIN(view->digits, STRING_SIZE(spaces)));
2656                         }
2657                         waddstr(view->win, ": ");
2658                         col_offset = view->digits + 2;
2659                 }
2661                 while (text && col_offset + col < view->width) {
2662                         int cols_max = view->width - col_offset - col;
2663                         char *pos = text;
2664                         int cols;
2666                         if (*text == '\t') {
2667                                 text++;
2668                                 assert(sizeof(spaces) > TABSIZE);
2669                                 pos = spaces;
2670                                 cols = opt_tab_size - (col % opt_tab_size);
2672                         } else {
2673                                 text = strchr(text, '\t');
2674                                 cols = line ? text - pos : strlen(pos);
2675                         }
2677                         waddnstr(view->win, pos, MIN(cols, cols_max));
2678                         col += cols;
2679                 }
2681         } else {
2682                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2684                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2685         }
2687         return TRUE;
2690 static bool
2691 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2693         char refbuf[SIZEOF_STR];
2694         char *ref = NULL;
2695         FILE *pipe;
2697         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2698                 return TRUE;
2700         pipe = popen(refbuf, "r");
2701         if (!pipe)
2702                 return TRUE;
2704         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2705                 ref = chomp_string(ref);
2706         pclose(pipe);
2708         if (!ref || !*ref)
2709                 return TRUE;
2711         /* This is the only fatal call, since it can "corrupt" the buffer. */
2712         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2713                 return FALSE;
2715         return TRUE;
2718 static void
2719 add_pager_refs(struct view *view, struct line *line)
2721         char buf[SIZEOF_STR];
2722         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2723         struct ref **refs;
2724         size_t bufpos = 0, refpos = 0;
2725         const char *sep = "Refs: ";
2726         bool is_tag = FALSE;
2728         assert(line->type == LINE_COMMIT);
2730         refs = get_refs(commit_id);
2731         if (!refs) {
2732                 if (view == VIEW(REQ_VIEW_DIFF))
2733                         goto try_add_describe_ref;
2734                 return;
2735         }
2737         do {
2738                 struct ref *ref = refs[refpos];
2739                 char *fmt = ref->tag    ? "%s[%s]" :
2740                             ref->remote ? "%s<%s>" : "%s%s";
2742                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2743                         return;
2744                 sep = ", ";
2745                 if (ref->tag)
2746                         is_tag = TRUE;
2747         } while (refs[refpos++]->next);
2749         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2750 try_add_describe_ref:
2751                 /* Add <tag>-g<commit_id> "fake" reference. */
2752                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2753                         return;
2754         }
2756         if (bufpos == 0)
2757                 return;
2759         if (!realloc_lines(view, view->line_size + 1))
2760                 return;
2762         add_line_text(view, buf, LINE_PP_REFS);
2765 static bool
2766 pager_read(struct view *view, char *data)
2768         struct line *line;
2770         if (!data)
2771                 return TRUE;
2773         line = add_line_text(view, data, get_line_type(data));
2774         if (!line)
2775                 return FALSE;
2777         if (line->type == LINE_COMMIT &&
2778             (view == VIEW(REQ_VIEW_DIFF) ||
2779              view == VIEW(REQ_VIEW_LOG)))
2780                 add_pager_refs(view, line);
2782         return TRUE;
2785 static enum request
2786 pager_request(struct view *view, enum request request, struct line *line)
2788         int split = 0;
2790         if (request != REQ_ENTER)
2791                 return request;
2793         if (line->type == LINE_COMMIT &&
2794            (view == VIEW(REQ_VIEW_LOG) ||
2795             view == VIEW(REQ_VIEW_PAGER))) {
2796                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2797                 split = 1;
2798         }
2800         /* Always scroll the view even if it was split. That way
2801          * you can use Enter to scroll through the log view and
2802          * split open each commit diff. */
2803         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2805         /* FIXME: A minor workaround. Scrolling the view will call report("")
2806          * but if we are scrolling a non-current view this won't properly
2807          * update the view title. */
2808         if (split)
2809                 update_view_title(view);
2811         return REQ_NONE;
2814 static bool
2815 pager_grep(struct view *view, struct line *line)
2817         regmatch_t pmatch;
2818         char *text = line->data;
2820         if (!*text)
2821                 return FALSE;
2823         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2824                 return FALSE;
2826         return TRUE;
2829 static void
2830 pager_select(struct view *view, struct line *line)
2832         if (line->type == LINE_COMMIT) {
2833                 char *text = (char *)line->data + STRING_SIZE("commit ");
2835                 if (view != VIEW(REQ_VIEW_PAGER))
2836                         string_copy_rev(view->ref, text);
2837                 string_copy_rev(ref_commit, text);
2838         }
2841 static struct view_ops pager_ops = {
2842         "line",
2843         NULL,
2844         pager_read,
2845         pager_draw,
2846         pager_request,
2847         pager_grep,
2848         pager_select,
2849 };
2852 /*
2853  * Help backend
2854  */
2856 static bool
2857 help_open(struct view *view)
2859         char buf[BUFSIZ];
2860         int lines = ARRAY_SIZE(req_info) + 2;
2861         int i;
2863         if (view->lines > 0)
2864                 return TRUE;
2866         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2867                 if (!req_info[i].request)
2868                         lines++;
2870         lines += run_requests + 1;
2872         view->line = calloc(lines, sizeof(*view->line));
2873         if (!view->line)
2874                 return FALSE;
2876         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2878         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2879                 char *key;
2881                 if (req_info[i].request == REQ_NONE)
2882                         continue;
2884                 if (!req_info[i].request) {
2885                         add_line_text(view, "", LINE_DEFAULT);
2886                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2887                         continue;
2888                 }
2890                 key = get_key(req_info[i].request);
2891                 if (!*key)
2892                         key = "(no key defined)";
2894                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2895                         continue;
2897                 add_line_text(view, buf, LINE_DEFAULT);
2898         }
2900         if (run_requests) {
2901                 add_line_text(view, "", LINE_DEFAULT);
2902                 add_line_text(view, "External commands:", LINE_DEFAULT);
2903         }
2905         for (i = 0; i < run_requests; i++) {
2906                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2907                 char *key;
2909                 if (!req)
2910                         continue;
2912                 key = get_key_name(req->key);
2913                 if (!*key)
2914                         key = "(no key defined)";
2916                 if (!string_format(buf, "    %-10s %-14s `%s`",
2917                                    keymap_table[req->keymap].name,
2918                                    key, req->cmd))
2919                         continue;
2921                 add_line_text(view, buf, LINE_DEFAULT);
2922         }
2924         return TRUE;
2927 static struct view_ops help_ops = {
2928         "line",
2929         help_open,
2930         NULL,
2931         pager_draw,
2932         pager_request,
2933         pager_grep,
2934         pager_select,
2935 };
2938 /*
2939  * Tree backend
2940  */
2942 struct tree_stack_entry {
2943         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2944         unsigned long lineno;           /* Line number to restore */
2945         char *name;                     /* Position of name in opt_path */
2946 };
2948 /* The top of the path stack. */
2949 static struct tree_stack_entry *tree_stack = NULL;
2950 unsigned long tree_lineno = 0;
2952 static void
2953 pop_tree_stack_entry(void)
2955         struct tree_stack_entry *entry = tree_stack;
2957         tree_lineno = entry->lineno;
2958         entry->name[0] = 0;
2959         tree_stack = entry->prev;
2960         free(entry);
2963 static void
2964 push_tree_stack_entry(char *name, unsigned long lineno)
2966         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2967         size_t pathlen = strlen(opt_path);
2969         if (!entry)
2970                 return;
2972         entry->prev = tree_stack;
2973         entry->name = opt_path + pathlen;
2974         tree_stack = entry;
2976         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2977                 pop_tree_stack_entry();
2978                 return;
2979         }
2981         /* Move the current line to the first tree entry. */
2982         tree_lineno = 1;
2983         entry->lineno = lineno;
2986 /* Parse output from git-ls-tree(1):
2987  *
2988  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2989  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2990  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2991  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2992  */
2994 #define SIZEOF_TREE_ATTR \
2995         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2997 #define TREE_UP_FORMAT "040000 tree %s\t.."
2999 static int
3000 tree_compare_entry(enum line_type type1, char *name1,
3001                    enum line_type type2, char *name2)
3003         if (type1 != type2) {
3004                 if (type1 == LINE_TREE_DIR)
3005                         return -1;
3006                 return 1;
3007         }
3009         return strcmp(name1, name2);
3012 static bool
3013 tree_read(struct view *view, char *text)
3015         size_t textlen = text ? strlen(text) : 0;
3016         char buf[SIZEOF_STR];
3017         unsigned long pos;
3018         enum line_type type;
3019         bool first_read = view->lines == 0;
3021         if (textlen <= SIZEOF_TREE_ATTR)
3022                 return FALSE;
3024         type = text[STRING_SIZE("100644 ")] == 't'
3025              ? LINE_TREE_DIR : LINE_TREE_FILE;
3027         if (first_read) {
3028                 /* Add path info line */
3029                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3030                     !realloc_lines(view, view->line_size + 1) ||
3031                     !add_line_text(view, buf, LINE_DEFAULT))
3032                         return FALSE;
3034                 /* Insert "link" to parent directory. */
3035                 if (*opt_path) {
3036                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3037                             !realloc_lines(view, view->line_size + 1) ||
3038                             !add_line_text(view, buf, LINE_TREE_DIR))
3039                                 return FALSE;
3040                 }
3041         }
3043         /* Strip the path part ... */
3044         if (*opt_path) {
3045                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3046                 size_t striplen = strlen(opt_path);
3047                 char *path = text + SIZEOF_TREE_ATTR;
3049                 if (pathlen > striplen)
3050                         memmove(path, path + striplen,
3051                                 pathlen - striplen + 1);
3052         }
3054         /* Skip "Directory ..." and ".." line. */
3055         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3056                 struct line *line = &view->line[pos];
3057                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3058                 char *path2 = text + SIZEOF_TREE_ATTR;
3059                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3061                 if (cmp <= 0)
3062                         continue;
3064                 text = strdup(text);
3065                 if (!text)
3066                         return FALSE;
3068                 if (view->lines > pos)
3069                         memmove(&view->line[pos + 1], &view->line[pos],
3070                                 (view->lines - pos) * sizeof(*line));
3072                 line = &view->line[pos];
3073                 line->data = text;
3074                 line->type = type;
3075                 view->lines++;
3076                 return TRUE;
3077         }
3079         if (!add_line_text(view, text, type))
3080                 return FALSE;
3082         if (tree_lineno > view->lineno) {
3083                 view->lineno = tree_lineno;
3084                 tree_lineno = 0;
3085         }
3087         return TRUE;
3090 static enum request
3091 tree_request(struct view *view, enum request request, struct line *line)
3093         enum open_flags flags;
3095         if (request == REQ_TREE_PARENT) {
3096                 if (*opt_path) {
3097                         /* fake 'cd  ..' */
3098                         request = REQ_ENTER;
3099                         line = &view->line[1];
3100                 } else {
3101                         /* quit view if at top of tree */
3102                         return REQ_VIEW_CLOSE;
3103                 }
3104         }
3105         if (request != REQ_ENTER)
3106                 return request;
3108         /* Cleanup the stack if the tree view is at a different tree. */
3109         while (!*opt_path && tree_stack)
3110                 pop_tree_stack_entry();
3112         switch (line->type) {
3113         case LINE_TREE_DIR:
3114                 /* Depending on whether it is a subdir or parent (updir?) link
3115                  * mangle the path buffer. */
3116                 if (line == &view->line[1] && *opt_path) {
3117                         pop_tree_stack_entry();
3119                 } else {
3120                         char *data = line->data;
3121                         char *basename = data + SIZEOF_TREE_ATTR;
3123                         push_tree_stack_entry(basename, view->lineno);
3124                 }
3126                 /* Trees and subtrees share the same ID, so they are not not
3127                  * unique like blobs. */
3128                 flags = OPEN_RELOAD;
3129                 request = REQ_VIEW_TREE;
3130                 break;
3132         case LINE_TREE_FILE:
3133                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3134                 request = REQ_VIEW_BLOB;
3135                 break;
3137         default:
3138                 return TRUE;
3139         }
3141         open_view(view, request, flags);
3142         if (request == REQ_VIEW_TREE) {
3143                 view->lineno = tree_lineno;
3144         }
3146         return REQ_NONE;
3149 static void
3150 tree_select(struct view *view, struct line *line)
3152         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3154         if (line->type == LINE_TREE_FILE) {
3155                 string_copy_rev(ref_blob, text);
3157         } else if (line->type != LINE_TREE_DIR) {
3158                 return;
3159         }
3161         string_copy_rev(view->ref, text);
3164 static struct view_ops tree_ops = {
3165         "file",
3166         NULL,
3167         tree_read,
3168         pager_draw,
3169         tree_request,
3170         pager_grep,
3171         tree_select,
3172 };
3174 static bool
3175 blob_read(struct view *view, char *line)
3177         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3180 static struct view_ops blob_ops = {
3181         "line",
3182         NULL,
3183         blob_read,
3184         pager_draw,
3185         pager_request,
3186         pager_grep,
3187         pager_select,
3188 };
3191 /*
3192  * Status backend
3193  */
3195 struct status {
3196         char status;
3197         struct {
3198                 mode_t mode;
3199                 char rev[SIZEOF_REV];
3200                 char name[SIZEOF_STR];
3201         } old;
3202         struct {
3203                 mode_t mode;
3204                 char rev[SIZEOF_REV];
3205                 char name[SIZEOF_STR];
3206         } new;
3207 };
3209 static struct status stage_status;
3210 static enum line_type stage_line_type;
3212 /* Get fields from the diff line:
3213  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3214  */
3215 static inline bool
3216 status_get_diff(struct status *file, char *buf, size_t bufsize)
3218         char *old_mode = buf +  1;
3219         char *new_mode = buf +  8;
3220         char *old_rev  = buf + 15;
3221         char *new_rev  = buf + 56;
3222         char *status   = buf + 97;
3224         if (bufsize < 99 ||
3225             old_mode[-1] != ':' ||
3226             new_mode[-1] != ' ' ||
3227             old_rev[-1]  != ' ' ||
3228             new_rev[-1]  != ' ' ||
3229             status[-1]   != ' ')
3230                 return FALSE;
3232         file->status = *status;
3234         string_copy_rev(file->old.rev, old_rev);
3235         string_copy_rev(file->new.rev, new_rev);
3237         file->old.mode = strtoul(old_mode, NULL, 8);
3238         file->new.mode = strtoul(new_mode, NULL, 8);
3240         file->old.name[0] = file->new.name[0] = 0;
3242         return TRUE;
3245 static bool
3246 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3248         struct status *file = NULL;
3249         struct status *unmerged = NULL;
3250         char buf[SIZEOF_STR * 4];
3251         size_t bufsize = 0;
3252         FILE *pipe;
3254         pipe = popen(cmd, "r");
3255         if (!pipe)
3256                 return FALSE;
3258         add_line_data(view, NULL, type);
3260         while (!feof(pipe) && !ferror(pipe)) {
3261                 char *sep;
3262                 size_t readsize;
3264                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3265                 if (!readsize)
3266                         break;
3267                 bufsize += readsize;
3269                 /* Process while we have NUL chars. */
3270                 while ((sep = memchr(buf, 0, bufsize))) {
3271                         size_t sepsize = sep - buf + 1;
3273                         if (!file) {
3274                                 if (!realloc_lines(view, view->line_size + 1))
3275                                         goto error_out;
3277                                 file = calloc(1, sizeof(*file));
3278                                 if (!file)
3279                                         goto error_out;
3281                                 add_line_data(view, file, type);
3282                         }
3284                         /* Parse diff info part. */
3285                         if (!diff) {
3286                                 file->status = '?';
3288                         } else if (!file->status) {
3289                                 if (!status_get_diff(file, buf, sepsize))
3290                                         goto error_out;
3292                                 bufsize -= sepsize;
3293                                 memmove(buf, sep + 1, bufsize);
3295                                 sep = memchr(buf, 0, bufsize);
3296                                 if (!sep)
3297                                         break;
3298                                 sepsize = sep - buf + 1;
3300                                 /* Collapse all 'M'odified entries that
3301                                  * follow a associated 'U'nmerged entry.
3302                                  */
3303                                 if (file->status == 'U') {
3304                                         unmerged = file;
3306                                 } else if (unmerged) {
3307                                         int collapse = !strcmp(buf, unmerged->new.name);
3309                                         unmerged = NULL;
3310                                         if (collapse) {
3311                                                 free(file);
3312                                                 view->lines--;
3313                                                 continue;
3314                                         }
3315                                 }
3316                         }
3318                         /* Grab the old name for rename/copy. */
3319                         if (!*file->old.name &&
3320                             (file->status == 'R' || file->status == 'C')) {
3321                                 sepsize = sep - buf + 1;
3322                                 string_ncopy(file->old.name, buf, sepsize);
3323                                 bufsize -= sepsize;
3324                                 memmove(buf, sep + 1, bufsize);
3326                                 sep = memchr(buf, 0, bufsize);
3327                                 if (!sep)
3328                                         break;
3329                                 sepsize = sep - buf + 1;
3330                         }
3332                         /* git-ls-files just delivers a NUL separated
3333                          * list of file names similar to the second half
3334                          * of the git-diff-* output. */
3335                         string_ncopy(file->new.name, buf, sepsize);
3336                         if (!*file->old.name)
3337                                 string_copy(file->old.name, file->new.name);
3338                         bufsize -= sepsize;
3339                         memmove(buf, sep + 1, bufsize);
3340                         file = NULL;
3341                 }
3342         }
3344         if (ferror(pipe)) {
3345 error_out:
3346                 pclose(pipe);
3347                 return FALSE;
3348         }
3350         if (!view->line[view->lines - 1].data)
3351                 add_line_data(view, NULL, LINE_STAT_NONE);
3353         pclose(pipe);
3354         return TRUE;
3357 /* Don't show unmerged entries in the staged section. */
3358 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3359 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3360 #define STATUS_LIST_OTHER_CMD \
3361         "git ls-files -z --others --exclude-per-directory=.gitignore"
3363 #define STATUS_DIFF_INDEX_SHOW_CMD \
3364         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3366 #define STATUS_DIFF_FILES_SHOW_CMD \
3367         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3369 /* First parse staged info using git-diff-index(1), then parse unstaged
3370  * info using git-diff-files(1), and finally untracked files using
3371  * git-ls-files(1). */
3372 static bool
3373 status_open(struct view *view)
3375         struct stat statbuf;
3376         char exclude[SIZEOF_STR];
3377         char cmd[SIZEOF_STR];
3378         unsigned long prev_lineno = view->lineno;
3379         size_t i;
3381         for (i = 0; i < view->lines; i++)
3382                 free(view->line[i].data);
3383         free(view->line);
3384         view->lines = view->line_size = view->lineno = 0;
3385         view->line = NULL;
3387         if (!realloc_lines(view, view->line_size + 6))
3388                 return FALSE;
3390         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3391                 return FALSE;
3393         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3395         if (stat(exclude, &statbuf) >= 0) {
3396                 size_t cmdsize = strlen(cmd);
3398                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3399                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3400                         return FALSE;
3401         }
3403         system("git update-index -q --refresh");
3405         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3406             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3407             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3408                 return FALSE;
3410         /* If all went well restore the previous line number to stay in
3411          * the context. */
3412         if (prev_lineno < view->lines)
3413                 view->lineno = prev_lineno;
3414         else
3415                 view->lineno = view->lines - 1;
3417         return TRUE;
3420 static bool
3421 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3423         struct status *status = line->data;
3424         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3426         wmove(view->win, lineno, 0);
3428         if (selected) {
3429                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3430                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3431                 tilde_attr = -1;
3433         } else if (!status && line->type != LINE_STAT_NONE) {
3434                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3435                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3437         } else {
3438                 wattrset(view->win, get_line_attr(line->type));
3439         }
3441         if (!status) {
3442                 char *text;
3444                 switch (line->type) {
3445                 case LINE_STAT_STAGED:
3446                         text = "Changes to be committed:";
3447                         break;
3449                 case LINE_STAT_UNSTAGED:
3450                         text = "Changed but not updated:";
3451                         break;
3453                 case LINE_STAT_UNTRACKED:
3454                         text = "Untracked files:";
3455                         break;
3457                 case LINE_STAT_NONE:
3458                         text = "    (no files)";
3459                         break;
3461                 default:
3462                         return FALSE;
3463                 }
3465                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3466                 return TRUE;
3467         }
3469         waddch(view->win, status->status);
3470         if (!selected)
3471                 wattrset(view->win, A_NORMAL);
3472         wmove(view->win, lineno, 4);
3473         if (view->width < 5)
3474                 return TRUE;
3476         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3477         return TRUE;
3480 static enum request
3481 status_enter(struct view *view, struct line *line)
3483         struct status *status = line->data;
3484         char oldpath[SIZEOF_STR] = "";
3485         char newpath[SIZEOF_STR] = "";
3486         char *info;
3487         size_t cmdsize = 0;
3489         if (line->type == LINE_STAT_NONE ||
3490             (!status && line[1].type == LINE_STAT_NONE)) {
3491                 report("No file to diff");
3492                 return REQ_NONE;
3493         }
3495         if (status) {
3496                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3497                         return REQ_QUIT;
3498                 /* Diffs for unmerged entries are empty when pasing the
3499                  * new path, so leave it empty. */
3500                 if (status->status != 'U' &&
3501                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3502                         return REQ_QUIT;
3503         }
3505         if (opt_cdup[0] &&
3506             line->type != LINE_STAT_UNTRACKED &&
3507             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3508                 return REQ_QUIT;
3510         switch (line->type) {
3511         case LINE_STAT_STAGED:
3512                 if (!string_format_from(opt_cmd, &cmdsize,
3513                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3514                         return REQ_QUIT;
3515                 if (status)
3516                         info = "Staged changes to %s";
3517                 else
3518                         info = "Staged changes";
3519                 break;
3521         case LINE_STAT_UNSTAGED:
3522                 if (!string_format_from(opt_cmd, &cmdsize,
3523                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3524                         return REQ_QUIT;
3525                 if (status)
3526                         info = "Unstaged changes to %s";
3527                 else
3528                         info = "Unstaged changes";
3529                 break;
3531         case LINE_STAT_UNTRACKED:
3532                 if (opt_pipe)
3533                         return REQ_QUIT;
3536                 if (!status) {
3537                         report("No file to show");
3538                         return REQ_NONE;
3539                 }
3541                 opt_pipe = fopen(status->new.name, "r");
3542                 info = "Untracked file %s";
3543                 break;
3545         default:
3546                 die("line type %d not handled in switch", line->type);
3547         }
3549         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3550         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3551                 if (status) {
3552                         stage_status = *status;
3553                 } else {
3554                         memset(&stage_status, 0, sizeof(stage_status));
3555                 }
3557                 stage_line_type = line->type;
3558                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3559         }
3561         return REQ_NONE;
3565 static bool
3566 status_update_file(struct view *view, struct status *status, enum line_type type)
3568         char cmd[SIZEOF_STR];
3569         char buf[SIZEOF_STR];
3570         size_t cmdsize = 0;
3571         size_t bufsize = 0;
3572         size_t written = 0;
3573         FILE *pipe;
3575         if (opt_cdup[0] &&
3576             type != LINE_STAT_UNTRACKED &&
3577             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3578                 return FALSE;
3580         switch (type) {
3581         case LINE_STAT_STAGED:
3582                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3583                                         status->old.mode,
3584                                         status->old.rev,
3585                                         status->old.name, 0))
3586                         return FALSE;
3588                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3589                 break;
3591         case LINE_STAT_UNSTAGED:
3592         case LINE_STAT_UNTRACKED:
3593                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3594                         return FALSE;
3596                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3597                 break;
3599         default:
3600                 die("line type %d not handled in switch", type);
3601         }
3603         pipe = popen(cmd, "w");
3604         if (!pipe)
3605                 return FALSE;
3607         while (!ferror(pipe) && written < bufsize) {
3608                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3609         }
3611         pclose(pipe);
3613         if (written != bufsize)
3614                 return FALSE;
3616         return TRUE;
3619 static void
3620 status_update(struct view *view)
3622         struct line *line = &view->line[view->lineno];
3624         assert(view->lines);
3626         if (!line->data) {
3627                 while (++line < view->line + view->lines && line->data) {
3628                         if (!status_update_file(view, line->data, line->type))
3629                                 report("Failed to update file status");
3630                 }
3632                 if (!line[-1].data) {
3633                         report("Nothing to update");
3634                         return;
3635                 }
3637         } else if (!status_update_file(view, line->data, line->type)) {
3638                 report("Failed to update file status");
3639         }
3642 static enum request
3643 status_request(struct view *view, enum request request, struct line *line)
3645         struct status *status = line->data;
3647         switch (request) {
3648         case REQ_STATUS_UPDATE:
3649                 status_update(view);
3650                 break;
3652         case REQ_STATUS_MERGE:
3653                 if (!status || status->status != 'U') {
3654                         report("Merging only possible for files with unmerged status ('U').");
3655                         return REQ_NONE;
3656                 }
3657                 open_mergetool(status->new.name);
3658                 break;
3660         case REQ_EDIT:
3661                 if (!status)
3662                         return request;
3664                 open_editor(status->status != '?', status->new.name);
3665                 break;
3667         case REQ_ENTER:
3668                 /* After returning the status view has been split to
3669                  * show the stage view. No further reloading is
3670                  * necessary. */
3671                 status_enter(view, line);
3672                 return REQ_NONE;
3674         case REQ_REFRESH:
3675                 /* Simply reload the view. */
3676                 break;
3678         default:
3679                 return request;
3680         }
3682         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3684         return REQ_NONE;
3687 static void
3688 status_select(struct view *view, struct line *line)
3690         struct status *status = line->data;
3691         char file[SIZEOF_STR] = "all files";
3692         char *text;
3693         char *key;
3695         if (status && !string_format(file, "'%s'", status->new.name))
3696                 return;
3698         if (!status && line[1].type == LINE_STAT_NONE)
3699                 line++;
3701         switch (line->type) {
3702         case LINE_STAT_STAGED:
3703                 text = "Press %s to unstage %s for commit";
3704                 break;
3706         case LINE_STAT_UNSTAGED:
3707                 text = "Press %s to stage %s for commit";
3708                 break;
3710         case LINE_STAT_UNTRACKED:
3711                 text = "Press %s to stage %s for addition";
3712                 break;
3714         case LINE_STAT_NONE:
3715                 text = "Nothing to update";
3716                 break;
3718         default:
3719                 die("line type %d not handled in switch", line->type);
3720         }
3722         if (status && status->status == 'U') {
3723                 text = "Press %s to resolve conflict in %s";
3724                 key = get_key(REQ_STATUS_MERGE);
3726         } else {
3727                 key = get_key(REQ_STATUS_UPDATE);
3728         }
3730         string_format(view->ref, text, key, file);
3733 static bool
3734 status_grep(struct view *view, struct line *line)
3736         struct status *status = line->data;
3737         enum { S_STATUS, S_NAME, S_END } state;
3738         char buf[2] = "?";
3739         regmatch_t pmatch;
3741         if (!status)
3742                 return FALSE;
3744         for (state = S_STATUS; state < S_END; state++) {
3745                 char *text;
3747                 switch (state) {
3748                 case S_NAME:    text = status->new.name;        break;
3749                 case S_STATUS:
3750                         buf[0] = status->status;
3751                         text = buf;
3752                         break;
3754                 default:
3755                         return FALSE;
3756                 }
3758                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3759                         return TRUE;
3760         }
3762         return FALSE;
3765 static struct view_ops status_ops = {
3766         "file",
3767         status_open,
3768         NULL,
3769         status_draw,
3770         status_request,
3771         status_grep,
3772         status_select,
3773 };
3776 static bool
3777 stage_diff_line(FILE *pipe, struct line *line)
3779         char *buf = line->data;
3780         size_t bufsize = strlen(buf);
3781         size_t written = 0;
3783         while (!ferror(pipe) && written < bufsize) {
3784                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3785         }
3787         fputc('\n', pipe);
3789         return written == bufsize;
3792 static struct line *
3793 stage_diff_hdr(struct view *view, struct line *line)
3795         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3796         struct line *diff_hdr;
3798         if (line->type == LINE_DIFF_CHUNK)
3799                 diff_hdr = line - 1;
3800         else
3801                 diff_hdr = view->line + 1;
3803         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3804                 if (diff_hdr->type == LINE_DIFF_HEADER)
3805                         return diff_hdr;
3807                 diff_hdr += diff_hdr_dir;
3808         }
3810         return NULL;
3813 static bool
3814 stage_update_chunk(struct view *view, struct line *line)
3816         char cmd[SIZEOF_STR];
3817         size_t cmdsize = 0;
3818         struct line *diff_hdr, *diff_chunk, *diff_end;
3819         FILE *pipe;
3821         diff_hdr = stage_diff_hdr(view, line);
3822         if (!diff_hdr)
3823                 return FALSE;
3825         if (opt_cdup[0] &&
3826             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3827                 return FALSE;
3829         if (!string_format_from(cmd, &cmdsize,
3830                                 "git apply --cached %s - && "
3831                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3832                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3833                 return FALSE;
3835         pipe = popen(cmd, "w");
3836         if (!pipe)
3837                 return FALSE;
3839         diff_end = view->line + view->lines;
3840         if (line->type != LINE_DIFF_CHUNK) {
3841                 diff_chunk = diff_hdr;
3843         } else {
3844                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3845                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3846                             diff_chunk->type == LINE_DIFF_HEADER)
3847                                 diff_end = diff_chunk;
3849                 diff_chunk = line;
3851                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3852                         switch (diff_hdr->type) {
3853                         case LINE_DIFF_HEADER:
3854                         case LINE_DIFF_INDEX:
3855                         case LINE_DIFF_ADD:
3856                         case LINE_DIFF_DEL:
3857                                 break;
3859                         default:
3860                                 diff_hdr++;
3861                                 continue;
3862                         }
3864                         if (!stage_diff_line(pipe, diff_hdr++)) {
3865                                 pclose(pipe);
3866                                 return FALSE;
3867                         }
3868                 }
3869         }
3871         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3872                 diff_chunk++;
3874         pclose(pipe);
3876         if (diff_chunk != diff_end)
3877                 return FALSE;
3879         return TRUE;
3882 static void
3883 stage_update(struct view *view, struct line *line)
3885         if (stage_line_type != LINE_STAT_UNTRACKED &&
3886             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3887                 if (!stage_update_chunk(view, line)) {
3888                         report("Failed to apply chunk");
3889                         return;
3890                 }
3892         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3893                 report("Failed to update file");
3894                 return;
3895         }
3897         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3899         view = VIEW(REQ_VIEW_STATUS);
3900         if (view_is_displayed(view))
3901                 status_enter(view, &view->line[view->lineno]);
3904 static enum request
3905 stage_request(struct view *view, enum request request, struct line *line)
3907         switch (request) {
3908         case REQ_STATUS_UPDATE:
3909                 stage_update(view, line);
3910                 break;
3912         case REQ_EDIT:
3913                 if (!stage_status.new.name[0])
3914                         return request;
3916                 open_editor(stage_status.status != '?', stage_status.new.name);
3917                 break;
3919         case REQ_ENTER:
3920                 pager_request(view, request, line);
3921                 break;
3923         default:
3924                 return request;
3925         }
3927         return REQ_NONE;
3930 static struct view_ops stage_ops = {
3931         "line",
3932         NULL,
3933         pager_read,
3934         pager_draw,
3935         stage_request,
3936         pager_grep,
3937         pager_select,
3938 };
3941 /*
3942  * Revision graph
3943  */
3945 struct commit {
3946         char id[SIZEOF_REV];            /* SHA1 ID. */
3947         char title[128];                /* First line of the commit message. */
3948         char author[75];                /* Author of the commit. */
3949         struct tm time;                 /* Date from the author ident. */
3950         struct ref **refs;              /* Repository references. */
3951         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3952         size_t graph_size;              /* The width of the graph array. */
3953 };
3955 /* Size of rev graph with no  "padding" columns */
3956 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3958 struct rev_graph {
3959         struct rev_graph *prev, *next, *parents;
3960         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3961         size_t size;
3962         struct commit *commit;
3963         size_t pos;
3964         unsigned int boundary:1;
3965 };
3967 /* Parents of the commit being visualized. */
3968 static struct rev_graph graph_parents[4];
3970 /* The current stack of revisions on the graph. */
3971 static struct rev_graph graph_stacks[4] = {
3972         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3973         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3974         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3975         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3976 };
3978 static inline bool
3979 graph_parent_is_merge(struct rev_graph *graph)
3981         return graph->parents->size > 1;
3984 static inline void
3985 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3987         struct commit *commit = graph->commit;
3989         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3990                 commit->graph[commit->graph_size++] = symbol;
3993 static void
3994 done_rev_graph(struct rev_graph *graph)
3996         if (graph_parent_is_merge(graph) &&
3997             graph->pos < graph->size - 1 &&
3998             graph->next->size == graph->size + graph->parents->size - 1) {
3999                 size_t i = graph->pos + graph->parents->size - 1;
4001                 graph->commit->graph_size = i * 2;
4002                 while (i < graph->next->size - 1) {
4003                         append_to_rev_graph(graph, ' ');
4004                         append_to_rev_graph(graph, '\\');
4005                         i++;
4006                 }
4007         }
4009         graph->size = graph->pos = 0;
4010         graph->commit = NULL;
4011         memset(graph->parents, 0, sizeof(*graph->parents));
4014 static void
4015 push_rev_graph(struct rev_graph *graph, char *parent)
4017         int i;
4019         /* "Collapse" duplicate parents lines.
4020          *
4021          * FIXME: This needs to also update update the drawn graph but
4022          * for now it just serves as a method for pruning graph lines. */
4023         for (i = 0; i < graph->size; i++)
4024                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4025                         return;
4027         if (graph->size < SIZEOF_REVITEMS) {
4028                 string_copy_rev(graph->rev[graph->size++], parent);
4029         }
4032 static chtype
4033 get_rev_graph_symbol(struct rev_graph *graph)
4035         chtype symbol;
4037         if (graph->boundary)
4038                 symbol = REVGRAPH_BOUND;
4039         else if (graph->parents->size == 0)
4040                 symbol = REVGRAPH_INIT;
4041         else if (graph_parent_is_merge(graph))
4042                 symbol = REVGRAPH_MERGE;
4043         else if (graph->pos >= graph->size)
4044                 symbol = REVGRAPH_BRANCH;
4045         else
4046                 symbol = REVGRAPH_COMMIT;
4048         return symbol;
4051 static void
4052 draw_rev_graph(struct rev_graph *graph)
4054         struct rev_filler {
4055                 chtype separator, line;
4056         };
4057         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4058         static struct rev_filler fillers[] = {
4059                 { ' ',  REVGRAPH_LINE },
4060                 { '`',  '.' },
4061                 { '\'', ' ' },
4062                 { '/',  ' ' },
4063         };
4064         chtype symbol = get_rev_graph_symbol(graph);
4065         struct rev_filler *filler;
4066         size_t i;
4068         filler = &fillers[DEFAULT];
4070         for (i = 0; i < graph->pos; i++) {
4071                 append_to_rev_graph(graph, filler->line);
4072                 if (graph_parent_is_merge(graph->prev) &&
4073                     graph->prev->pos == i)
4074                         filler = &fillers[RSHARP];
4076                 append_to_rev_graph(graph, filler->separator);
4077         }
4079         /* Place the symbol for this revision. */
4080         append_to_rev_graph(graph, symbol);
4082         if (graph->prev->size > graph->size)
4083                 filler = &fillers[RDIAG];
4084         else
4085                 filler = &fillers[DEFAULT];
4087         i++;
4089         for (; i < graph->size; i++) {
4090                 append_to_rev_graph(graph, filler->separator);
4091                 append_to_rev_graph(graph, filler->line);
4092                 if (graph_parent_is_merge(graph->prev) &&
4093                     i < graph->prev->pos + graph->parents->size)
4094                         filler = &fillers[RSHARP];
4095                 if (graph->prev->size > graph->size)
4096                         filler = &fillers[LDIAG];
4097         }
4099         if (graph->prev->size > graph->size) {
4100                 append_to_rev_graph(graph, filler->separator);
4101                 if (filler->line != ' ')
4102                         append_to_rev_graph(graph, filler->line);
4103         }
4106 /* Prepare the next rev graph */
4107 static void
4108 prepare_rev_graph(struct rev_graph *graph)
4110         size_t i;
4112         /* First, traverse all lines of revisions up to the active one. */
4113         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4114                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4115                         break;
4117                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4118         }
4120         /* Interleave the new revision parent(s). */
4121         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4122                 push_rev_graph(graph->next, graph->parents->rev[i]);
4124         /* Lastly, put any remaining revisions. */
4125         for (i = graph->pos + 1; i < graph->size; i++)
4126                 push_rev_graph(graph->next, graph->rev[i]);
4129 static void
4130 update_rev_graph(struct rev_graph *graph)
4132         /* If this is the finalizing update ... */
4133         if (graph->commit)
4134                 prepare_rev_graph(graph);
4136         /* Graph visualization needs a one rev look-ahead,
4137          * so the first update doesn't visualize anything. */
4138         if (!graph->prev->commit)
4139                 return;
4141         draw_rev_graph(graph->prev);
4142         done_rev_graph(graph->prev->prev);
4146 /*
4147  * Main view backend
4148  */
4150 static bool
4151 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4153         char buf[DATE_COLS + 1];
4154         struct commit *commit = line->data;
4155         enum line_type type;
4156         int col = 0;
4157         size_t timelen;
4158         int tilde_attr;
4159         int space;
4161         if (!*commit->author)
4162                 return FALSE;
4164         space = view->width;
4165         wmove(view->win, lineno, col);
4167         if (selected) {
4168                 type = LINE_CURSOR;
4169                 wattrset(view->win, get_line_attr(type));
4170                 wchgat(view->win, -1, 0, type, NULL);
4171                 tilde_attr = -1;
4172         } else {
4173                 type = LINE_MAIN_COMMIT;
4174                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4175                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4176         }
4178         {
4179                 int n;
4181                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4182                 n = draw_text(
4183                         view, buf, view->width - col, col, FALSE, tilde_attr);
4184                 draw_text(
4185                         view, " ", view->width - col - n, col + n, FALSE,
4186                         tilde_attr);
4188                 col += DATE_COLS;
4189                 wmove(view->win, lineno, col);
4190                 if (col >= view->width)
4191                         return TRUE;
4192         }
4193         if (type != LINE_CURSOR)
4194                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4196         {
4197                 int max_len;
4199                 max_len = view->width - col;
4200                 if (max_len > AUTHOR_COLS - 1)
4201                         max_len = AUTHOR_COLS - 1;
4202                 draw_text(
4203                         view, commit->author, max_len, col, TRUE, tilde_attr);
4204                 col += AUTHOR_COLS;
4205                 if (col >= view->width)
4206                         return TRUE;
4207         }
4209         if (opt_rev_graph && commit->graph_size) {
4210                 size_t graph_size = view->width - col;
4211                 size_t i;
4213                 if (type != LINE_CURSOR)
4214                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4215                 wmove(view->win, lineno, col);
4216                 if (graph_size > commit->graph_size)
4217                         graph_size = commit->graph_size;
4218                 /* Using waddch() instead of waddnstr() ensures that
4219                  * they'll be rendered correctly for the cursor line. */
4220                 for (i = 0; i < graph_size; i++)
4221                         waddch(view->win, commit->graph[i]);
4223                 col += commit->graph_size + 1;
4224                 if (col >= view->width)
4225                         return TRUE;
4226                 waddch(view->win, ' ');
4227         }
4228         if (type != LINE_CURSOR)
4229                 wattrset(view->win, A_NORMAL);
4231         wmove(view->win, lineno, col);
4233         if (commit->refs) {
4234                 size_t i = 0;
4236                 do {
4237                         if (type == LINE_CURSOR)
4238                                 ;
4239                         else if (commit->refs[i]->tag)
4240                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4241                         else if (commit->refs[i]->remote)
4242                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4243                         else
4244                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4246                         col += draw_text(
4247                                 view, "[", view->width - col, col, TRUE,
4248                                 tilde_attr);
4249                         col += draw_text(
4250                                 view, commit->refs[i]->name, view->width - col,
4251                                 col, TRUE, tilde_attr);
4252                         col += draw_text(
4253                                 view, "]", view->width - col, col, TRUE,
4254                                 tilde_attr);
4255                         if (type != LINE_CURSOR)
4256                                 wattrset(view->win, A_NORMAL);
4257                         col += draw_text(
4258                                 view, " ", view->width - col, col, TRUE,
4259                                 tilde_attr);
4260                         if (col >= view->width)
4261                                 return TRUE;
4262                 } while (commit->refs[i++]->next);
4263         }
4265         if (type != LINE_CURSOR)
4266                 wattrset(view->win, get_line_attr(type));
4268         col += draw_text(
4269                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4271         return TRUE;
4274 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4275 static bool
4276 main_read(struct view *view, char *line)
4278         static struct rev_graph *graph = graph_stacks;
4279         enum line_type type;
4280         struct commit *commit;
4282         if (!line) {
4283                 update_rev_graph(graph);
4284                 return TRUE;
4285         }
4287         type = get_line_type(line);
4288         if (type == LINE_COMMIT) {
4289                 commit = calloc(1, sizeof(struct commit));
4290                 if (!commit)
4291                         return FALSE;
4293                 line += STRING_SIZE("commit ");
4294                 if (*line == '-') {
4295                         graph->boundary = 1;
4296                         line++;
4297                 }
4299                 string_copy_rev(commit->id, line);
4300                 commit->refs = get_refs(commit->id);
4301                 graph->commit = commit;
4302                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4303                 return TRUE;
4304         }
4306         if (!view->lines)
4307                 return TRUE;
4308         commit = view->line[view->lines - 1].data;
4310         switch (type) {
4311         case LINE_PARENT:
4312                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4313                 break;
4315         case LINE_AUTHOR:
4316         {
4317                 /* Parse author lines where the name may be empty:
4318                  *      author  <email@address.tld> 1138474660 +0100
4319                  */
4320                 char *ident = line + STRING_SIZE("author ");
4321                 char *nameend = strchr(ident, '<');
4322                 char *emailend = strchr(ident, '>');
4324                 if (!nameend || !emailend)
4325                         break;
4327                 update_rev_graph(graph);
4328                 graph = graph->next;
4330                 *nameend = *emailend = 0;
4331                 ident = chomp_string(ident);
4332                 if (!*ident) {
4333                         ident = chomp_string(nameend + 1);
4334                         if (!*ident)
4335                                 ident = "Unknown";
4336                 }
4338                 string_ncopy(commit->author, ident, strlen(ident));
4340                 /* Parse epoch and timezone */
4341                 if (emailend[1] == ' ') {
4342                         char *secs = emailend + 2;
4343                         char *zone = strchr(secs, ' ');
4344                         time_t time = (time_t) atol(secs);
4346                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4347                                 long tz;
4349                                 zone++;
4350                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4351                                 tz += ('0' - zone[2]) * 60 * 60;
4352                                 tz += ('0' - zone[3]) * 60;
4353                                 tz += ('0' - zone[4]) * 60;
4355                                 if (zone[0] == '-')
4356                                         tz = -tz;
4358                                 time -= tz;
4359                         }
4361                         gmtime_r(&time, &commit->time);
4362                 }
4363                 break;
4364         }
4365         default:
4366                 /* Fill in the commit title if it has not already been set. */
4367                 if (commit->title[0])
4368                         break;
4370                 /* Require titles to start with a non-space character at the
4371                  * offset used by git log. */
4372                 if (strncmp(line, "    ", 4))
4373                         break;
4374                 line += 4;
4375                 /* Well, if the title starts with a whitespace character,
4376                  * try to be forgiving.  Otherwise we end up with no title. */
4377                 while (isspace(*line))
4378                         line++;
4379                 if (*line == '\0')
4380                         break;
4381                 /* FIXME: More graceful handling of titles; append "..." to
4382                  * shortened titles, etc. */
4384                 string_ncopy(commit->title, line, strlen(line));
4385         }
4387         return TRUE;
4390 static enum request
4391 main_request(struct view *view, enum request request, struct line *line)
4393         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4395         if (request == REQ_ENTER)
4396                 open_view(view, REQ_VIEW_DIFF, flags);
4397         else
4398                 return request;
4400         return REQ_NONE;
4403 static bool
4404 main_grep(struct view *view, struct line *line)
4406         struct commit *commit = line->data;
4407         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4408         char buf[DATE_COLS + 1];
4409         regmatch_t pmatch;
4411         for (state = S_TITLE; state < S_END; state++) {
4412                 char *text;
4414                 switch (state) {
4415                 case S_TITLE:   text = commit->title;   break;
4416                 case S_AUTHOR:  text = commit->author;  break;
4417                 case S_DATE:
4418                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4419                                 continue;
4420                         text = buf;
4421                         break;
4423                 default:
4424                         return FALSE;
4425                 }
4427                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4428                         return TRUE;
4429         }
4431         return FALSE;
4434 static void
4435 main_select(struct view *view, struct line *line)
4437         struct commit *commit = line->data;
4439         string_copy_rev(view->ref, commit->id);
4440         string_copy_rev(ref_commit, view->ref);
4443 static struct view_ops main_ops = {
4444         "commit",
4445         NULL,
4446         main_read,
4447         main_draw,
4448         main_request,
4449         main_grep,
4450         main_select,
4451 };
4454 /*
4455  * Unicode / UTF-8 handling
4456  *
4457  * NOTE: Much of the following code for dealing with unicode is derived from
4458  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4459  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4460  */
4462 /* I've (over)annotated a lot of code snippets because I am not entirely
4463  * confident that the approach taken by this small UTF-8 interface is correct.
4464  * --jonas */
4466 static inline int
4467 unicode_width(unsigned long c)
4469         if (c >= 0x1100 &&
4470            (c <= 0x115f                         /* Hangul Jamo */
4471             || c == 0x2329
4472             || c == 0x232a
4473             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4474                                                 /* CJK ... Yi */
4475             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4476             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4477             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4478             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4479             || (c >= 0xffe0  && c <= 0xffe6)
4480             || (c >= 0x20000 && c <= 0x2fffd)
4481             || (c >= 0x30000 && c <= 0x3fffd)))
4482                 return 2;
4484         if (c == '\t')
4485                 return opt_tab_size;
4487         return 1;
4490 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4491  * Illegal bytes are set one. */
4492 static const unsigned char utf8_bytes[256] = {
4493         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,
4494         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,
4495         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,
4496         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,
4497         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,
4498         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,
4499         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,
4500         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,
4501 };
4503 /* Decode UTF-8 multi-byte representation into a unicode character. */
4504 static inline unsigned long
4505 utf8_to_unicode(const char *string, size_t length)
4507         unsigned long unicode;
4509         switch (length) {
4510         case 1:
4511                 unicode  =   string[0];
4512                 break;
4513         case 2:
4514                 unicode  =  (string[0] & 0x1f) << 6;
4515                 unicode +=  (string[1] & 0x3f);
4516                 break;
4517         case 3:
4518                 unicode  =  (string[0] & 0x0f) << 12;
4519                 unicode += ((string[1] & 0x3f) << 6);
4520                 unicode +=  (string[2] & 0x3f);
4521                 break;
4522         case 4:
4523                 unicode  =  (string[0] & 0x0f) << 18;
4524                 unicode += ((string[1] & 0x3f) << 12);
4525                 unicode += ((string[2] & 0x3f) << 6);
4526                 unicode +=  (string[3] & 0x3f);
4527                 break;
4528         case 5:
4529                 unicode  =  (string[0] & 0x0f) << 24;
4530                 unicode += ((string[1] & 0x3f) << 18);
4531                 unicode += ((string[2] & 0x3f) << 12);
4532                 unicode += ((string[3] & 0x3f) << 6);
4533                 unicode +=  (string[4] & 0x3f);
4534                 break;
4535         case 6:
4536                 unicode  =  (string[0] & 0x01) << 30;
4537                 unicode += ((string[1] & 0x3f) << 24);
4538                 unicode += ((string[2] & 0x3f) << 18);
4539                 unicode += ((string[3] & 0x3f) << 12);
4540                 unicode += ((string[4] & 0x3f) << 6);
4541                 unicode +=  (string[5] & 0x3f);
4542                 break;
4543         default:
4544                 die("Invalid unicode length");
4545         }
4547         /* Invalid characters could return the special 0xfffd value but NUL
4548          * should be just as good. */
4549         return unicode > 0xffff ? 0 : unicode;
4552 /* Calculates how much of string can be shown within the given maximum width
4553  * and sets trimmed parameter to non-zero value if all of string could not be
4554  * shown. If the reserve flag is TRUE, it will reserve at least one
4555  * trailing character, which can be useful when drawing a delimiter.
4556  *
4557  * Returns the number of bytes to output from string to satisfy max_width. */
4558 static size_t
4559 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4561         const char *start = string;
4562         const char *end = strchr(string, '\0');
4563         unsigned char last_bytes = 0;
4564         size_t width = 0;
4566         *trimmed = 0;
4568         while (string < end) {
4569                 int c = *(unsigned char *) string;
4570                 unsigned char bytes = utf8_bytes[c];
4571                 size_t ucwidth;
4572                 unsigned long unicode;
4574                 if (string + bytes > end)
4575                         break;
4577                 /* Change representation to figure out whether
4578                  * it is a single- or double-width character. */
4580                 unicode = utf8_to_unicode(string, bytes);
4581                 /* FIXME: Graceful handling of invalid unicode character. */
4582                 if (!unicode)
4583                         break;
4585                 ucwidth = unicode_width(unicode);
4586                 width  += ucwidth;
4587                 if (width > max_width) {
4588                         *trimmed = 1;
4589                         if (reserve && width - ucwidth == max_width) {
4590                                 string -= last_bytes;
4591                         }
4592                         break;
4593                 }
4595                 string  += bytes;
4596                 last_bytes = bytes;
4597         }
4599         return string - start;
4603 /*
4604  * Status management
4605  */
4607 /* Whether or not the curses interface has been initialized. */
4608 static bool cursed = FALSE;
4610 /* The status window is used for polling keystrokes. */
4611 static WINDOW *status_win;
4613 static bool status_empty = TRUE;
4615 /* Update status and title window. */
4616 static void
4617 report(const char *msg, ...)
4619         struct view *view = display[current_view];
4621         if (input_mode)
4622                 return;
4624         if (!view) {
4625                 char buf[SIZEOF_STR];
4626                 va_list args;
4628                 va_start(args, msg);
4629                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4630                         buf[sizeof(buf) - 1] = 0;
4631                         buf[sizeof(buf) - 2] = '.';
4632                         buf[sizeof(buf) - 3] = '.';
4633                         buf[sizeof(buf) - 4] = '.';
4634                 }
4635                 va_end(args);
4636                 die("%s", buf);
4637         }
4639         if (!status_empty || *msg) {
4640                 va_list args;
4642                 va_start(args, msg);
4644                 wmove(status_win, 0, 0);
4645                 if (*msg) {
4646                         vwprintw(status_win, msg, args);
4647                         status_empty = FALSE;
4648                 } else {
4649                         status_empty = TRUE;
4650                 }
4651                 wclrtoeol(status_win);
4652                 wrefresh(status_win);
4654                 va_end(args);
4655         }
4657         update_view_title(view);
4658         update_display_cursor(view);
4661 /* Controls when nodelay should be in effect when polling user input. */
4662 static void
4663 set_nonblocking_input(bool loading)
4665         static unsigned int loading_views;
4667         if ((loading == FALSE && loading_views-- == 1) ||
4668             (loading == TRUE  && loading_views++ == 0))
4669                 nodelay(status_win, loading);
4672 static void
4673 init_display(void)
4675         int x, y;
4677         /* Initialize the curses library */
4678         if (isatty(STDIN_FILENO)) {
4679                 cursed = !!initscr();
4680         } else {
4681                 /* Leave stdin and stdout alone when acting as a pager. */
4682                 FILE *io = fopen("/dev/tty", "r+");
4684                 if (!io)
4685                         die("Failed to open /dev/tty");
4686                 cursed = !!newterm(NULL, io, io);
4687         }
4689         if (!cursed)
4690                 die("Failed to initialize curses");
4692         nonl();         /* Tell curses not to do NL->CR/NL on output */
4693         cbreak();       /* Take input chars one at a time, no wait for \n */
4694         noecho();       /* Don't echo input */
4695         leaveok(stdscr, TRUE);
4697         if (has_colors())
4698                 init_colors();
4700         getmaxyx(stdscr, y, x);
4701         status_win = newwin(1, 0, y - 1, 0);
4702         if (!status_win)
4703                 die("Failed to create status window");
4705         /* Enable keyboard mapping */
4706         keypad(status_win, TRUE);
4707         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4710 static char *
4711 read_prompt(const char *prompt)
4713         enum { READING, STOP, CANCEL } status = READING;
4714         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4715         int pos = 0;
4717         while (status == READING) {
4718                 struct view *view;
4719                 int i, key;
4721                 input_mode = TRUE;
4723                 foreach_view (view, i)
4724                         update_view(view);
4726                 input_mode = FALSE;
4728                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4729                 wclrtoeol(status_win);
4731                 /* Refresh, accept single keystroke of input */
4732                 key = wgetch(status_win);
4733                 switch (key) {
4734                 case KEY_RETURN:
4735                 case KEY_ENTER:
4736                 case '\n':
4737                         status = pos ? STOP : CANCEL;
4738                         break;
4740                 case KEY_BACKSPACE:
4741                         if (pos > 0)
4742                                 pos--;
4743                         else
4744                                 status = CANCEL;
4745                         break;
4747                 case KEY_ESC:
4748                         status = CANCEL;
4749                         break;
4751                 case ERR:
4752                         break;
4754                 default:
4755                         if (pos >= sizeof(buf)) {
4756                                 report("Input string too long");
4757                                 return NULL;
4758                         }
4760                         if (isprint(key))
4761                                 buf[pos++] = (char) key;
4762                 }
4763         }
4765         /* Clear the status window */
4766         status_empty = FALSE;
4767         report("");
4769         if (status == CANCEL)
4770                 return NULL;
4772         buf[pos++] = 0;
4774         return buf;
4777 /*
4778  * Repository references
4779  */
4781 static struct ref *refs;
4782 static size_t refs_size;
4784 /* Id <-> ref store */
4785 static struct ref ***id_refs;
4786 static size_t id_refs_size;
4788 static struct ref **
4789 get_refs(char *id)
4791         struct ref ***tmp_id_refs;
4792         struct ref **ref_list = NULL;
4793         size_t ref_list_size = 0;
4794         size_t i;
4796         for (i = 0; i < id_refs_size; i++)
4797                 if (!strcmp(id, id_refs[i][0]->id))
4798                         return id_refs[i];
4800         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4801         if (!tmp_id_refs)
4802                 return NULL;
4804         id_refs = tmp_id_refs;
4806         for (i = 0; i < refs_size; i++) {
4807                 struct ref **tmp;
4809                 if (strcmp(id, refs[i].id))
4810                         continue;
4812                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4813                 if (!tmp) {
4814                         if (ref_list)
4815                                 free(ref_list);
4816                         return NULL;
4817                 }
4819                 ref_list = tmp;
4820                 if (ref_list_size > 0)
4821                         ref_list[ref_list_size - 1]->next = 1;
4822                 ref_list[ref_list_size] = &refs[i];
4824                 /* XXX: The properties of the commit chains ensures that we can
4825                  * safely modify the shared ref. The repo references will
4826                  * always be similar for the same id. */
4827                 ref_list[ref_list_size]->next = 0;
4828                 ref_list_size++;
4829         }
4831         if (ref_list)
4832                 id_refs[id_refs_size++] = ref_list;
4834         return ref_list;
4837 static int
4838 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4840         struct ref *ref;
4841         bool tag = FALSE;
4842         bool remote = FALSE;
4844         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4845                 /* Commits referenced by tags has "^{}" appended. */
4846                 if (name[namelen - 1] != '}')
4847                         return OK;
4849                 while (namelen > 0 && name[namelen] != '^')
4850                         namelen--;
4852                 tag = TRUE;
4853                 namelen -= STRING_SIZE("refs/tags/");
4854                 name    += STRING_SIZE("refs/tags/");
4856         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4857                 remote = TRUE;
4858                 namelen -= STRING_SIZE("refs/remotes/");
4859                 name    += STRING_SIZE("refs/remotes/");
4861         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4862                 namelen -= STRING_SIZE("refs/heads/");
4863                 name    += STRING_SIZE("refs/heads/");
4865         } else if (!strcmp(name, "HEAD")) {
4866                 return OK;
4867         }
4869         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4870         if (!refs)
4871                 return ERR;
4873         ref = &refs[refs_size++];
4874         ref->name = malloc(namelen + 1);
4875         if (!ref->name)
4876                 return ERR;
4878         strncpy(ref->name, name, namelen);
4879         ref->name[namelen] = 0;
4880         ref->tag = tag;
4881         ref->remote = remote;
4882         string_copy_rev(ref->id, id);
4884         return OK;
4887 static int
4888 load_refs(void)
4890         const char *cmd_env = getenv("TIG_LS_REMOTE");
4891         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4893         return read_properties(popen(cmd, "r"), "\t", read_ref);
4896 static int
4897 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4899         if (!strcmp(name, "i18n.commitencoding"))
4900                 string_ncopy(opt_encoding, value, valuelen);
4902         if (!strcmp(name, "core.editor"))
4903                 string_ncopy(opt_editor, value, valuelen);
4905         return OK;
4908 static int
4909 load_repo_config(void)
4911         return read_properties(popen(GIT_CONFIG " --list", "r"),
4912                                "=", read_repo_config_option);
4915 static int
4916 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4918         if (!opt_git_dir[0]) {
4919                 string_ncopy(opt_git_dir, name, namelen);
4921         } else if (opt_is_inside_work_tree == -1) {
4922                 /* This can be 3 different values depending on the
4923                  * version of git being used. If git-rev-parse does not
4924                  * understand --is-inside-work-tree it will simply echo
4925                  * the option else either "true" or "false" is printed.
4926                  * Default to true for the unknown case. */
4927                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4929         } else {
4930                 string_ncopy(opt_cdup, name, namelen);
4931         }
4933         return OK;
4936 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4937  * must be the last one! */
4938 static int
4939 load_repo_info(void)
4941         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4942                                "=", read_repo_info);
4945 static int
4946 read_properties(FILE *pipe, const char *separators,
4947                 int (*read_property)(char *, size_t, char *, size_t))
4949         char buffer[BUFSIZ];
4950         char *name;
4951         int state = OK;
4953         if (!pipe)
4954                 return ERR;
4956         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4957                 char *value;
4958                 size_t namelen;
4959                 size_t valuelen;
4961                 name = chomp_string(name);
4962                 namelen = strcspn(name, separators);
4964                 if (name[namelen]) {
4965                         name[namelen] = 0;
4966                         value = chomp_string(name + namelen + 1);
4967                         valuelen = strlen(value);
4969                 } else {
4970                         value = "";
4971                         valuelen = 0;
4972                 }
4974                 state = read_property(name, namelen, value, valuelen);
4975         }
4977         if (state != ERR && ferror(pipe))
4978                 state = ERR;
4980         pclose(pipe);
4982         return state;
4986 /*
4987  * Main
4988  */
4990 static void __NORETURN
4991 quit(int sig)
4993         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4994         if (cursed)
4995                 endwin();
4996         exit(0);
4999 static void __NORETURN
5000 die(const char *err, ...)
5002         va_list args;
5004         endwin();
5006         va_start(args, err);
5007         fputs("tig: ", stderr);
5008         vfprintf(stderr, err, args);
5009         fputs("\n", stderr);
5010         va_end(args);
5012         exit(1);
5015 static void
5016 warn(const char *msg, ...)
5018         va_list args;
5020         va_start(args, msg);
5021         fputs("tig warning: ", stderr);
5022         vfprintf(stderr, msg, args);
5023         fputs("\n", stderr);
5024         va_end(args);
5027 int
5028 main(int argc, char *argv[])
5030         struct view *view;
5031         enum request request;
5032         size_t i;
5034         signal(SIGINT, quit);
5036         if (setlocale(LC_ALL, "")) {
5037                 char *codeset = nl_langinfo(CODESET);
5039                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5040         }
5042         if (load_repo_info() == ERR)
5043                 die("Failed to load repo info.");
5045         if (load_options() == ERR)
5046                 die("Failed to load user config.");
5048         /* Load the repo config file so options can be overwritten from
5049          * the command line. */
5050         if (load_repo_config() == ERR)
5051                 die("Failed to load repo config.");
5053         if (!parse_options(argc, argv))
5054                 return 0;
5056         /* Require a git repository unless when running in pager mode. */
5057         if (!opt_git_dir[0])
5058                 die("Not a git repository");
5060         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5061                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5062                 if (opt_iconv == ICONV_NONE)
5063                         die("Failed to initialize character set conversion");
5064         }
5066         if (load_refs() == ERR)
5067                 die("Failed to load refs.");
5069         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5070                 view->cmd_env = getenv(view->cmd_env);
5072         request = opt_request;
5074         init_display();
5076         while (view_driver(display[current_view], request)) {
5077                 int key;
5078                 int i;
5080                 foreach_view (view, i)
5081                         update_view(view);
5083                 /* Refresh, accept single keystroke of input */
5084                 key = wgetch(status_win);
5086                 /* wgetch() with nodelay() enabled returns ERR when there's no
5087                  * input. */
5088                 if (key == ERR) {
5089                         request = REQ_NONE;
5090                         continue;
5091                 }
5093                 request = get_keybinding(display[current_view]->keymap, key);
5095                 /* Some low-level request handling. This keeps access to
5096                  * status_win restricted. */
5097                 switch (request) {
5098                 case REQ_PROMPT:
5099                 {
5100                         char *cmd = read_prompt(":");
5102                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5103                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5104                                         opt_request = REQ_VIEW_DIFF;
5105                                 } else {
5106                                         opt_request = REQ_VIEW_PAGER;
5107                                 }
5108                                 break;
5109                         }
5111                         request = REQ_NONE;
5112                         break;
5113                 }
5114                 case REQ_SEARCH:
5115                 case REQ_SEARCH_BACK:
5116                 {
5117                         const char *prompt = request == REQ_SEARCH
5118                                            ? "/" : "?";
5119                         char *search = read_prompt(prompt);
5121                         if (search)
5122                                 string_ncopy(opt_search, search, strlen(search));
5123                         else
5124                                 request = REQ_NONE;
5125                         break;
5126                 }
5127                 case REQ_SCREEN_RESIZE:
5128                 {
5129                         int height, width;
5131                         getmaxyx(stdscr, height, width);
5133                         /* Resize the status view and let the view driver take
5134                          * care of resizing the displayed views. */
5135                         wresize(status_win, 1, width);
5136                         mvwin(status_win, height - 1, 0);
5137                         wrefresh(status_win);
5138                         break;
5139                 }
5140                 default:
5141                         break;
5142                 }
5143         }
5145         quit(0);
5147         return 0;