Code

Unify REQ_NONE and REQ_UNKNOWN by moving REQ_NONE to be the last request
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
60 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
62 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x)  (sizeof(x) - 1)
65 #define SIZEOF_STR      1024    /* Default string size. */
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT   'I'
72 #define REVGRAPH_MERGE  'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE   '|'
77 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT   (-1)
82 #define ICONV_NONE      ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST     /* nothing */
85 #endif
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
89 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS     20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
96 #define TABSIZE         8
98 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
104 #define TIG_LS_REMOTE \
105         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD     \
111         "git log --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114         "git log --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD    \
117         "git ls-tree %s %s"
119 #define TIG_BLOB_CMD    \
120         "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD    ""
124 #define TIG_PAGER_CMD   ""
125 #define TIG_STATUS_CMD  ""
126 #define TIG_STAGE_CMD   ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int tag:1;     /* Is it a tag? */
138         unsigned int remote:1;  /* Is it a remote ref? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(char *id);
144 struct int_map {
145         const char *name;
146         int namelen;
147         int value;
148 };
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152                  int *value, const char *name, int namelen)
155         int i;
157         for (i = 0; i < map_size; i++)
158                 if (namelen == map[i].namelen &&
159                     !strncasecmp(name, map[i].name, namelen)) {
160                         *value = map[i].value;
161                         return OK;
162                 }
164         return ERR;
168 /*
169  * String helpers
170  */
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175         if (srclen > dstlen - 1)
176                 srclen = dstlen - 1;
178         strncpy(dst, src, srclen);
179         dst[srclen] = 0;
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188         string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
196 static char *
197 chomp_string(char *name)
199         int namelen;
201         while (isspace(*name))
202                 name++;
204         namelen = strlen(name) - 1;
205         while (namelen > 0 && isspace(name[namelen]))
206                 name[namelen--] = 0;
208         return name;
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
214         va_list args;
215         size_t pos = bufpos ? *bufpos : 0;
217         va_start(args, fmt);
218         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219         va_end(args);
221         if (bufpos)
222                 *bufpos = pos;
224         return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228         string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231         string_nformat(buf, sizeof(buf), from, fmt, args)
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
236         size_t i;
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240         /* Diff-Header == DIFF_HEADER */
241         for (i = 0; i < len; i++) {
242                 if (toupper(str1[i]) == toupper(str2[i]))
243                         continue;
245                 if (string_enum_sep(str1[i]) &&
246                     string_enum_sep(str2[i]))
247                         continue;
249                 return str1[i] - str2[i];
250         }
252         return 0;
255 /* Shell quoting
256  *
257  * NOTE: The following is a slightly modified copy of the git project's shell
258  * quoting routines found in the quote.c file.
259  *
260  * Help to copy the thing properly quoted for the shell safety.  any single
261  * quote is replaced with '\'', any exclamation point is replaced with '\!',
262  * and the whole thing is enclosed in a
263  *
264  * E.g.
265  *  original     sq_quote     result
266  *  name     ==> name      ==> 'name'
267  *  a b      ==> a b       ==> 'a b'
268  *  a'b      ==> a'\''b    ==> 'a'\''b'
269  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
270  */
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
275         char c;
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
279         BUFPUT('\'');
280         while ((c = *src++)) {
281                 if (c == '\'' || c == '!') {
282                         BUFPUT('\'');
283                         BUFPUT('\\');
284                         BUFPUT(c);
285                         BUFPUT('\'');
286                 } else {
287                         BUFPUT(c);
288                 }
289         }
290         BUFPUT('\'');
292         if (bufsize < SIZEOF_STR)
293                 buf[bufsize] = 0;
295         return bufsize;
299 /*
300  * User requests
301  */
303 #define REQ_INFO \
304         /* XXX: Keep the view request first and in sync with views[]. */ \
305         REQ_GROUP("View switching") \
306         REQ_(VIEW_MAIN,         "Show main view"), \
307         REQ_(VIEW_DIFF,         "Show diff view"), \
308         REQ_(VIEW_LOG,          "Show log view"), \
309         REQ_(VIEW_TREE,         "Show tree view"), \
310         REQ_(VIEW_BLOB,         "Show blob view"), \
311         REQ_(VIEW_HELP,         "Show help page"), \
312         REQ_(VIEW_PAGER,        "Show pager view"), \
313         REQ_(VIEW_STATUS,       "Show status view"), \
314         REQ_(VIEW_STAGE,        "Show stage view"), \
315         \
316         REQ_GROUP("View manipulation") \
317         REQ_(ENTER,             "Enter current line and scroll"), \
318         REQ_(NEXT,              "Move to next"), \
319         REQ_(PREVIOUS,          "Move to previous"), \
320         REQ_(VIEW_NEXT,         "Move focus to next view"), \
321         REQ_(REFRESH,           "Reload and refresh"), \
322         REQ_(VIEW_CLOSE,        "Close the current view"), \
323         REQ_(QUIT,              "Close all views and quit"), \
324         \
325         REQ_GROUP("Cursor navigation") \
326         REQ_(MOVE_UP,           "Move cursor one line up"), \
327         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
328         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
329         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
330         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
331         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
332         \
333         REQ_GROUP("Scrolling") \
334         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
335         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
336         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
337         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
338         \
339         REQ_GROUP("Searching") \
340         REQ_(SEARCH,            "Search the view"), \
341         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
342         REQ_(FIND_NEXT,         "Find next search match"), \
343         REQ_(FIND_PREV,         "Find previous search match"), \
344         \
345         REQ_GROUP("Misc") \
346         REQ_(PROMPT,            "Bring up the prompt"), \
347         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
348         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
349         REQ_(SHOW_VERSION,      "Show version information"), \
350         REQ_(STOP_LOADING,      "Stop all loading views"), \
351         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
352         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
353         REQ_(STATUS_UPDATE,     "Update file status"), \
354         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
355         REQ_(EDIT,              "Open in editor"), \
356         REQ_(CHERRY_PICK,       "Cherry-pick commit to current branch"), \
357         REQ_(NONE,              "Do nothing")
360 /* User action requests. */
361 enum request {
362 #define REQ_GROUP(help)
363 #define REQ_(req, help) REQ_##req
365         /* Offset all requests to avoid conflicts with ncurses getch values. */
366         REQ_OFFSET = KEY_MAX + 1,
367         REQ_INFO
369 #undef  REQ_GROUP
370 #undef  REQ_
371 };
373 struct request_info {
374         enum request request;
375         char *name;
376         int namelen;
377         char *help;
378 };
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
383         REQ_INFO
384 #undef  REQ_GROUP
385 #undef  REQ_
386 };
388 static enum request
389 get_request(const char *name)
391         int namelen = strlen(name);
392         int i;
394         for (i = 0; i < ARRAY_SIZE(req_info); i++)
395                 if (req_info[i].namelen == namelen &&
396                     !string_enum_compare(req_info[i].name, name, namelen))
397                         return req_info[i].request;
399         return REQ_NONE;
403 /*
404  * Options
405  */
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
409 "\n"
410 "Usage: tig [options]\n"
411 "   or: tig [options] [--] [git log options]\n"
412 "   or: tig [options] log  [git log options]\n"
413 "   or: tig [options] diff [git diff options]\n"
414 "   or: tig [options] show [git show options]\n"
415 "   or: tig [options] <    [git command output]\n"
416 "\n"
417 "Options:\n"
418 "  -l                          Start up in log view\n"
419 "  -d                          Start up in diff view\n"
420 "  -S                          Start up in status view\n"
421 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
422 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
423 "  --                          Mark end of tig options\n"
424 "  -v, --version               Show version and exit\n"
425 "  -h, --help                  Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number             = FALSE;
429 static bool opt_rev_graph               = FALSE;
430 static int opt_num_interval             = NUMBER_INTERVAL;
431 static int opt_tab_size                 = TABSIZE;
432 static enum request opt_request         = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR]         = "";
434 static char opt_path[SIZEOF_STR]        = "";
435 static FILE *opt_pipe                   = NULL;
436 static char opt_encoding[20]            = "UTF-8";
437 static bool opt_utf8                    = TRUE;
438 static char opt_codeset[20]             = "UTF-8";
439 static iconv_t opt_iconv                = ICONV_NONE;
440 static char opt_search[SIZEOF_STR]      = "";
441 static char opt_cdup[SIZEOF_STR]        = "";
442 static char opt_git_dir[SIZEOF_STR]     = "";
443 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR]      = "";
446 enum option_type {
447         OPT_NONE,
448         OPT_INT,
449 };
451 static bool
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
454         va_list args;
455         char *value = "";
456         int *number;
458         if (opt[0] != '-')
459                 return FALSE;
461         if (opt[1] == '-') {
462                 int namelen = strlen(name);
464                 opt += 2;
466                 if (strncmp(opt, name, namelen))
467                         return FALSE;
469                 if (opt[namelen] == '=')
470                         value = opt + namelen + 1;
472         } else {
473                 if (!short_name || opt[1] != short_name)
474                         return FALSE;
475                 value = opt + 2;
476         }
478         va_start(args, type);
479         if (type == OPT_INT) {
480                 number = va_arg(args, int *);
481                 if (isdigit(*value))
482                         *number = atoi(value);
483         }
484         va_end(args);
486         return TRUE;
489 /* Returns the index of log or diff command or -1 to exit. */
490 static bool
491 parse_options(int argc, char *argv[])
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                     !strcmp(opt, "show")) {
501                         opt_request = opt[0] == 'l'
502                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503                         break;
504                 }
506                 if (opt[0] && opt[0] != '-')
507                         break;
509                 if (!strcmp(opt, "-l")) {
510                         opt_request = REQ_VIEW_LOG;
511                         continue;
512                 }
514                 if (!strcmp(opt, "-d")) {
515                         opt_request = REQ_VIEW_DIFF;
516                         continue;
517                 }
519                 if (!strcmp(opt, "-S")) {
520                         opt_request = REQ_VIEW_STATUS;
521                         continue;
522                 }
524                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
525                         opt_line_number = TRUE;
526                         continue;
527                 }
529                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
530                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
531                         continue;
532                 }
534                 if (check_option(opt, 'v', "version", OPT_NONE)) {
535                         printf("tig version %s\n", TIG_VERSION);
536                         return FALSE;
537                 }
539                 if (check_option(opt, 'h', "help", OPT_NONE)) {
540                         printf(usage);
541                         return FALSE;
542                 }
544                 if (!strcmp(opt, "--")) {
545                         i++;
546                         break;
547                 }
549                 die("unknown option '%s'\n\n%s", opt, usage);
550         }
552         if (!isatty(STDIN_FILENO)) {
553                 opt_request = REQ_VIEW_PAGER;
554                 opt_pipe = stdin;
556         } else if (i < argc) {
557                 size_t buf_size;
559                 if (opt_request == REQ_VIEW_MAIN)
560                         /* XXX: This is vulnerable to the user overriding
561                          * options required for the main view parser. */
562                         string_copy(opt_cmd, "git log --pretty=raw");
563                 else
564                         string_copy(opt_cmd, "git");
565                 buf_size = strlen(opt_cmd);
567                 while (buf_size < sizeof(opt_cmd) && i < argc) {
568                         opt_cmd[buf_size++] = ' ';
569                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
570                 }
572                 if (buf_size >= sizeof(opt_cmd))
573                         die("command too long");
575                 opt_cmd[buf_size] = 0;
576         }
578         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
579                 opt_utf8 = FALSE;
581         return TRUE;
585 /*
586  * Line-oriented content detection.
587  */
589 #define LINE_INFO \
590 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
591 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
592 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
593 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
594 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
595 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
603 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
604 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
605 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
606 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
607 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
610 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
611 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
612 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
614 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
615 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
616 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
619 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
620 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
621 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
622 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
623 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
624 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
625 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
626 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
630 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
631 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
632 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
633 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
634 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
636 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
638 enum line_type {
639 #define LINE(type, line, fg, bg, attr) \
640         LINE_##type
641         LINE_INFO
642 #undef  LINE
643 };
645 struct line_info {
646         const char *name;       /* Option name. */
647         int namelen;            /* Size of option name. */
648         const char *line;       /* The start of line to match. */
649         int linelen;            /* Size of string to match. */
650         int fg, bg, attr;       /* Color and text attributes for the lines. */
651 };
653 static struct line_info line_info[] = {
654 #define LINE(type, line, fg, bg, attr) \
655         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
656         LINE_INFO
657 #undef  LINE
658 };
660 static enum line_type
661 get_line_type(char *line)
663         int linelen = strlen(line);
664         enum line_type type;
666         for (type = 0; type < ARRAY_SIZE(line_info); type++)
667                 /* Case insensitive search matches Signed-off-by lines better. */
668                 if (linelen >= line_info[type].linelen &&
669                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
670                         return type;
672         return LINE_DEFAULT;
675 static inline int
676 get_line_attr(enum line_type type)
678         assert(type < ARRAY_SIZE(line_info));
679         return COLOR_PAIR(type) | line_info[type].attr;
682 static struct line_info *
683 get_line_info(char *name, int namelen)
685         enum line_type type;
687         for (type = 0; type < ARRAY_SIZE(line_info); type++)
688                 if (namelen == line_info[type].namelen &&
689                     !string_enum_compare(line_info[type].name, name, namelen))
690                         return &line_info[type];
692         return NULL;
695 static void
696 init_colors(void)
698         int default_bg = COLOR_BLACK;
699         int default_fg = COLOR_WHITE;
700         enum line_type type;
702         start_color();
704         if (use_default_colors() != ERR) {
705                 default_bg = -1;
706                 default_fg = -1;
707         }
709         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
710                 struct line_info *info = &line_info[type];
711                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
712                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
714                 init_pair(type, fg, bg);
715         }
718 struct line {
719         enum line_type type;
721         /* State flags */
722         unsigned int selected:1;
724         void *data;             /* User data */
725 };
728 /*
729  * Keys
730  */
732 struct keybinding {
733         int alias;
734         enum request request;
735         struct keybinding *next;
736 };
738 static struct keybinding default_keybindings[] = {
739         /* View switching */
740         { 'm',          REQ_VIEW_MAIN },
741         { 'd',          REQ_VIEW_DIFF },
742         { 'l',          REQ_VIEW_LOG },
743         { 't',          REQ_VIEW_TREE },
744         { 'f',          REQ_VIEW_BLOB },
745         { 'p',          REQ_VIEW_PAGER },
746         { 'h',          REQ_VIEW_HELP },
747         { 'S',          REQ_VIEW_STATUS },
748         { 'c',          REQ_VIEW_STAGE },
750         /* View manipulation */
751         { 'q',          REQ_VIEW_CLOSE },
752         { KEY_TAB,      REQ_VIEW_NEXT },
753         { KEY_RETURN,   REQ_ENTER },
754         { KEY_UP,       REQ_PREVIOUS },
755         { KEY_DOWN,     REQ_NEXT },
756         { 'R',          REQ_REFRESH },
758         /* Cursor navigation */
759         { 'k',          REQ_MOVE_UP },
760         { 'j',          REQ_MOVE_DOWN },
761         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
762         { KEY_END,      REQ_MOVE_LAST_LINE },
763         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
764         { ' ',          REQ_MOVE_PAGE_DOWN },
765         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
766         { 'b',          REQ_MOVE_PAGE_UP },
767         { '-',          REQ_MOVE_PAGE_UP },
769         /* Scrolling */
770         { KEY_IC,       REQ_SCROLL_LINE_UP },
771         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
772         { 'w',          REQ_SCROLL_PAGE_UP },
773         { 's',          REQ_SCROLL_PAGE_DOWN },
775         /* Searching */
776         { '/',          REQ_SEARCH },
777         { '?',          REQ_SEARCH_BACK },
778         { 'n',          REQ_FIND_NEXT },
779         { 'N',          REQ_FIND_PREV },
781         /* Misc */
782         { 'Q',          REQ_QUIT },
783         { 'z',          REQ_STOP_LOADING },
784         { 'v',          REQ_SHOW_VERSION },
785         { 'r',          REQ_SCREEN_REDRAW },
786         { '.',          REQ_TOGGLE_LINENO },
787         { 'g',          REQ_TOGGLE_REV_GRAPH },
788         { ':',          REQ_PROMPT },
789         { 'u',          REQ_STATUS_UPDATE },
790         { 'M',          REQ_STATUS_MERGE },
791         { 'e',          REQ_EDIT },
792         { 'C',          REQ_CHERRY_PICK },
794         /* Using the ncurses SIGWINCH handler. */
795         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
796 };
798 #define KEYMAP_INFO \
799         KEYMAP_(GENERIC), \
800         KEYMAP_(MAIN), \
801         KEYMAP_(DIFF), \
802         KEYMAP_(LOG), \
803         KEYMAP_(TREE), \
804         KEYMAP_(BLOB), \
805         KEYMAP_(PAGER), \
806         KEYMAP_(HELP), \
807         KEYMAP_(STATUS), \
808         KEYMAP_(STAGE)
810 enum keymap {
811 #define KEYMAP_(name) KEYMAP_##name
812         KEYMAP_INFO
813 #undef  KEYMAP_
814 };
816 static struct int_map keymap_table[] = {
817 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
818         KEYMAP_INFO
819 #undef  KEYMAP_
820 };
822 #define set_keymap(map, name) \
823         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
825 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
827 static void
828 add_keybinding(enum keymap keymap, enum request request, int key)
830         struct keybinding *keybinding;
832         keybinding = calloc(1, sizeof(*keybinding));
833         if (!keybinding)
834                 die("Failed to allocate keybinding");
836         keybinding->alias = key;
837         keybinding->request = request;
838         keybinding->next = keybindings[keymap];
839         keybindings[keymap] = keybinding;
842 /* Looks for a key binding first in the given map, then in the generic map, and
843  * lastly in the default keybindings. */
844 static enum request
845 get_keybinding(enum keymap keymap, int key)
847         struct keybinding *kbd;
848         int i;
850         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
851                 if (kbd->alias == key)
852                         return kbd->request;
854         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
855                 if (kbd->alias == key)
856                         return kbd->request;
858         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
859                 if (default_keybindings[i].alias == key)
860                         return default_keybindings[i].request;
862         return (enum request) key;
866 struct key {
867         char *name;
868         int value;
869 };
871 static struct key key_table[] = {
872         { "Enter",      KEY_RETURN },
873         { "Space",      ' ' },
874         { "Backspace",  KEY_BACKSPACE },
875         { "Tab",        KEY_TAB },
876         { "Escape",     KEY_ESC },
877         { "Left",       KEY_LEFT },
878         { "Right",      KEY_RIGHT },
879         { "Up",         KEY_UP },
880         { "Down",       KEY_DOWN },
881         { "Insert",     KEY_IC },
882         { "Delete",     KEY_DC },
883         { "Hash",       '#' },
884         { "Home",       KEY_HOME },
885         { "End",        KEY_END },
886         { "PageUp",     KEY_PPAGE },
887         { "PageDown",   KEY_NPAGE },
888         { "F1",         KEY_F(1) },
889         { "F2",         KEY_F(2) },
890         { "F3",         KEY_F(3) },
891         { "F4",         KEY_F(4) },
892         { "F5",         KEY_F(5) },
893         { "F6",         KEY_F(6) },
894         { "F7",         KEY_F(7) },
895         { "F8",         KEY_F(8) },
896         { "F9",         KEY_F(9) },
897         { "F10",        KEY_F(10) },
898         { "F11",        KEY_F(11) },
899         { "F12",        KEY_F(12) },
900 };
902 static int
903 get_key_value(const char *name)
905         int i;
907         for (i = 0; i < ARRAY_SIZE(key_table); i++)
908                 if (!strcasecmp(key_table[i].name, name))
909                         return key_table[i].value;
911         if (strlen(name) == 1 && isprint(*name))
912                 return (int) *name;
914         return ERR;
917 static char *
918 get_key(enum request request)
920         static char buf[BUFSIZ];
921         static char key_char[] = "'X'";
922         size_t pos = 0;
923         char *sep = "";
924         int i;
926         buf[pos] = 0;
928         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
929                 struct keybinding *keybinding = &default_keybindings[i];
930                 char *seq = NULL;
931                 int key;
933                 if (keybinding->request != request)
934                         continue;
936                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
937                         if (key_table[key].value == keybinding->alias)
938                                 seq = key_table[key].name;
940                 if (seq == NULL &&
941                     keybinding->alias < 127 &&
942                     isprint(keybinding->alias)) {
943                         key_char[1] = (char) keybinding->alias;
944                         seq = key_char;
945                 }
947                 if (!seq)
948                         seq = "'?'";
950                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
951                         return "Too many keybindings!";
952                 sep = ", ";
953         }
955         return buf;
959 /*
960  * User config file handling.
961  */
963 static struct int_map color_map[] = {
964 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
965         COLOR_MAP(DEFAULT),
966         COLOR_MAP(BLACK),
967         COLOR_MAP(BLUE),
968         COLOR_MAP(CYAN),
969         COLOR_MAP(GREEN),
970         COLOR_MAP(MAGENTA),
971         COLOR_MAP(RED),
972         COLOR_MAP(WHITE),
973         COLOR_MAP(YELLOW),
974 };
976 #define set_color(color, name) \
977         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
979 static struct int_map attr_map[] = {
980 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
981         ATTR_MAP(NORMAL),
982         ATTR_MAP(BLINK),
983         ATTR_MAP(BOLD),
984         ATTR_MAP(DIM),
985         ATTR_MAP(REVERSE),
986         ATTR_MAP(STANDOUT),
987         ATTR_MAP(UNDERLINE),
988 };
990 #define set_attribute(attr, name) \
991         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
993 static int   config_lineno;
994 static bool  config_errors;
995 static char *config_msg;
997 /* Wants: object fgcolor bgcolor [attr] */
998 static int
999 option_color_command(int argc, char *argv[])
1001         struct line_info *info;
1003         if (argc != 3 && argc != 4) {
1004                 config_msg = "Wrong number of arguments given to color command";
1005                 return ERR;
1006         }
1008         info = get_line_info(argv[0], strlen(argv[0]));
1009         if (!info) {
1010                 config_msg = "Unknown color name";
1011                 return ERR;
1012         }
1014         if (set_color(&info->fg, argv[1]) == ERR ||
1015             set_color(&info->bg, argv[2]) == ERR) {
1016                 config_msg = "Unknown color";
1017                 return ERR;
1018         }
1020         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1021                 config_msg = "Unknown attribute";
1022                 return ERR;
1023         }
1025         return OK;
1028 /* Wants: name = value */
1029 static int
1030 option_set_command(int argc, char *argv[])
1032         if (argc != 3) {
1033                 config_msg = "Wrong number of arguments given to set command";
1034                 return ERR;
1035         }
1037         if (strcmp(argv[1], "=")) {
1038                 config_msg = "No value assigned";
1039                 return ERR;
1040         }
1042         if (!strcmp(argv[0], "show-rev-graph")) {
1043                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1044                                  !strcmp(argv[2], "true") ||
1045                                  !strcmp(argv[2], "yes"));
1046                 return OK;
1047         }
1049         if (!strcmp(argv[0], "line-number-interval")) {
1050                 opt_num_interval = atoi(argv[2]);
1051                 return OK;
1052         }
1054         if (!strcmp(argv[0], "tab-size")) {
1055                 opt_tab_size = atoi(argv[2]);
1056                 return OK;
1057         }
1059         if (!strcmp(argv[0], "commit-encoding")) {
1060                 char *arg = argv[2];
1061                 int delimiter = *arg;
1062                 int i;
1064                 switch (delimiter) {
1065                 case '"':
1066                 case '\'':
1067                         for (arg++, i = 0; arg[i]; i++)
1068                                 if (arg[i] == delimiter) {
1069                                         arg[i] = 0;
1070                                         break;
1071                                 }
1072                 default:
1073                         string_ncopy(opt_encoding, arg, strlen(arg));
1074                         return OK;
1075                 }
1076         }
1078         config_msg = "Unknown variable name";
1079         return ERR;
1082 /* Wants: mode request key */
1083 static int
1084 option_bind_command(int argc, char *argv[])
1086         enum request request;
1087         int keymap;
1088         int key;
1090         if (argc != 3) {
1091                 config_msg = "Wrong number of arguments given to bind command";
1092                 return ERR;
1093         }
1095         if (set_keymap(&keymap, argv[0]) == ERR) {
1096                 config_msg = "Unknown key map";
1097                 return ERR;
1098         }
1100         key = get_key_value(argv[1]);
1101         if (key == ERR) {
1102                 config_msg = "Unknown key";
1103                 return ERR;
1104         }
1106         request = get_request(argv[2]);
1107         if (request == REQ_NONE) {
1108                 config_msg = "Unknown request name";
1109                 return ERR;
1110         }
1112         add_keybinding(keymap, request, key);
1114         return OK;
1117 static int
1118 set_option(char *opt, char *value)
1120         char *argv[16];
1121         int valuelen;
1122         int argc = 0;
1124         /* Tokenize */
1125         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1126                 argv[argc++] = value;
1128                 value += valuelen;
1129                 if (!*value)
1130                         break;
1132                 *value++ = 0;
1133                 while (isspace(*value))
1134                         value++;
1135         }
1137         if (!strcmp(opt, "color"))
1138                 return option_color_command(argc, argv);
1140         if (!strcmp(opt, "set"))
1141                 return option_set_command(argc, argv);
1143         if (!strcmp(opt, "bind"))
1144                 return option_bind_command(argc, argv);
1146         config_msg = "Unknown option command";
1147         return ERR;
1150 static int
1151 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1153         int status = OK;
1155         config_lineno++;
1156         config_msg = "Internal error";
1158         /* Check for comment markers, since read_properties() will
1159          * only ensure opt and value are split at first " \t". */
1160         optlen = strcspn(opt, "#");
1161         if (optlen == 0)
1162                 return OK;
1164         if (opt[optlen] != 0) {
1165                 config_msg = "No option value";
1166                 status = ERR;
1168         }  else {
1169                 /* Look for comment endings in the value. */
1170                 size_t len = strcspn(value, "#");
1172                 if (len < valuelen) {
1173                         valuelen = len;
1174                         value[valuelen] = 0;
1175                 }
1177                 status = set_option(opt, value);
1178         }
1180         if (status == ERR) {
1181                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1182                         config_lineno, (int) optlen, opt, config_msg);
1183                 config_errors = TRUE;
1184         }
1186         /* Always keep going if errors are encountered. */
1187         return OK;
1190 static int
1191 load_options(void)
1193         char *home = getenv("HOME");
1194         char buf[SIZEOF_STR];
1195         FILE *file;
1197         config_lineno = 0;
1198         config_errors = FALSE;
1200         if (!home || !string_format(buf, "%s/.tigrc", home))
1201                 return ERR;
1203         /* It's ok that the file doesn't exist. */
1204         file = fopen(buf, "r");
1205         if (!file)
1206                 return OK;
1208         if (read_properties(file, " \t", read_option) == ERR ||
1209             config_errors == TRUE)
1210                 fprintf(stderr, "Errors while loading %s.\n", buf);
1212         return OK;
1216 /*
1217  * The viewer
1218  */
1220 struct view;
1221 struct view_ops;
1223 /* The display array of active views and the index of the current view. */
1224 static struct view *display[2];
1225 static unsigned int current_view;
1227 /* Reading from the prompt? */
1228 static bool input_mode = FALSE;
1230 #define foreach_displayed_view(view, i) \
1231         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1233 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1235 /* Current head and commit ID */
1236 static char ref_blob[SIZEOF_REF]        = "";
1237 static char ref_commit[SIZEOF_REF]      = "HEAD";
1238 static char ref_head[SIZEOF_REF]        = "HEAD";
1240 struct view {
1241         const char *name;       /* View name */
1242         const char *cmd_fmt;    /* Default command line format */
1243         const char *cmd_env;    /* Command line set via environment */
1244         const char *id;         /* Points to either of ref_{head,commit,blob} */
1246         struct view_ops *ops;   /* View operations */
1248         enum keymap keymap;     /* What keymap does this view have */
1250         char cmd[SIZEOF_STR];   /* Command buffer */
1251         char ref[SIZEOF_REF];   /* Hovered commit reference */
1252         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1254         int height, width;      /* The width and height of the main window */
1255         WINDOW *win;            /* The main window */
1256         WINDOW *title;          /* The title window living below the main window */
1258         /* Navigation */
1259         unsigned long offset;   /* Offset of the window top */
1260         unsigned long lineno;   /* Current line number */
1262         /* Searching */
1263         char grep[SIZEOF_STR];  /* Search string */
1264         regex_t *regex;         /* Pre-compiled regex */
1266         /* If non-NULL, points to the view that opened this view. If this view
1267          * is closed tig will switch back to the parent view. */
1268         struct view *parent;
1270         /* Buffering */
1271         unsigned long lines;    /* Total number of lines */
1272         struct line *line;      /* Line index */
1273         unsigned long line_size;/* Total number of allocated lines */
1274         unsigned int digits;    /* Number of digits in the lines member. */
1276         /* Loading */
1277         FILE *pipe;
1278         time_t start_time;
1279 };
1281 struct view_ops {
1282         /* What type of content being displayed. Used in the title bar. */
1283         const char *type;
1284         /* Open and reads in all view content. */
1285         bool (*open)(struct view *view);
1286         /* Read one line; updates view->line. */
1287         bool (*read)(struct view *view, char *data);
1288         /* Draw one line; @lineno must be < view->height. */
1289         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1290         /* Depending on view handle a special requests. */
1291         enum request (*request)(struct view *view, enum request request, struct line *line);
1292         /* Search for regex in a line. */
1293         bool (*grep)(struct view *view, struct line *line);
1294         /* Select line */
1295         void (*select)(struct view *view, struct line *line);
1296 };
1298 static struct view_ops pager_ops;
1299 static struct view_ops main_ops;
1300 static struct view_ops tree_ops;
1301 static struct view_ops blob_ops;
1302 static struct view_ops help_ops;
1303 static struct view_ops status_ops;
1304 static struct view_ops stage_ops;
1306 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1307         { name, cmd, #env, ref, ops, map}
1309 #define VIEW_(id, name, ops, ref) \
1310         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1313 static struct view views[] = {
1314         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1315         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1316         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1317         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1318         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1319         VIEW_(HELP,   "help",   &help_ops,   ""),
1320         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1321         VIEW_(STATUS, "status", &status_ops, ""),
1322         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1323 };
1325 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1327 #define foreach_view(view, i) \
1328         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1330 #define view_is_displayed(view) \
1331         (view == display[0] || view == display[1])
1333 static bool
1334 draw_view_line(struct view *view, unsigned int lineno)
1336         struct line *line;
1337         bool selected = (view->offset + lineno == view->lineno);
1338         bool draw_ok;
1340         assert(view_is_displayed(view));
1342         if (view->offset + lineno >= view->lines)
1343                 return FALSE;
1345         line = &view->line[view->offset + lineno];
1347         if (selected) {
1348                 line->selected = TRUE;
1349                 view->ops->select(view, line);
1350         } else if (line->selected) {
1351                 line->selected = FALSE;
1352                 wmove(view->win, lineno, 0);
1353                 wclrtoeol(view->win);
1354         }
1356         scrollok(view->win, FALSE);
1357         draw_ok = view->ops->draw(view, line, lineno, selected);
1358         scrollok(view->win, TRUE);
1360         return draw_ok;
1363 static void
1364 redraw_view_from(struct view *view, int lineno)
1366         assert(0 <= lineno && lineno < view->height);
1368         for (; lineno < view->height; lineno++) {
1369                 if (!draw_view_line(view, lineno))
1370                         break;
1371         }
1373         redrawwin(view->win);
1374         if (input_mode)
1375                 wnoutrefresh(view->win);
1376         else
1377                 wrefresh(view->win);
1380 static void
1381 redraw_view(struct view *view)
1383         wclear(view->win);
1384         redraw_view_from(view, 0);
1388 static void
1389 update_view_title(struct view *view)
1391         char buf[SIZEOF_STR];
1392         char state[SIZEOF_STR];
1393         size_t bufpos = 0, statelen = 0;
1395         assert(view_is_displayed(view));
1397         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1398                 unsigned int view_lines = view->offset + view->height;
1399                 unsigned int lines = view->lines
1400                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1401                                    : 0;
1403                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1404                                    view->ops->type,
1405                                    view->lineno + 1,
1406                                    view->lines,
1407                                    lines);
1409                 if (view->pipe) {
1410                         time_t secs = time(NULL) - view->start_time;
1412                         /* Three git seconds are a long time ... */
1413                         if (secs > 2)
1414                                 string_format_from(state, &statelen, " %lds", secs);
1415                 }
1416         }
1418         string_format_from(buf, &bufpos, "[%s]", view->name);
1419         if (*view->ref && bufpos < view->width) {
1420                 size_t refsize = strlen(view->ref);
1421                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1423                 if (minsize < view->width)
1424                         refsize = view->width - minsize + 7;
1425                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1426         }
1428         if (statelen && bufpos < view->width) {
1429                 string_format_from(buf, &bufpos, " %s", state);
1430         }
1432         if (view == display[current_view])
1433                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1434         else
1435                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1437         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1438         wclrtoeol(view->title);
1439         wmove(view->title, 0, view->width - 1);
1441         if (input_mode)
1442                 wnoutrefresh(view->title);
1443         else
1444                 wrefresh(view->title);
1447 static void
1448 resize_display(void)
1450         int offset, i;
1451         struct view *base = display[0];
1452         struct view *view = display[1] ? display[1] : display[0];
1454         /* Setup window dimensions */
1456         getmaxyx(stdscr, base->height, base->width);
1458         /* Make room for the status window. */
1459         base->height -= 1;
1461         if (view != base) {
1462                 /* Horizontal split. */
1463                 view->width   = base->width;
1464                 view->height  = SCALE_SPLIT_VIEW(base->height);
1465                 base->height -= view->height;
1467                 /* Make room for the title bar. */
1468                 view->height -= 1;
1469         }
1471         /* Make room for the title bar. */
1472         base->height -= 1;
1474         offset = 0;
1476         foreach_displayed_view (view, i) {
1477                 if (!view->win) {
1478                         view->win = newwin(view->height, 0, offset, 0);
1479                         if (!view->win)
1480                                 die("Failed to create %s view", view->name);
1482                         scrollok(view->win, TRUE);
1484                         view->title = newwin(1, 0, offset + view->height, 0);
1485                         if (!view->title)
1486                                 die("Failed to create title window");
1488                 } else {
1489                         wresize(view->win, view->height, view->width);
1490                         mvwin(view->win,   offset, 0);
1491                         mvwin(view->title, offset + view->height, 0);
1492                 }
1494                 offset += view->height + 1;
1495         }
1498 static void
1499 redraw_display(void)
1501         struct view *view;
1502         int i;
1504         foreach_displayed_view (view, i) {
1505                 redraw_view(view);
1506                 update_view_title(view);
1507         }
1510 static void
1511 update_display_cursor(struct view *view)
1513         /* Move the cursor to the right-most column of the cursor line.
1514          *
1515          * XXX: This could turn out to be a bit expensive, but it ensures that
1516          * the cursor does not jump around. */
1517         if (view->lines) {
1518                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1519                 wrefresh(view->win);
1520         }
1523 /*
1524  * Navigation
1525  */
1527 /* Scrolling backend */
1528 static void
1529 do_scroll_view(struct view *view, int lines)
1531         bool redraw_current_line = FALSE;
1533         /* The rendering expects the new offset. */
1534         view->offset += lines;
1536         assert(0 <= view->offset && view->offset < view->lines);
1537         assert(lines);
1539         /* Move current line into the view. */
1540         if (view->lineno < view->offset) {
1541                 view->lineno = view->offset;
1542                 redraw_current_line = TRUE;
1543         } else if (view->lineno >= view->offset + view->height) {
1544                 view->lineno = view->offset + view->height - 1;
1545                 redraw_current_line = TRUE;
1546         }
1548         assert(view->offset <= view->lineno && view->lineno < view->lines);
1550         /* Redraw the whole screen if scrolling is pointless. */
1551         if (view->height < ABS(lines)) {
1552                 redraw_view(view);
1554         } else {
1555                 int line = lines > 0 ? view->height - lines : 0;
1556                 int end = line + ABS(lines);
1558                 wscrl(view->win, lines);
1560                 for (; line < end; line++) {
1561                         if (!draw_view_line(view, line))
1562                                 break;
1563                 }
1565                 if (redraw_current_line)
1566                         draw_view_line(view, view->lineno - view->offset);
1567         }
1569         redrawwin(view->win);
1570         wrefresh(view->win);
1571         report("");
1574 /* Scroll frontend */
1575 static void
1576 scroll_view(struct view *view, enum request request)
1578         int lines = 1;
1580         assert(view_is_displayed(view));
1582         switch (request) {
1583         case REQ_SCROLL_PAGE_DOWN:
1584                 lines = view->height;
1585         case REQ_SCROLL_LINE_DOWN:
1586                 if (view->offset + lines > view->lines)
1587                         lines = view->lines - view->offset;
1589                 if (lines == 0 || view->offset + view->height >= view->lines) {
1590                         report("Cannot scroll beyond the last line");
1591                         return;
1592                 }
1593                 break;
1595         case REQ_SCROLL_PAGE_UP:
1596                 lines = view->height;
1597         case REQ_SCROLL_LINE_UP:
1598                 if (lines > view->offset)
1599                         lines = view->offset;
1601                 if (lines == 0) {
1602                         report("Cannot scroll beyond the first line");
1603                         return;
1604                 }
1606                 lines = -lines;
1607                 break;
1609         default:
1610                 die("request %d not handled in switch", request);
1611         }
1613         do_scroll_view(view, lines);
1616 /* Cursor moving */
1617 static void
1618 move_view(struct view *view, enum request request)
1620         int scroll_steps = 0;
1621         int steps;
1623         switch (request) {
1624         case REQ_MOVE_FIRST_LINE:
1625                 steps = -view->lineno;
1626                 break;
1628         case REQ_MOVE_LAST_LINE:
1629                 steps = view->lines - view->lineno - 1;
1630                 break;
1632         case REQ_MOVE_PAGE_UP:
1633                 steps = view->height > view->lineno
1634                       ? -view->lineno : -view->height;
1635                 break;
1637         case REQ_MOVE_PAGE_DOWN:
1638                 steps = view->lineno + view->height >= view->lines
1639                       ? view->lines - view->lineno - 1 : view->height;
1640                 break;
1642         case REQ_MOVE_UP:
1643                 steps = -1;
1644                 break;
1646         case REQ_MOVE_DOWN:
1647                 steps = 1;
1648                 break;
1650         default:
1651                 die("request %d not handled in switch", request);
1652         }
1654         if (steps <= 0 && view->lineno == 0) {
1655                 report("Cannot move beyond the first line");
1656                 return;
1658         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1659                 report("Cannot move beyond the last line");
1660                 return;
1661         }
1663         /* Move the current line */
1664         view->lineno += steps;
1665         assert(0 <= view->lineno && view->lineno < view->lines);
1667         /* Check whether the view needs to be scrolled */
1668         if (view->lineno < view->offset ||
1669             view->lineno >= view->offset + view->height) {
1670                 scroll_steps = steps;
1671                 if (steps < 0 && -steps > view->offset) {
1672                         scroll_steps = -view->offset;
1674                 } else if (steps > 0) {
1675                         if (view->lineno == view->lines - 1 &&
1676                             view->lines > view->height) {
1677                                 scroll_steps = view->lines - view->offset - 1;
1678                                 if (scroll_steps >= view->height)
1679                                         scroll_steps -= view->height - 1;
1680                         }
1681                 }
1682         }
1684         if (!view_is_displayed(view)) {
1685                 view->offset += scroll_steps;
1686                 assert(0 <= view->offset && view->offset < view->lines);
1687                 view->ops->select(view, &view->line[view->lineno]);
1688                 return;
1689         }
1691         /* Repaint the old "current" line if we be scrolling */
1692         if (ABS(steps) < view->height)
1693                 draw_view_line(view, view->lineno - steps - view->offset);
1695         if (scroll_steps) {
1696                 do_scroll_view(view, scroll_steps);
1697                 return;
1698         }
1700         /* Draw the current line */
1701         draw_view_line(view, view->lineno - view->offset);
1703         redrawwin(view->win);
1704         wrefresh(view->win);
1705         report("");
1709 /*
1710  * Searching
1711  */
1713 static void search_view(struct view *view, enum request request);
1715 static bool
1716 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1718         assert(view_is_displayed(view));
1720         if (!view->ops->grep(view, line))
1721                 return FALSE;
1723         if (lineno - view->offset >= view->height) {
1724                 view->offset = lineno;
1725                 view->lineno = lineno;
1726                 redraw_view(view);
1728         } else {
1729                 unsigned long old_lineno = view->lineno - view->offset;
1731                 view->lineno = lineno;
1732                 draw_view_line(view, old_lineno);
1734                 draw_view_line(view, view->lineno - view->offset);
1735                 redrawwin(view->win);
1736                 wrefresh(view->win);
1737         }
1739         report("Line %ld matches '%s'", lineno + 1, view->grep);
1740         return TRUE;
1743 static void
1744 find_next(struct view *view, enum request request)
1746         unsigned long lineno = view->lineno;
1747         int direction;
1749         if (!*view->grep) {
1750                 if (!*opt_search)
1751                         report("No previous search");
1752                 else
1753                         search_view(view, request);
1754                 return;
1755         }
1757         switch (request) {
1758         case REQ_SEARCH:
1759         case REQ_FIND_NEXT:
1760                 direction = 1;
1761                 break;
1763         case REQ_SEARCH_BACK:
1764         case REQ_FIND_PREV:
1765                 direction = -1;
1766                 break;
1768         default:
1769                 return;
1770         }
1772         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1773                 lineno += direction;
1775         /* Note, lineno is unsigned long so will wrap around in which case it
1776          * will become bigger than view->lines. */
1777         for (; lineno < view->lines; lineno += direction) {
1778                 struct line *line = &view->line[lineno];
1780                 if (find_next_line(view, lineno, line))
1781                         return;
1782         }
1784         report("No match found for '%s'", view->grep);
1787 static void
1788 search_view(struct view *view, enum request request)
1790         int regex_err;
1792         if (view->regex) {
1793                 regfree(view->regex);
1794                 *view->grep = 0;
1795         } else {
1796                 view->regex = calloc(1, sizeof(*view->regex));
1797                 if (!view->regex)
1798                         return;
1799         }
1801         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1802         if (regex_err != 0) {
1803                 char buf[SIZEOF_STR] = "unknown error";
1805                 regerror(regex_err, view->regex, buf, sizeof(buf));
1806                 report("Search failed: %s", buf);
1807                 return;
1808         }
1810         string_copy(view->grep, opt_search);
1812         find_next(view, request);
1815 /*
1816  * Incremental updating
1817  */
1819 static void
1820 end_update(struct view *view)
1822         if (!view->pipe)
1823                 return;
1824         set_nonblocking_input(FALSE);
1825         if (view->pipe == stdin)
1826                 fclose(view->pipe);
1827         else
1828                 pclose(view->pipe);
1829         view->pipe = NULL;
1832 static bool
1833 begin_update(struct view *view)
1835         if (view->pipe)
1836                 end_update(view);
1838         if (opt_cmd[0]) {
1839                 string_copy(view->cmd, opt_cmd);
1840                 opt_cmd[0] = 0;
1841                 /* When running random commands, initially show the
1842                  * command in the title. However, it maybe later be
1843                  * overwritten if a commit line is selected. */
1844                 if (view == VIEW(REQ_VIEW_PAGER))
1845                         string_copy(view->ref, view->cmd);
1846                 else
1847                         view->ref[0] = 0;
1849         } else if (view == VIEW(REQ_VIEW_TREE)) {
1850                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1851                 char path[SIZEOF_STR];
1853                 if (strcmp(view->vid, view->id))
1854                         opt_path[0] = path[0] = 0;
1855                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1856                         return FALSE;
1858                 if (!string_format(view->cmd, format, view->id, path))
1859                         return FALSE;
1861         } else {
1862                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1863                 const char *id = view->id;
1865                 if (!string_format(view->cmd, format, id, id, id, id, id))
1866                         return FALSE;
1868                 /* Put the current ref_* value to the view title ref
1869                  * member. This is needed by the blob view. Most other
1870                  * views sets it automatically after loading because the
1871                  * first line is a commit line. */
1872                 string_copy_rev(view->ref, view->id);
1873         }
1875         /* Special case for the pager view. */
1876         if (opt_pipe) {
1877                 view->pipe = opt_pipe;
1878                 opt_pipe = NULL;
1879         } else {
1880                 view->pipe = popen(view->cmd, "r");
1881         }
1883         if (!view->pipe)
1884                 return FALSE;
1886         set_nonblocking_input(TRUE);
1888         view->offset = 0;
1889         view->lines  = 0;
1890         view->lineno = 0;
1891         string_copy_rev(view->vid, view->id);
1893         if (view->line) {
1894                 int i;
1896                 for (i = 0; i < view->lines; i++)
1897                         if (view->line[i].data)
1898                                 free(view->line[i].data);
1900                 free(view->line);
1901                 view->line = NULL;
1902         }
1904         view->start_time = time(NULL);
1906         return TRUE;
1909 static struct line *
1910 realloc_lines(struct view *view, size_t line_size)
1912         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1914         if (!tmp)
1915                 return NULL;
1917         view->line = tmp;
1918         view->line_size = line_size;
1919         return view->line;
1922 static bool
1923 update_view(struct view *view)
1925         char in_buffer[BUFSIZ];
1926         char out_buffer[BUFSIZ * 2];
1927         char *line;
1928         /* The number of lines to read. If too low it will cause too much
1929          * redrawing (and possible flickering), if too high responsiveness
1930          * will suffer. */
1931         unsigned long lines = view->height;
1932         int redraw_from = -1;
1934         if (!view->pipe)
1935                 return TRUE;
1937         /* Only redraw if lines are visible. */
1938         if (view->offset + view->height >= view->lines)
1939                 redraw_from = view->lines - view->offset;
1941         /* FIXME: This is probably not perfect for backgrounded views. */
1942         if (!realloc_lines(view, view->lines + lines))
1943                 goto alloc_error;
1945         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1946                 size_t linelen = strlen(line);
1948                 if (linelen)
1949                         line[linelen - 1] = 0;
1951                 if (opt_iconv != ICONV_NONE) {
1952                         ICONV_CONST char *inbuf = line;
1953                         size_t inlen = linelen;
1955                         char *outbuf = out_buffer;
1956                         size_t outlen = sizeof(out_buffer);
1958                         size_t ret;
1960                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1961                         if (ret != (size_t) -1) {
1962                                 line = out_buffer;
1963                                 linelen = strlen(out_buffer);
1964                         }
1965                 }
1967                 if (!view->ops->read(view, line))
1968                         goto alloc_error;
1970                 if (lines-- == 1)
1971                         break;
1972         }
1974         {
1975                 int digits;
1977                 lines = view->lines;
1978                 for (digits = 0; lines; digits++)
1979                         lines /= 10;
1981                 /* Keep the displayed view in sync with line number scaling. */
1982                 if (digits != view->digits) {
1983                         view->digits = digits;
1984                         redraw_from = 0;
1985                 }
1986         }
1988         if (!view_is_displayed(view))
1989                 goto check_pipe;
1991         if (view == VIEW(REQ_VIEW_TREE)) {
1992                 /* Clear the view and redraw everything since the tree sorting
1993                  * might have rearranged things. */
1994                 redraw_view(view);
1996         } else if (redraw_from >= 0) {
1997                 /* If this is an incremental update, redraw the previous line
1998                  * since for commits some members could have changed when
1999                  * loading the main view. */
2000                 if (redraw_from > 0)
2001                         redraw_from--;
2003                 /* Since revision graph visualization requires knowledge
2004                  * about the parent commit, it causes a further one-off
2005                  * needed to be redrawn for incremental updates. */
2006                 if (redraw_from > 0 && opt_rev_graph)
2007                         redraw_from--;
2009                 /* Incrementally draw avoids flickering. */
2010                 redraw_view_from(view, redraw_from);
2011         }
2013         /* Update the title _after_ the redraw so that if the redraw picks up a
2014          * commit reference in view->ref it'll be available here. */
2015         update_view_title(view);
2017 check_pipe:
2018         if (ferror(view->pipe)) {
2019                 report("Failed to read: %s", strerror(errno));
2020                 goto end;
2022         } else if (feof(view->pipe)) {
2023                 report("");
2024                 goto end;
2025         }
2027         return TRUE;
2029 alloc_error:
2030         report("Allocation failure");
2032 end:
2033         view->ops->read(view, NULL);
2034         end_update(view);
2035         return FALSE;
2038 static struct line *
2039 add_line_data(struct view *view, void *data, enum line_type type)
2041         struct line *line = &view->line[view->lines++];
2043         memset(line, 0, sizeof(*line));
2044         line->type = type;
2045         line->data = data;
2047         return line;
2050 static struct line *
2051 add_line_text(struct view *view, char *data, enum line_type type)
2053         if (data)
2054                 data = strdup(data);
2056         return data ? add_line_data(view, data, type) : NULL;
2060 /*
2061  * View opening
2062  */
2064 enum open_flags {
2065         OPEN_DEFAULT = 0,       /* Use default view switching. */
2066         OPEN_SPLIT = 1,         /* Split current view. */
2067         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2068         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2069 };
2071 static void
2072 open_view(struct view *prev, enum request request, enum open_flags flags)
2074         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2075         bool split = !!(flags & OPEN_SPLIT);
2076         bool reload = !!(flags & OPEN_RELOAD);
2077         struct view *view = VIEW(request);
2078         int nviews = displayed_views();
2079         struct view *base_view = display[0];
2081         if (view == prev && nviews == 1 && !reload) {
2082                 report("Already in %s view", view->name);
2083                 return;
2084         }
2086         if (view->ops->open) {
2087                 if (!view->ops->open(view)) {
2088                         report("Failed to load %s view", view->name);
2089                         return;
2090                 }
2092         } else if ((reload || strcmp(view->vid, view->id)) &&
2093                    !begin_update(view)) {
2094                 report("Failed to load %s view", view->name);
2095                 return;
2096         }
2098         if (split) {
2099                 display[1] = view;
2100                 if (!backgrounded)
2101                         current_view = 1;
2102         } else {
2103                 /* Maximize the current view. */
2104                 memset(display, 0, sizeof(display));
2105                 current_view = 0;
2106                 display[current_view] = view;
2107         }
2109         /* Resize the view when switching between split- and full-screen,
2110          * or when switching between two different full-screen views. */
2111         if (nviews != displayed_views() ||
2112             (nviews == 1 && base_view != display[0]))
2113                 resize_display();
2115         if (split && prev->lineno - prev->offset >= prev->height) {
2116                 /* Take the title line into account. */
2117                 int lines = prev->lineno - prev->offset - prev->height + 1;
2119                 /* Scroll the view that was split if the current line is
2120                  * outside the new limited view. */
2121                 do_scroll_view(prev, lines);
2122         }
2124         if (prev && view != prev) {
2125                 if (split && !backgrounded) {
2126                         /* "Blur" the previous view. */
2127                         update_view_title(prev);
2128                 }
2130                 view->parent = prev;
2131         }
2133         if (view->pipe && view->lines == 0) {
2134                 /* Clear the old view and let the incremental updating refill
2135                  * the screen. */
2136                 wclear(view->win);
2137                 report("");
2138         } else {
2139                 redraw_view(view);
2140                 report("");
2141         }
2143         /* If the view is backgrounded the above calls to report()
2144          * won't redraw the view title. */
2145         if (backgrounded)
2146                 update_view_title(view);
2149 static void
2150 open_external_viewer(const char *cmd)
2152         def_prog_mode();           /* save current tty modes */
2153         endwin();                  /* restore original tty modes */
2154         system(cmd);
2155         fprintf(stderr, "Press Enter to continue");
2156         getc(stdin);
2157         reset_prog_mode();
2158         redraw_display();
2161 static void
2162 open_mergetool(const char *file)
2164         char cmd[SIZEOF_STR];
2165         char file_sq[SIZEOF_STR];
2167         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2168             string_format(cmd, "git mergetool %s", file_sq)) {
2169                 open_external_viewer(cmd);
2170         }
2173 static void
2174 open_editor(bool from_root, const char *file)
2176         char cmd[SIZEOF_STR];
2177         char file_sq[SIZEOF_STR];
2178         char *editor;
2179         char *prefix = from_root ? opt_cdup : "";
2181         editor = getenv("GIT_EDITOR");
2182         if (!editor && *opt_editor)
2183                 editor = opt_editor;
2184         if (!editor)
2185                 editor = getenv("VISUAL");
2186         if (!editor)
2187                 editor = getenv("EDITOR");
2188         if (!editor)
2189                 editor = "vi";
2191         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2192             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2193                 open_external_viewer(cmd);
2194         }
2197 /*
2198  * User request switch noodle
2199  */
2201 static int
2202 view_driver(struct view *view, enum request request)
2204         int i;
2206         if (request == REQ_NONE) {
2207                 doupdate();
2208                 return TRUE;
2209         }
2211         if (view && view->lines) {
2212                 request = view->ops->request(view, request, &view->line[view->lineno]);
2213                 if (request == REQ_NONE)
2214                         return TRUE;
2215         }
2217         switch (request) {
2218         case REQ_MOVE_UP:
2219         case REQ_MOVE_DOWN:
2220         case REQ_MOVE_PAGE_UP:
2221         case REQ_MOVE_PAGE_DOWN:
2222         case REQ_MOVE_FIRST_LINE:
2223         case REQ_MOVE_LAST_LINE:
2224                 move_view(view, request);
2225                 break;
2227         case REQ_SCROLL_LINE_DOWN:
2228         case REQ_SCROLL_LINE_UP:
2229         case REQ_SCROLL_PAGE_DOWN:
2230         case REQ_SCROLL_PAGE_UP:
2231                 scroll_view(view, request);
2232                 break;
2234         case REQ_VIEW_BLOB:
2235                 if (!ref_blob[0]) {
2236                         report("No file chosen, press %s to open tree view",
2237                                get_key(REQ_VIEW_TREE));
2238                         break;
2239                 }
2240                 open_view(view, request, OPEN_DEFAULT);
2241                 break;
2243         case REQ_VIEW_PAGER:
2244                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2245                         report("No pager content, press %s to run command from prompt",
2246                                get_key(REQ_PROMPT));
2247                         break;
2248                 }
2249                 open_view(view, request, OPEN_DEFAULT);
2250                 break;
2252         case REQ_VIEW_STAGE:
2253                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2254                         report("No stage content, press %s to open the status view and choose file",
2255                                get_key(REQ_VIEW_STATUS));
2256                         break;
2257                 }
2258                 open_view(view, request, OPEN_DEFAULT);
2259                 break;
2261         case REQ_VIEW_STATUS:
2262                 if (opt_is_inside_work_tree == FALSE) {
2263                         report("The status view requires a working tree");
2264                         break;
2265                 }
2266                 open_view(view, request, OPEN_DEFAULT);
2267                 break;
2269         case REQ_VIEW_MAIN:
2270         case REQ_VIEW_DIFF:
2271         case REQ_VIEW_LOG:
2272         case REQ_VIEW_TREE:
2273         case REQ_VIEW_HELP:
2274                 open_view(view, request, OPEN_DEFAULT);
2275                 break;
2277         case REQ_NEXT:
2278         case REQ_PREVIOUS:
2279                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2281                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2282                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2283                    (view == VIEW(REQ_VIEW_STAGE) &&
2284                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2285                    (view == VIEW(REQ_VIEW_BLOB) &&
2286                      view->parent == VIEW(REQ_VIEW_TREE))) {
2287                         int line;
2289                         view = view->parent;
2290                         line = view->lineno;
2291                         move_view(view, request);
2292                         if (view_is_displayed(view))
2293                                 update_view_title(view);
2294                         if (line != view->lineno)
2295                                 view->ops->request(view, REQ_ENTER,
2296                                                    &view->line[view->lineno]);
2298                 } else {
2299                         move_view(view, request);
2300                 }
2301                 break;
2303         case REQ_VIEW_NEXT:
2304         {
2305                 int nviews = displayed_views();
2306                 int next_view = (current_view + 1) % nviews;
2308                 if (next_view == current_view) {
2309                         report("Only one view is displayed");
2310                         break;
2311                 }
2313                 current_view = next_view;
2314                 /* Blur out the title of the previous view. */
2315                 update_view_title(view);
2316                 report("");
2317                 break;
2318         }
2319         case REQ_REFRESH:
2320                 report("Refreshing is not yet supported for the %s view", view->name);
2321                 break;
2323         case REQ_TOGGLE_LINENO:
2324                 opt_line_number = !opt_line_number;
2325                 redraw_display();
2326                 break;
2328         case REQ_TOGGLE_REV_GRAPH:
2329                 opt_rev_graph = !opt_rev_graph;
2330                 redraw_display();
2331                 break;
2333         case REQ_PROMPT:
2334                 /* Always reload^Wrerun commands from the prompt. */
2335                 open_view(view, opt_request, OPEN_RELOAD);
2336                 break;
2338         case REQ_SEARCH:
2339         case REQ_SEARCH_BACK:
2340                 search_view(view, request);
2341                 break;
2343         case REQ_FIND_NEXT:
2344         case REQ_FIND_PREV:
2345                 find_next(view, request);
2346                 break;
2348         case REQ_STOP_LOADING:
2349                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2350                         view = &views[i];
2351                         if (view->pipe)
2352                                 report("Stopped loading the %s view", view->name),
2353                         end_update(view);
2354                 }
2355                 break;
2357         case REQ_SHOW_VERSION:
2358                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2359                 return TRUE;
2361         case REQ_SCREEN_RESIZE:
2362                 resize_display();
2363                 /* Fall-through */
2364         case REQ_SCREEN_REDRAW:
2365                 redraw_display();
2366                 break;
2368         case REQ_EDIT:
2369                 report("Nothing to edit");
2370                 break;
2372         case REQ_CHERRY_PICK:
2373                 report("Nothing to cherry-pick");
2374                 break;
2376         case REQ_ENTER:
2377                 report("Nothing to enter");
2378                 break;
2381         case REQ_VIEW_CLOSE:
2382                 /* XXX: Mark closed views by letting view->parent point to the
2383                  * view itself. Parents to closed view should never be
2384                  * followed. */
2385                 if (view->parent &&
2386                     view->parent->parent != view->parent) {
2387                         memset(display, 0, sizeof(display));
2388                         current_view = 0;
2389                         display[current_view] = view->parent;
2390                         view->parent = view;
2391                         resize_display();
2392                         redraw_display();
2393                         break;
2394                 }
2395                 /* Fall-through */
2396         case REQ_QUIT:
2397                 return FALSE;
2399         default:
2400                 /* An unknown key will show most commonly used commands. */
2401                 report("Unknown key, press 'h' for help");
2402                 return TRUE;
2403         }
2405         return TRUE;
2409 /*
2410  * Pager backend
2411  */
2413 static bool
2414 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2416         char *text = line->data;
2417         enum line_type type = line->type;
2418         int textlen = strlen(text);
2419         int attr;
2421         wmove(view->win, lineno, 0);
2423         if (selected) {
2424                 type = LINE_CURSOR;
2425                 wchgat(view->win, -1, 0, type, NULL);
2426         }
2428         attr = get_line_attr(type);
2429         wattrset(view->win, attr);
2431         if (opt_line_number || opt_tab_size < TABSIZE) {
2432                 static char spaces[] = "                    ";
2433                 int col_offset = 0, col = 0;
2435                 if (opt_line_number) {
2436                         unsigned long real_lineno = view->offset + lineno + 1;
2438                         if (real_lineno == 1 ||
2439                             (real_lineno % opt_num_interval) == 0) {
2440                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2442                         } else {
2443                                 waddnstr(view->win, spaces,
2444                                          MIN(view->digits, STRING_SIZE(spaces)));
2445                         }
2446                         waddstr(view->win, ": ");
2447                         col_offset = view->digits + 2;
2448                 }
2450                 while (text && col_offset + col < view->width) {
2451                         int cols_max = view->width - col_offset - col;
2452                         char *pos = text;
2453                         int cols;
2455                         if (*text == '\t') {
2456                                 text++;
2457                                 assert(sizeof(spaces) > TABSIZE);
2458                                 pos = spaces;
2459                                 cols = opt_tab_size - (col % opt_tab_size);
2461                         } else {
2462                                 text = strchr(text, '\t');
2463                                 cols = line ? text - pos : strlen(pos);
2464                         }
2466                         waddnstr(view->win, pos, MIN(cols, cols_max));
2467                         col += cols;
2468                 }
2470         } else {
2471                 int col = 0, pos = 0;
2473                 for (; pos < textlen && col < view->width; pos++, col++)
2474                         if (text[pos] == '\t')
2475                                 col += TABSIZE - (col % TABSIZE) - 1;
2477                 waddnstr(view->win, text, pos);
2478         }
2480         return TRUE;
2483 static bool
2484 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2486         char refbuf[SIZEOF_STR];
2487         char *ref = NULL;
2488         FILE *pipe;
2490         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2491                 return TRUE;
2493         pipe = popen(refbuf, "r");
2494         if (!pipe)
2495                 return TRUE;
2497         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2498                 ref = chomp_string(ref);
2499         pclose(pipe);
2501         if (!ref || !*ref)
2502                 return TRUE;
2504         /* This is the only fatal call, since it can "corrupt" the buffer. */
2505         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2506                 return FALSE;
2508         return TRUE;
2511 static void
2512 add_pager_refs(struct view *view, struct line *line)
2514         char buf[SIZEOF_STR];
2515         char *commit_id = line->data + STRING_SIZE("commit ");
2516         struct ref **refs;
2517         size_t bufpos = 0, refpos = 0;
2518         const char *sep = "Refs: ";
2519         bool is_tag = FALSE;
2521         assert(line->type == LINE_COMMIT);
2523         refs = get_refs(commit_id);
2524         if (!refs) {
2525                 if (view == VIEW(REQ_VIEW_DIFF))
2526                         goto try_add_describe_ref;
2527                 return;
2528         }
2530         do {
2531                 struct ref *ref = refs[refpos];
2532                 char *fmt = ref->tag    ? "%s[%s]" :
2533                             ref->remote ? "%s<%s>" : "%s%s";
2535                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2536                         return;
2537                 sep = ", ";
2538                 if (ref->tag)
2539                         is_tag = TRUE;
2540         } while (refs[refpos++]->next);
2542         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2543 try_add_describe_ref:
2544                 /* Add <tag>-g<commit_id> "fake" reference. */
2545                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2546                         return;
2547         }
2549         if (bufpos == 0)
2550                 return;
2552         if (!realloc_lines(view, view->line_size + 1))
2553                 return;
2555         add_line_text(view, buf, LINE_PP_REFS);
2558 static bool
2559 pager_read(struct view *view, char *data)
2561         struct line *line;
2563         if (!data)
2564                 return TRUE;
2566         line = add_line_text(view, data, get_line_type(data));
2567         if (!line)
2568                 return FALSE;
2570         if (line->type == LINE_COMMIT &&
2571             (view == VIEW(REQ_VIEW_DIFF) ||
2572              view == VIEW(REQ_VIEW_LOG)))
2573                 add_pager_refs(view, line);
2575         return TRUE;
2578 static enum request
2579 pager_request(struct view *view, enum request request, struct line *line)
2581         int split = 0;
2583         if (request != REQ_ENTER)
2584                 return request;
2586         if (line->type == LINE_COMMIT &&
2587            (view == VIEW(REQ_VIEW_LOG) ||
2588             view == VIEW(REQ_VIEW_PAGER))) {
2589                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2590                 split = 1;
2591         }
2593         /* Always scroll the view even if it was split. That way
2594          * you can use Enter to scroll through the log view and
2595          * split open each commit diff. */
2596         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2598         /* FIXME: A minor workaround. Scrolling the view will call report("")
2599          * but if we are scrolling a non-current view this won't properly
2600          * update the view title. */
2601         if (split)
2602                 update_view_title(view);
2604         return REQ_NONE;
2607 static bool
2608 pager_grep(struct view *view, struct line *line)
2610         regmatch_t pmatch;
2611         char *text = line->data;
2613         if (!*text)
2614                 return FALSE;
2616         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2617                 return FALSE;
2619         return TRUE;
2622 static void
2623 pager_select(struct view *view, struct line *line)
2625         if (line->type == LINE_COMMIT) {
2626                 char *text = line->data + STRING_SIZE("commit ");
2628                 if (view != VIEW(REQ_VIEW_PAGER))
2629                         string_copy_rev(view->ref, text);
2630                 string_copy_rev(ref_commit, text);
2631         }
2634 static struct view_ops pager_ops = {
2635         "line",
2636         NULL,
2637         pager_read,
2638         pager_draw,
2639         pager_request,
2640         pager_grep,
2641         pager_select,
2642 };
2645 /*
2646  * Help backend
2647  */
2649 static bool
2650 help_open(struct view *view)
2652         char buf[BUFSIZ];
2653         int lines = ARRAY_SIZE(req_info) + 2;
2654         int i;
2656         if (view->lines > 0)
2657                 return TRUE;
2659         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2660                 if (!req_info[i].request)
2661                         lines++;
2663         view->line = calloc(lines, sizeof(*view->line));
2664         if (!view->line)
2665                 return FALSE;
2667         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2669         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2670                 char *key;
2672                 if (req_info[i].request == REQ_NONE)
2673                         continue;
2675                 if (!req_info[i].request) {
2676                         add_line_text(view, "", LINE_DEFAULT);
2677                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2678                         continue;
2679                 }
2681                 key = get_key(req_info[i].request);
2682                 if (!*key)
2683                         key = "(no key defined)";
2685                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2686                         continue;
2688                 add_line_text(view, buf, LINE_DEFAULT);
2689         }
2691         return TRUE;
2694 static struct view_ops help_ops = {
2695         "line",
2696         help_open,
2697         NULL,
2698         pager_draw,
2699         pager_request,
2700         pager_grep,
2701         pager_select,
2702 };
2705 /*
2706  * Tree backend
2707  */
2709 struct tree_stack_entry {
2710         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2711         unsigned long lineno;           /* Line number to restore */
2712         char *name;                     /* Position of name in opt_path */
2713 };
2715 /* The top of the path stack. */
2716 static struct tree_stack_entry *tree_stack = NULL;
2717 unsigned long tree_lineno = 0;
2719 static void
2720 pop_tree_stack_entry(void)
2722         struct tree_stack_entry *entry = tree_stack;
2724         tree_lineno = entry->lineno;
2725         entry->name[0] = 0;
2726         tree_stack = entry->prev;
2727         free(entry);
2730 static void
2731 push_tree_stack_entry(char *name, unsigned long lineno)
2733         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2734         size_t pathlen = strlen(opt_path);
2736         if (!entry)
2737                 return;
2739         entry->prev = tree_stack;
2740         entry->name = opt_path + pathlen;
2741         tree_stack = entry;
2743         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2744                 pop_tree_stack_entry();
2745                 return;
2746         }
2748         /* Move the current line to the first tree entry. */
2749         tree_lineno = 1;
2750         entry->lineno = lineno;
2753 /* Parse output from git-ls-tree(1):
2754  *
2755  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2756  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2757  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2758  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2759  */
2761 #define SIZEOF_TREE_ATTR \
2762         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2764 #define TREE_UP_FORMAT "040000 tree %s\t.."
2766 static int
2767 tree_compare_entry(enum line_type type1, char *name1,
2768                    enum line_type type2, char *name2)
2770         if (type1 != type2) {
2771                 if (type1 == LINE_TREE_DIR)
2772                         return -1;
2773                 return 1;
2774         }
2776         return strcmp(name1, name2);
2779 static bool
2780 tree_read(struct view *view, char *text)
2782         size_t textlen = text ? strlen(text) : 0;
2783         char buf[SIZEOF_STR];
2784         unsigned long pos;
2785         enum line_type type;
2786         bool first_read = view->lines == 0;
2788         if (textlen <= SIZEOF_TREE_ATTR)
2789                 return FALSE;
2791         type = text[STRING_SIZE("100644 ")] == 't'
2792              ? LINE_TREE_DIR : LINE_TREE_FILE;
2794         if (first_read) {
2795                 /* Add path info line */
2796                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2797                     !realloc_lines(view, view->line_size + 1) ||
2798                     !add_line_text(view, buf, LINE_DEFAULT))
2799                         return FALSE;
2801                 /* Insert "link" to parent directory. */
2802                 if (*opt_path) {
2803                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2804                             !realloc_lines(view, view->line_size + 1) ||
2805                             !add_line_text(view, buf, LINE_TREE_DIR))
2806                                 return FALSE;
2807                 }
2808         }
2810         /* Strip the path part ... */
2811         if (*opt_path) {
2812                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2813                 size_t striplen = strlen(opt_path);
2814                 char *path = text + SIZEOF_TREE_ATTR;
2816                 if (pathlen > striplen)
2817                         memmove(path, path + striplen,
2818                                 pathlen - striplen + 1);
2819         }
2821         /* Skip "Directory ..." and ".." line. */
2822         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2823                 struct line *line = &view->line[pos];
2824                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2825                 char *path2 = text + SIZEOF_TREE_ATTR;
2826                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2828                 if (cmp <= 0)
2829                         continue;
2831                 text = strdup(text);
2832                 if (!text)
2833                         return FALSE;
2835                 if (view->lines > pos)
2836                         memmove(&view->line[pos + 1], &view->line[pos],
2837                                 (view->lines - pos) * sizeof(*line));
2839                 line = &view->line[pos];
2840                 line->data = text;
2841                 line->type = type;
2842                 view->lines++;
2843                 return TRUE;
2844         }
2846         if (!add_line_text(view, text, type))
2847                 return FALSE;
2849         if (tree_lineno > view->lineno) {
2850                 view->lineno = tree_lineno;
2851                 tree_lineno = 0;
2852         }
2854         return TRUE;
2857 static enum request
2858 tree_request(struct view *view, enum request request, struct line *line)
2860         enum open_flags flags;
2862         if (request != REQ_ENTER)
2863                 return request;
2865         /* Cleanup the stack if the tree view is at a different tree. */
2866         while (!*opt_path && tree_stack)
2867                 pop_tree_stack_entry();
2869         switch (line->type) {
2870         case LINE_TREE_DIR:
2871                 /* Depending on whether it is a subdir or parent (updir?) link
2872                  * mangle the path buffer. */
2873                 if (line == &view->line[1] && *opt_path) {
2874                         pop_tree_stack_entry();
2876                 } else {
2877                         char *data = line->data;
2878                         char *basename = data + SIZEOF_TREE_ATTR;
2880                         push_tree_stack_entry(basename, view->lineno);
2881                 }
2883                 /* Trees and subtrees share the same ID, so they are not not
2884                  * unique like blobs. */
2885                 flags = OPEN_RELOAD;
2886                 request = REQ_VIEW_TREE;
2887                 break;
2889         case LINE_TREE_FILE:
2890                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2891                 request = REQ_VIEW_BLOB;
2892                 break;
2894         default:
2895                 return TRUE;
2896         }
2898         open_view(view, request, flags);
2899         if (request == REQ_VIEW_TREE) {
2900                 view->lineno = tree_lineno;
2901         }
2903         return REQ_NONE;
2906 static void
2907 tree_select(struct view *view, struct line *line)
2909         char *text = line->data + STRING_SIZE("100644 blob ");
2911         if (line->type == LINE_TREE_FILE) {
2912                 string_copy_rev(ref_blob, text);
2914         } else if (line->type != LINE_TREE_DIR) {
2915                 return;
2916         }
2918         string_copy_rev(view->ref, text);
2921 static struct view_ops tree_ops = {
2922         "file",
2923         NULL,
2924         tree_read,
2925         pager_draw,
2926         tree_request,
2927         pager_grep,
2928         tree_select,
2929 };
2931 static bool
2932 blob_read(struct view *view, char *line)
2934         return add_line_text(view, line, LINE_DEFAULT) != NULL;
2937 static struct view_ops blob_ops = {
2938         "line",
2939         NULL,
2940         blob_read,
2941         pager_draw,
2942         pager_request,
2943         pager_grep,
2944         pager_select,
2945 };
2948 /*
2949  * Status backend
2950  */
2952 struct status {
2953         char status;
2954         struct {
2955                 mode_t mode;
2956                 char rev[SIZEOF_REV];
2957         } old;
2958         struct {
2959                 mode_t mode;
2960                 char rev[SIZEOF_REV];
2961         } new;
2962         char name[SIZEOF_STR];
2963 };
2965 static struct status stage_status;
2966 static enum line_type stage_line_type;
2968 /* Get fields from the diff line:
2969  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2970  */
2971 static inline bool
2972 status_get_diff(struct status *file, char *buf, size_t bufsize)
2974         char *old_mode = buf +  1;
2975         char *new_mode = buf +  8;
2976         char *old_rev  = buf + 15;
2977         char *new_rev  = buf + 56;
2978         char *status   = buf + 97;
2980         if (bufsize != 99 ||
2981             old_mode[-1] != ':' ||
2982             new_mode[-1] != ' ' ||
2983             old_rev[-1]  != ' ' ||
2984             new_rev[-1]  != ' ' ||
2985             status[-1]   != ' ')
2986                 return FALSE;
2988         file->status = *status;
2990         string_copy_rev(file->old.rev, old_rev);
2991         string_copy_rev(file->new.rev, new_rev);
2993         file->old.mode = strtoul(old_mode, NULL, 8);
2994         file->new.mode = strtoul(new_mode, NULL, 8);
2996         file->name[0] = 0;
2998         return TRUE;
3001 static bool
3002 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3004         struct status *file = NULL;
3005         struct status *unmerged = NULL;
3006         char buf[SIZEOF_STR * 4];
3007         size_t bufsize = 0;
3008         FILE *pipe;
3010         pipe = popen(cmd, "r");
3011         if (!pipe)
3012                 return FALSE;
3014         add_line_data(view, NULL, type);
3016         while (!feof(pipe) && !ferror(pipe)) {
3017                 char *sep;
3018                 size_t readsize;
3020                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3021                 if (!readsize)
3022                         break;
3023                 bufsize += readsize;
3025                 /* Process while we have NUL chars. */
3026                 while ((sep = memchr(buf, 0, bufsize))) {
3027                         size_t sepsize = sep - buf + 1;
3029                         if (!file) {
3030                                 if (!realloc_lines(view, view->line_size + 1))
3031                                         goto error_out;
3033                                 file = calloc(1, sizeof(*file));
3034                                 if (!file)
3035                                         goto error_out;
3037                                 add_line_data(view, file, type);
3038                         }
3040                         /* Parse diff info part. */
3041                         if (!diff) {
3042                                 file->status = '?';
3044                         } else if (!file->status) {
3045                                 if (!status_get_diff(file, buf, sepsize))
3046                                         goto error_out;
3048                                 bufsize -= sepsize;
3049                                 memmove(buf, sep + 1, bufsize);
3051                                 sep = memchr(buf, 0, bufsize);
3052                                 if (!sep)
3053                                         break;
3054                                 sepsize = sep - buf + 1;
3056                                 /* Collapse all 'M'odified entries that
3057                                  * follow a associated 'U'nmerged entry.
3058                                  */
3059                                 if (file->status == 'U') {
3060                                         unmerged = file;
3062                                 } else if (unmerged) {
3063                                         int collapse = !strcmp(buf, unmerged->name);
3065                                         unmerged = NULL;
3066                                         if (collapse) {
3067                                                 free(file);
3068                                                 view->lines--;
3069                                                 continue;
3070                                         }
3071                                 }
3072                         }
3074                         /* git-ls-files just delivers a NUL separated
3075                          * list of file names similar to the second half
3076                          * of the git-diff-* output. */
3077                         string_ncopy(file->name, buf, sepsize);
3078                         bufsize -= sepsize;
3079                         memmove(buf, sep + 1, bufsize);
3080                         file = NULL;
3081                 }
3082         }
3084         if (ferror(pipe)) {
3085 error_out:
3086                 pclose(pipe);
3087                 return FALSE;
3088         }
3090         if (!view->line[view->lines - 1].data)
3091                 add_line_data(view, NULL, LINE_STAT_NONE);
3093         pclose(pipe);
3094         return TRUE;
3097 /* Don't show unmerged entries in the staged section. */
3098 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3099 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3100 #define STATUS_LIST_OTHER_CMD \
3101         "git ls-files -z --others --exclude-per-directory=.gitignore"
3103 #define STATUS_DIFF_SHOW_CMD \
3104         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3106 /* First parse staged info using git-diff-index(1), then parse unstaged
3107  * info using git-diff-files(1), and finally untracked files using
3108  * git-ls-files(1). */
3109 static bool
3110 status_open(struct view *view)
3112         struct stat statbuf;
3113         char exclude[SIZEOF_STR];
3114         char cmd[SIZEOF_STR];
3115         unsigned long prev_lineno = view->lineno;
3116         size_t i;
3118         for (i = 0; i < view->lines; i++)
3119                 free(view->line[i].data);
3120         free(view->line);
3121         view->lines = view->line_size = view->lineno = 0;
3122         view->line = NULL;
3124         if (!realloc_lines(view, view->line_size + 6))
3125                 return FALSE;
3127         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3128                 return FALSE;
3130         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3132         if (stat(exclude, &statbuf) >= 0) {
3133                 size_t cmdsize = strlen(cmd);
3135                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3136                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3137                         return FALSE;
3138         }
3140         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3141             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3142             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3143                 return FALSE;
3145         /* If all went well restore the previous line number to stay in
3146          * the context. */
3147         if (prev_lineno < view->lines)
3148                 view->lineno = prev_lineno;
3149         else
3150                 view->lineno = view->lines - 1;
3152         return TRUE;
3155 static bool
3156 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3158         struct status *status = line->data;
3160         wmove(view->win, lineno, 0);
3162         if (selected) {
3163                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3164                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3166         } else if (!status && line->type != LINE_STAT_NONE) {
3167                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3168                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3170         } else {
3171                 wattrset(view->win, get_line_attr(line->type));
3172         }
3174         if (!status) {
3175                 char *text;
3177                 switch (line->type) {
3178                 case LINE_STAT_STAGED:
3179                         text = "Changes to be committed:";
3180                         break;
3182                 case LINE_STAT_UNSTAGED:
3183                         text = "Changed but not updated:";
3184                         break;
3186                 case LINE_STAT_UNTRACKED:
3187                         text = "Untracked files:";
3188                         break;
3190                 case LINE_STAT_NONE:
3191                         text = "    (no files)";
3192                         break;
3194                 default:
3195                         return FALSE;
3196                 }
3198                 waddstr(view->win, text);
3199                 return TRUE;
3200         }
3202         waddch(view->win, status->status);
3203         if (!selected)
3204                 wattrset(view->win, A_NORMAL);
3205         wmove(view->win, lineno, 4);
3206         waddstr(view->win, status->name);
3208         return TRUE;
3211 static enum request
3212 status_enter(struct view *view, struct line *line)
3214         struct status *status = line->data;
3215         char path[SIZEOF_STR] = "";
3216         char *info;
3217         size_t cmdsize = 0;
3219         if (line->type == LINE_STAT_NONE ||
3220             (!status && line[1].type == LINE_STAT_NONE)) {
3221                 report("No file to diff");
3222                 return REQ_NONE;
3223         }
3225         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3226                 return REQ_QUIT;
3228         if (opt_cdup[0] &&
3229             line->type != LINE_STAT_UNTRACKED &&
3230             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3231                 return REQ_QUIT;
3233         switch (line->type) {
3234         case LINE_STAT_STAGED:
3235                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3236                                         "--cached", path))
3237                         return REQ_QUIT;
3238                 if (status)
3239                         info = "Staged changes to %s";
3240                 else
3241                         info = "Staged changes";
3242                 break;
3244         case LINE_STAT_UNSTAGED:
3245                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3246                                         "", path))
3247                         return REQ_QUIT;
3248                 if (status)
3249                         info = "Unstaged changes to %s";
3250                 else
3251                         info = "Unstaged changes";
3252                 break;
3254         case LINE_STAT_UNTRACKED:
3255                 if (opt_pipe)
3256                         return REQ_QUIT;
3259                 if (!status) {
3260                         report("No file to show");
3261                         return REQ_NONE;
3262                 }
3264                 opt_pipe = fopen(status->name, "r");
3265                 info = "Untracked file %s";
3266                 break;
3268         default:
3269                 die("w00t");
3270         }
3272         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3273         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3274                 if (status) {
3275                         stage_status = *status;
3276                 } else {
3277                         memset(&stage_status, 0, sizeof(stage_status));
3278                 }
3280                 stage_line_type = line->type;
3281                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3282         }
3284         return REQ_NONE;
3288 static bool
3289 status_update_file(struct view *view, struct status *status, enum line_type type)
3291         char cmd[SIZEOF_STR];
3292         char buf[SIZEOF_STR];
3293         size_t cmdsize = 0;
3294         size_t bufsize = 0;
3295         size_t written = 0;
3296         FILE *pipe;
3298         if (opt_cdup[0] &&
3299             type != LINE_STAT_UNTRACKED &&
3300             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3301                 return FALSE;
3303         switch (type) {
3304         case LINE_STAT_STAGED:
3305                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3306                                         status->old.mode,
3307                                         status->old.rev,
3308                                         status->name, 0))
3309                         return FALSE;
3311                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3312                 break;
3314         case LINE_STAT_UNSTAGED:
3315         case LINE_STAT_UNTRACKED:
3316                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3317                         return FALSE;
3319                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3320                 break;
3322         default:
3323                 die("w00t");
3324         }
3326         pipe = popen(cmd, "w");
3327         if (!pipe)
3328                 return FALSE;
3330         while (!ferror(pipe) && written < bufsize) {
3331                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3332         }
3334         pclose(pipe);
3336         if (written != bufsize)
3337                 return FALSE;
3339         return TRUE;
3342 static void
3343 status_update(struct view *view)
3345         struct line *line = &view->line[view->lineno];
3347         assert(view->lines);
3349         if (!line->data) {
3350                 while (++line < view->line + view->lines && line->data) {
3351                         if (!status_update_file(view, line->data, line->type))
3352                                 report("Failed to update file status");
3353                 }
3355                 if (!line[-1].data) {
3356                         report("Nothing to update");
3357                         return;
3358                 }
3360         } else if (!status_update_file(view, line->data, line->type)) {
3361                 report("Failed to update file status");
3362         }
3365 static enum request
3366 status_request(struct view *view, enum request request, struct line *line)
3368         struct status *status = line->data;
3370         switch (request) {
3371         case REQ_STATUS_UPDATE:
3372                 status_update(view);
3373                 break;
3375         case REQ_STATUS_MERGE:
3376                 open_mergetool(status->name);
3377                 break;
3379         case REQ_EDIT:
3380                 if (!status)
3381                         return request;
3383                 open_editor(status->status != '?', status->name);
3384                 break;
3386         case REQ_ENTER:
3387                 /* After returning the status view has been split to
3388                  * show the stage view. No further reloading is
3389                  * necessary. */
3390                 status_enter(view, line);
3391                 return REQ_NONE;
3393         case REQ_REFRESH:
3394                 /* Simply reload the view. */
3395                 break;
3397         default:
3398                 return request;
3399         }
3401         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3403         return REQ_NONE;
3406 static void
3407 status_select(struct view *view, struct line *line)
3409         struct status *status = line->data;
3410         char file[SIZEOF_STR] = "all files";
3411         char *text;
3412         char *key;
3414         if (status && !string_format(file, "'%s'", status->name))
3415                 return;
3417         if (!status && line[1].type == LINE_STAT_NONE)
3418                 line++;
3420         switch (line->type) {
3421         case LINE_STAT_STAGED:
3422                 text = "Press %s to unstage %s for commit";
3423                 break;
3425         case LINE_STAT_UNSTAGED:
3426                 text = "Press %s to stage %s for commit";
3427                 break;
3429         case LINE_STAT_UNTRACKED:
3430                 text = "Press %s to stage %s for addition";
3431                 break;
3433         case LINE_STAT_NONE:
3434                 text = "Nothing to update";
3435                 break;
3437         default:
3438                 die("w00t");
3439         }
3441         if (status && status->status == 'U') {
3442                 text = "Press %s to resolve conflict in %s";
3443                 key = get_key(REQ_STATUS_MERGE);
3445         } else {
3446                 key = get_key(REQ_STATUS_UPDATE);
3447         }
3449         string_format(view->ref, text, key, file);
3452 static bool
3453 status_grep(struct view *view, struct line *line)
3455         struct status *status = line->data;
3456         enum { S_STATUS, S_NAME, S_END } state;
3457         char buf[2] = "?";
3458         regmatch_t pmatch;
3460         if (!status)
3461                 return FALSE;
3463         for (state = S_STATUS; state < S_END; state++) {
3464                 char *text;
3466                 switch (state) {
3467                 case S_NAME:    text = status->name;    break;
3468                 case S_STATUS:
3469                         buf[0] = status->status;
3470                         text = buf;
3471                         break;
3473                 default:
3474                         return FALSE;
3475                 }
3477                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3478                         return TRUE;
3479         }
3481         return FALSE;
3484 static struct view_ops status_ops = {
3485         "file",
3486         status_open,
3487         NULL,
3488         status_draw,
3489         status_request,
3490         status_grep,
3491         status_select,
3492 };
3495 static bool
3496 stage_diff_line(FILE *pipe, struct line *line)
3498         char *buf = line->data;
3499         size_t bufsize = strlen(buf);
3500         size_t written = 0;
3502         while (!ferror(pipe) && written < bufsize) {
3503                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3504         }
3506         fputc('\n', pipe);
3508         return written == bufsize;
3511 static struct line *
3512 stage_diff_hdr(struct view *view, struct line *line)
3514         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3515         struct line *diff_hdr;
3517         if (line->type == LINE_DIFF_CHUNK)
3518                 diff_hdr = line - 1;
3519         else
3520                 diff_hdr = view->line + 1;
3522         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3523                 if (diff_hdr->type == LINE_DIFF_HEADER)
3524                         return diff_hdr;
3526                 diff_hdr += diff_hdr_dir;
3527         }
3529         return NULL;
3532 static bool
3533 stage_update_chunk(struct view *view, struct line *line)
3535         char cmd[SIZEOF_STR];
3536         size_t cmdsize = 0;
3537         struct line *diff_hdr, *diff_chunk, *diff_end;
3538         FILE *pipe;
3540         diff_hdr = stage_diff_hdr(view, line);
3541         if (!diff_hdr)
3542                 return FALSE;
3544         if (opt_cdup[0] &&
3545             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3546                 return FALSE;
3548         if (!string_format_from(cmd, &cmdsize,
3549                                 "git apply --cached %s - && "
3550                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3551                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3552                 return FALSE;
3554         pipe = popen(cmd, "w");
3555         if (!pipe)
3556                 return FALSE;
3558         diff_end = view->line + view->lines;
3559         if (line->type != LINE_DIFF_CHUNK) {
3560                 diff_chunk = diff_hdr;
3562         } else {
3563                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3564                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3565                             diff_chunk->type == LINE_DIFF_HEADER)
3566                                 diff_end = diff_chunk;
3568                 diff_chunk = line;
3570                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3571                         switch (diff_hdr->type) {
3572                         case LINE_DIFF_HEADER:
3573                         case LINE_DIFF_INDEX:
3574                         case LINE_DIFF_ADD:
3575                         case LINE_DIFF_DEL:
3576                                 break;
3578                         default:
3579                                 diff_hdr++;
3580                                 continue;
3581                         }
3583                         if (!stage_diff_line(pipe, diff_hdr++)) {
3584                                 pclose(pipe);
3585                                 return FALSE;
3586                         }
3587                 }
3588         }
3590         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3591                 diff_chunk++;
3593         pclose(pipe);
3595         if (diff_chunk != diff_end)
3596                 return FALSE;
3598         return TRUE;
3601 static void
3602 stage_update(struct view *view, struct line *line)
3604         if (stage_line_type != LINE_STAT_UNTRACKED &&
3605             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3606                 if (!stage_update_chunk(view, line)) {
3607                         report("Failed to apply chunk");
3608                         return;
3609                 }
3611         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3612                 report("Failed to update file");
3613                 return;
3614         }
3616         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3618         view = VIEW(REQ_VIEW_STATUS);
3619         if (view_is_displayed(view))
3620                 status_enter(view, &view->line[view->lineno]);
3623 static enum request
3624 stage_request(struct view *view, enum request request, struct line *line)
3626         switch (request) {
3627         case REQ_STATUS_UPDATE:
3628                 stage_update(view, line);
3629                 break;
3631         case REQ_EDIT:
3632                 if (!stage_status.name[0])
3633                         return request;
3635                 open_editor(stage_status.status != '?', stage_status.name);
3636                 break;
3638         case REQ_ENTER:
3639                 pager_request(view, request, line);
3640                 break;
3642         default:
3643                 return request;
3644         }
3646         return REQ_NONE;
3649 static struct view_ops stage_ops = {
3650         "line",
3651         NULL,
3652         pager_read,
3653         pager_draw,
3654         stage_request,
3655         pager_grep,
3656         pager_select,
3657 };
3660 /*
3661  * Revision graph
3662  */
3664 struct commit {
3665         char id[SIZEOF_REV];            /* SHA1 ID. */
3666         char title[128];                /* First line of the commit message. */
3667         char author[75];                /* Author of the commit. */
3668         struct tm time;                 /* Date from the author ident. */
3669         struct ref **refs;              /* Repository references. */
3670         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3671         size_t graph_size;              /* The width of the graph array. */
3672 };
3674 /* Size of rev graph with no  "padding" columns */
3675 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3677 struct rev_graph {
3678         struct rev_graph *prev, *next, *parents;
3679         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3680         size_t size;
3681         struct commit *commit;
3682         size_t pos;
3683 };
3685 /* Parents of the commit being visualized. */
3686 static struct rev_graph graph_parents[4];
3688 /* The current stack of revisions on the graph. */
3689 static struct rev_graph graph_stacks[4] = {
3690         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3691         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3692         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3693         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3694 };
3696 static inline bool
3697 graph_parent_is_merge(struct rev_graph *graph)
3699         return graph->parents->size > 1;
3702 static inline void
3703 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3705         struct commit *commit = graph->commit;
3707         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3708                 commit->graph[commit->graph_size++] = symbol;
3711 static void
3712 done_rev_graph(struct rev_graph *graph)
3714         if (graph_parent_is_merge(graph) &&
3715             graph->pos < graph->size - 1 &&
3716             graph->next->size == graph->size + graph->parents->size - 1) {
3717                 size_t i = graph->pos + graph->parents->size - 1;
3719                 graph->commit->graph_size = i * 2;
3720                 while (i < graph->next->size - 1) {
3721                         append_to_rev_graph(graph, ' ');
3722                         append_to_rev_graph(graph, '\\');
3723                         i++;
3724                 }
3725         }
3727         graph->size = graph->pos = 0;
3728         graph->commit = NULL;
3729         memset(graph->parents, 0, sizeof(*graph->parents));
3732 static void
3733 push_rev_graph(struct rev_graph *graph, char *parent)
3735         int i;
3737         /* "Collapse" duplicate parents lines.
3738          *
3739          * FIXME: This needs to also update update the drawn graph but
3740          * for now it just serves as a method for pruning graph lines. */
3741         for (i = 0; i < graph->size; i++)
3742                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3743                         return;
3745         if (graph->size < SIZEOF_REVITEMS) {
3746                 string_copy_rev(graph->rev[graph->size++], parent);
3747         }
3750 static chtype
3751 get_rev_graph_symbol(struct rev_graph *graph)
3753         chtype symbol;
3755         if (graph->parents->size == 0)
3756                 symbol = REVGRAPH_INIT;
3757         else if (graph_parent_is_merge(graph))
3758                 symbol = REVGRAPH_MERGE;
3759         else if (graph->pos >= graph->size)
3760                 symbol = REVGRAPH_BRANCH;
3761         else
3762                 symbol = REVGRAPH_COMMIT;
3764         return symbol;
3767 static void
3768 draw_rev_graph(struct rev_graph *graph)
3770         struct rev_filler {
3771                 chtype separator, line;
3772         };
3773         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3774         static struct rev_filler fillers[] = {
3775                 { ' ',  REVGRAPH_LINE },
3776                 { '`',  '.' },
3777                 { '\'', ' ' },
3778                 { '/',  ' ' },
3779         };
3780         chtype symbol = get_rev_graph_symbol(graph);
3781         struct rev_filler *filler;
3782         size_t i;
3784         filler = &fillers[DEFAULT];
3786         for (i = 0; i < graph->pos; i++) {
3787                 append_to_rev_graph(graph, filler->line);
3788                 if (graph_parent_is_merge(graph->prev) &&
3789                     graph->prev->pos == i)
3790                         filler = &fillers[RSHARP];
3792                 append_to_rev_graph(graph, filler->separator);
3793         }
3795         /* Place the symbol for this revision. */
3796         append_to_rev_graph(graph, symbol);
3798         if (graph->prev->size > graph->size)
3799                 filler = &fillers[RDIAG];
3800         else
3801                 filler = &fillers[DEFAULT];
3803         i++;
3805         for (; i < graph->size; i++) {
3806                 append_to_rev_graph(graph, filler->separator);
3807                 append_to_rev_graph(graph, filler->line);
3808                 if (graph_parent_is_merge(graph->prev) &&
3809                     i < graph->prev->pos + graph->parents->size)
3810                         filler = &fillers[RSHARP];
3811                 if (graph->prev->size > graph->size)
3812                         filler = &fillers[LDIAG];
3813         }
3815         if (graph->prev->size > graph->size) {
3816                 append_to_rev_graph(graph, filler->separator);
3817                 if (filler->line != ' ')
3818                         append_to_rev_graph(graph, filler->line);
3819         }
3822 /* Prepare the next rev graph */
3823 static void
3824 prepare_rev_graph(struct rev_graph *graph)
3826         size_t i;
3828         /* First, traverse all lines of revisions up to the active one. */
3829         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3830                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3831                         break;
3833                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3834         }
3836         /* Interleave the new revision parent(s). */
3837         for (i = 0; i < graph->parents->size; i++)
3838                 push_rev_graph(graph->next, graph->parents->rev[i]);
3840         /* Lastly, put any remaining revisions. */
3841         for (i = graph->pos + 1; i < graph->size; i++)
3842                 push_rev_graph(graph->next, graph->rev[i]);
3845 static void
3846 update_rev_graph(struct rev_graph *graph)
3848         /* If this is the finalizing update ... */
3849         if (graph->commit)
3850                 prepare_rev_graph(graph);
3852         /* Graph visualization needs a one rev look-ahead,
3853          * so the first update doesn't visualize anything. */
3854         if (!graph->prev->commit)
3855                 return;
3857         draw_rev_graph(graph->prev);
3858         done_rev_graph(graph->prev->prev);
3862 /*
3863  * Main view backend
3864  */
3866 static bool
3867 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3869         char buf[DATE_COLS + 1];
3870         struct commit *commit = line->data;
3871         enum line_type type;
3872         int col = 0;
3873         size_t timelen;
3874         size_t authorlen;
3875         int trimmed = 1;
3877         if (!*commit->author)
3878                 return FALSE;
3880         wmove(view->win, lineno, col);
3882         if (selected) {
3883                 type = LINE_CURSOR;
3884                 wattrset(view->win, get_line_attr(type));
3885                 wchgat(view->win, -1, 0, type, NULL);
3887         } else {
3888                 type = LINE_MAIN_COMMIT;
3889                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3890         }
3892         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3893         waddnstr(view->win, buf, timelen);
3894         waddstr(view->win, " ");
3896         col += DATE_COLS;
3897         wmove(view->win, lineno, col);
3898         if (type != LINE_CURSOR)
3899                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3901         if (opt_utf8) {
3902                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3903         } else {
3904                 authorlen = strlen(commit->author);
3905                 if (authorlen > AUTHOR_COLS - 2) {
3906                         authorlen = AUTHOR_COLS - 2;
3907                         trimmed = 1;
3908                 }
3909         }
3911         if (trimmed) {
3912                 waddnstr(view->win, commit->author, authorlen);
3913                 if (type != LINE_CURSOR)
3914                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3915                 waddch(view->win, '~');
3916         } else {
3917                 waddstr(view->win, commit->author);
3918         }
3920         col += AUTHOR_COLS;
3921         if (type != LINE_CURSOR)
3922                 wattrset(view->win, A_NORMAL);
3924         if (opt_rev_graph && commit->graph_size) {
3925                 size_t i;
3927                 wmove(view->win, lineno, col);
3928                 /* Using waddch() instead of waddnstr() ensures that
3929                  * they'll be rendered correctly for the cursor line. */
3930                 for (i = 0; i < commit->graph_size; i++)
3931                         waddch(view->win, commit->graph[i]);
3933                 waddch(view->win, ' ');
3934                 col += commit->graph_size + 1;
3935         }
3937         wmove(view->win, lineno, col);
3939         if (commit->refs) {
3940                 size_t i = 0;
3942                 do {
3943                         if (type == LINE_CURSOR)
3944                                 ;
3945                         else if (commit->refs[i]->tag)
3946                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3947                         else if (commit->refs[i]->remote)
3948                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3949                         else
3950                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3951                         waddstr(view->win, "[");
3952                         waddstr(view->win, commit->refs[i]->name);
3953                         waddstr(view->win, "]");
3954                         if (type != LINE_CURSOR)
3955                                 wattrset(view->win, A_NORMAL);
3956                         waddstr(view->win, " ");
3957                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3958                 } while (commit->refs[i++]->next);
3959         }
3961         if (type != LINE_CURSOR)
3962                 wattrset(view->win, get_line_attr(type));
3964         {
3965                 int titlelen = strlen(commit->title);
3967                 if (col + titlelen > view->width)
3968                         titlelen = view->width - col;
3970                 waddnstr(view->win, commit->title, titlelen);
3971         }
3973         return TRUE;
3976 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3977 static bool
3978 main_read(struct view *view, char *line)
3980         static struct rev_graph *graph = graph_stacks;
3981         enum line_type type;
3982         struct commit *commit;
3984         if (!line) {
3985                 update_rev_graph(graph);
3986                 return TRUE;
3987         }
3989         type = get_line_type(line);
3990         if (type == LINE_COMMIT) {
3991                 commit = calloc(1, sizeof(struct commit));
3992                 if (!commit)
3993                         return FALSE;
3995                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3996                 commit->refs = get_refs(commit->id);
3997                 graph->commit = commit;
3998                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3999                 return TRUE;
4000         }
4002         if (!view->lines)
4003                 return TRUE;
4004         commit = view->line[view->lines - 1].data;
4006         switch (type) {
4007         case LINE_PARENT:
4008                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4009                 break;
4011         case LINE_AUTHOR:
4012         {
4013                 /* Parse author lines where the name may be empty:
4014                  *      author  <email@address.tld> 1138474660 +0100
4015                  */
4016                 char *ident = line + STRING_SIZE("author ");
4017                 char *nameend = strchr(ident, '<');
4018                 char *emailend = strchr(ident, '>');
4020                 if (!nameend || !emailend)
4021                         break;
4023                 update_rev_graph(graph);
4024                 graph = graph->next;
4026                 *nameend = *emailend = 0;
4027                 ident = chomp_string(ident);
4028                 if (!*ident) {
4029                         ident = chomp_string(nameend + 1);
4030                         if (!*ident)
4031                                 ident = "Unknown";
4032                 }
4034                 string_ncopy(commit->author, ident, strlen(ident));
4036                 /* Parse epoch and timezone */
4037                 if (emailend[1] == ' ') {
4038                         char *secs = emailend + 2;
4039                         char *zone = strchr(secs, ' ');
4040                         time_t time = (time_t) atol(secs);
4042                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4043                                 long tz;
4045                                 zone++;
4046                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4047                                 tz += ('0' - zone[2]) * 60 * 60;
4048                                 tz += ('0' - zone[3]) * 60;
4049                                 tz += ('0' - zone[4]) * 60;
4051                                 if (zone[0] == '-')
4052                                         tz = -tz;
4054                                 time -= tz;
4055                         }
4057                         gmtime_r(&time, &commit->time);
4058                 }
4059                 break;
4060         }
4061         default:
4062                 /* Fill in the commit title if it has not already been set. */
4063                 if (commit->title[0])
4064                         break;
4066                 /* Require titles to start with a non-space character at the
4067                  * offset used by git log. */
4068                 if (strncmp(line, "    ", 4))
4069                         break;
4070                 line += 4;
4071                 /* Well, if the title starts with a whitespace character,
4072                  * try to be forgiving.  Otherwise we end up with no title. */
4073                 while (isspace(*line))
4074                         line++;
4075                 if (*line == '\0')
4076                         break;
4077                 /* FIXME: More graceful handling of titles; append "..." to
4078                  * shortened titles, etc. */
4080                 string_ncopy(commit->title, line, strlen(line));
4081         }
4083         return TRUE;
4086 static void
4087 cherry_pick_commit(struct commit *commit)
4089         char cmd[SIZEOF_STR];
4090         char *cherry_pick = getenv("TIG_CHERRY_PICK");
4092         if (!cherry_pick)
4093                 cherry_pick = "git cherry-pick";
4095         if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4096                 open_external_viewer(cmd);
4097         }
4100 static enum request
4101 main_request(struct view *view, enum request request, struct line *line)
4103         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4105         if (request == REQ_ENTER)
4106                 open_view(view, REQ_VIEW_DIFF, flags);
4107         else if (request == REQ_CHERRY_PICK)
4108                 cherry_pick_commit(line->data);
4109         else
4110                 return request;
4112         return REQ_NONE;
4115 static bool
4116 main_grep(struct view *view, struct line *line)
4118         struct commit *commit = line->data;
4119         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4120         char buf[DATE_COLS + 1];
4121         regmatch_t pmatch;
4123         for (state = S_TITLE; state < S_END; state++) {
4124                 char *text;
4126                 switch (state) {
4127                 case S_TITLE:   text = commit->title;   break;
4128                 case S_AUTHOR:  text = commit->author;  break;
4129                 case S_DATE:
4130                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4131                                 continue;
4132                         text = buf;
4133                         break;
4135                 default:
4136                         return FALSE;
4137                 }
4139                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4140                         return TRUE;
4141         }
4143         return FALSE;
4146 static void
4147 main_select(struct view *view, struct line *line)
4149         struct commit *commit = line->data;
4151         string_copy_rev(view->ref, commit->id);
4152         string_copy_rev(ref_commit, view->ref);
4155 static struct view_ops main_ops = {
4156         "commit",
4157         NULL,
4158         main_read,
4159         main_draw,
4160         main_request,
4161         main_grep,
4162         main_select,
4163 };
4166 /*
4167  * Unicode / UTF-8 handling
4168  *
4169  * NOTE: Much of the following code for dealing with unicode is derived from
4170  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4171  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4172  */
4174 /* I've (over)annotated a lot of code snippets because I am not entirely
4175  * confident that the approach taken by this small UTF-8 interface is correct.
4176  * --jonas */
4178 static inline int
4179 unicode_width(unsigned long c)
4181         if (c >= 0x1100 &&
4182            (c <= 0x115f                         /* Hangul Jamo */
4183             || c == 0x2329
4184             || c == 0x232a
4185             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4186                                                 /* CJK ... Yi */
4187             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4188             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4189             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4190             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4191             || (c >= 0xffe0  && c <= 0xffe6)
4192             || (c >= 0x20000 && c <= 0x2fffd)
4193             || (c >= 0x30000 && c <= 0x3fffd)))
4194                 return 2;
4196         return 1;
4199 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4200  * Illegal bytes are set one. */
4201 static const unsigned char utf8_bytes[256] = {
4202         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,
4203         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,
4204         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,
4205         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,
4206         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,
4207         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,
4208         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,
4209         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,
4210 };
4212 /* Decode UTF-8 multi-byte representation into a unicode character. */
4213 static inline unsigned long
4214 utf8_to_unicode(const char *string, size_t length)
4216         unsigned long unicode;
4218         switch (length) {
4219         case 1:
4220                 unicode  =   string[0];
4221                 break;
4222         case 2:
4223                 unicode  =  (string[0] & 0x1f) << 6;
4224                 unicode +=  (string[1] & 0x3f);
4225                 break;
4226         case 3:
4227                 unicode  =  (string[0] & 0x0f) << 12;
4228                 unicode += ((string[1] & 0x3f) << 6);
4229                 unicode +=  (string[2] & 0x3f);
4230                 break;
4231         case 4:
4232                 unicode  =  (string[0] & 0x0f) << 18;
4233                 unicode += ((string[1] & 0x3f) << 12);
4234                 unicode += ((string[2] & 0x3f) << 6);
4235                 unicode +=  (string[3] & 0x3f);
4236                 break;
4237         case 5:
4238                 unicode  =  (string[0] & 0x0f) << 24;
4239                 unicode += ((string[1] & 0x3f) << 18);
4240                 unicode += ((string[2] & 0x3f) << 12);
4241                 unicode += ((string[3] & 0x3f) << 6);
4242                 unicode +=  (string[4] & 0x3f);
4243                 break;
4244         case 6:
4245                 unicode  =  (string[0] & 0x01) << 30;
4246                 unicode += ((string[1] & 0x3f) << 24);
4247                 unicode += ((string[2] & 0x3f) << 18);
4248                 unicode += ((string[3] & 0x3f) << 12);
4249                 unicode += ((string[4] & 0x3f) << 6);
4250                 unicode +=  (string[5] & 0x3f);
4251                 break;
4252         default:
4253                 die("Invalid unicode length");
4254         }
4256         /* Invalid characters could return the special 0xfffd value but NUL
4257          * should be just as good. */
4258         return unicode > 0xffff ? 0 : unicode;
4261 /* Calculates how much of string can be shown within the given maximum width
4262  * and sets trimmed parameter to non-zero value if all of string could not be
4263  * shown.
4264  *
4265  * Additionally, adds to coloffset how many many columns to move to align with
4266  * the expected position. Takes into account how multi-byte and double-width
4267  * characters will effect the cursor position.
4268  *
4269  * Returns the number of bytes to output from string to satisfy max_width. */
4270 static size_t
4271 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4273         const char *start = string;
4274         const char *end = strchr(string, '\0');
4275         size_t mbwidth = 0;
4276         size_t width = 0;
4278         *trimmed = 0;
4280         while (string < end) {
4281                 int c = *(unsigned char *) string;
4282                 unsigned char bytes = utf8_bytes[c];
4283                 size_t ucwidth;
4284                 unsigned long unicode;
4286                 if (string + bytes > end)
4287                         break;
4289                 /* Change representation to figure out whether
4290                  * it is a single- or double-width character. */
4292                 unicode = utf8_to_unicode(string, bytes);
4293                 /* FIXME: Graceful handling of invalid unicode character. */
4294                 if (!unicode)
4295                         break;
4297                 ucwidth = unicode_width(unicode);
4298                 width  += ucwidth;
4299                 if (width > max_width) {
4300                         *trimmed = 1;
4301                         break;
4302                 }
4304                 /* The column offset collects the differences between the
4305                  * number of bytes encoding a character and the number of
4306                  * columns will be used for rendering said character.
4307                  *
4308                  * So if some character A is encoded in 2 bytes, but will be
4309                  * represented on the screen using only 1 byte this will and up
4310                  * adding 1 to the multi-byte column offset.
4311                  *
4312                  * Assumes that no double-width character can be encoding in
4313                  * less than two bytes. */
4314                 if (bytes > ucwidth)
4315                         mbwidth += bytes - ucwidth;
4317                 string  += bytes;
4318         }
4320         *coloffset += mbwidth;
4322         return string - start;
4326 /*
4327  * Status management
4328  */
4330 /* Whether or not the curses interface has been initialized. */
4331 static bool cursed = FALSE;
4333 /* The status window is used for polling keystrokes. */
4334 static WINDOW *status_win;
4336 static bool status_empty = TRUE;
4338 /* Update status and title window. */
4339 static void
4340 report(const char *msg, ...)
4342         struct view *view = display[current_view];
4344         if (input_mode)
4345                 return;
4347         if (!view) {
4348                 char buf[SIZEOF_STR];
4349                 va_list args;
4351                 va_start(args, msg);
4352                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4353                         buf[sizeof(buf) - 1] = 0;
4354                         buf[sizeof(buf) - 2] = '.';
4355                         buf[sizeof(buf) - 3] = '.';
4356                         buf[sizeof(buf) - 4] = '.';
4357                 }
4358                 va_end(args);
4359                 die("%s", buf);
4360         }
4362         if (!status_empty || *msg) {
4363                 va_list args;
4365                 va_start(args, msg);
4367                 wmove(status_win, 0, 0);
4368                 if (*msg) {
4369                         vwprintw(status_win, msg, args);
4370                         status_empty = FALSE;
4371                 } else {
4372                         status_empty = TRUE;
4373                 }
4374                 wclrtoeol(status_win);
4375                 wrefresh(status_win);
4377                 va_end(args);
4378         }
4380         update_view_title(view);
4381         update_display_cursor(view);
4384 /* Controls when nodelay should be in effect when polling user input. */
4385 static void
4386 set_nonblocking_input(bool loading)
4388         static unsigned int loading_views;
4390         if ((loading == FALSE && loading_views-- == 1) ||
4391             (loading == TRUE  && loading_views++ == 0))
4392                 nodelay(status_win, loading);
4395 static void
4396 init_display(void)
4398         int x, y;
4400         /* Initialize the curses library */
4401         if (isatty(STDIN_FILENO)) {
4402                 cursed = !!initscr();
4403         } else {
4404                 /* Leave stdin and stdout alone when acting as a pager. */
4405                 FILE *io = fopen("/dev/tty", "r+");
4407                 if (!io)
4408                         die("Failed to open /dev/tty");
4409                 cursed = !!newterm(NULL, io, io);
4410         }
4412         if (!cursed)
4413                 die("Failed to initialize curses");
4415         nonl();         /* Tell curses not to do NL->CR/NL on output */
4416         cbreak();       /* Take input chars one at a time, no wait for \n */
4417         noecho();       /* Don't echo input */
4418         leaveok(stdscr, TRUE);
4420         if (has_colors())
4421                 init_colors();
4423         getmaxyx(stdscr, y, x);
4424         status_win = newwin(1, 0, y - 1, 0);
4425         if (!status_win)
4426                 die("Failed to create status window");
4428         /* Enable keyboard mapping */
4429         keypad(status_win, TRUE);
4430         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4433 static char *
4434 read_prompt(const char *prompt)
4436         enum { READING, STOP, CANCEL } status = READING;
4437         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4438         int pos = 0;
4440         while (status == READING) {
4441                 struct view *view;
4442                 int i, key;
4444                 input_mode = TRUE;
4446                 foreach_view (view, i)
4447                         update_view(view);
4449                 input_mode = FALSE;
4451                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4452                 wclrtoeol(status_win);
4454                 /* Refresh, accept single keystroke of input */
4455                 key = wgetch(status_win);
4456                 switch (key) {
4457                 case KEY_RETURN:
4458                 case KEY_ENTER:
4459                 case '\n':
4460                         status = pos ? STOP : CANCEL;
4461                         break;
4463                 case KEY_BACKSPACE:
4464                         if (pos > 0)
4465                                 pos--;
4466                         else
4467                                 status = CANCEL;
4468                         break;
4470                 case KEY_ESC:
4471                         status = CANCEL;
4472                         break;
4474                 case ERR:
4475                         break;
4477                 default:
4478                         if (pos >= sizeof(buf)) {
4479                                 report("Input string too long");
4480                                 return NULL;
4481                         }
4483                         if (isprint(key))
4484                                 buf[pos++] = (char) key;
4485                 }
4486         }
4488         /* Clear the status window */
4489         status_empty = FALSE;
4490         report("");
4492         if (status == CANCEL)
4493                 return NULL;
4495         buf[pos++] = 0;
4497         return buf;
4500 /*
4501  * Repository references
4502  */
4504 static struct ref *refs;
4505 static size_t refs_size;
4507 /* Id <-> ref store */
4508 static struct ref ***id_refs;
4509 static size_t id_refs_size;
4511 static struct ref **
4512 get_refs(char *id)
4514         struct ref ***tmp_id_refs;
4515         struct ref **ref_list = NULL;
4516         size_t ref_list_size = 0;
4517         size_t i;
4519         for (i = 0; i < id_refs_size; i++)
4520                 if (!strcmp(id, id_refs[i][0]->id))
4521                         return id_refs[i];
4523         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4524         if (!tmp_id_refs)
4525                 return NULL;
4527         id_refs = tmp_id_refs;
4529         for (i = 0; i < refs_size; i++) {
4530                 struct ref **tmp;
4532                 if (strcmp(id, refs[i].id))
4533                         continue;
4535                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4536                 if (!tmp) {
4537                         if (ref_list)
4538                                 free(ref_list);
4539                         return NULL;
4540                 }
4542                 ref_list = tmp;
4543                 if (ref_list_size > 0)
4544                         ref_list[ref_list_size - 1]->next = 1;
4545                 ref_list[ref_list_size] = &refs[i];
4547                 /* XXX: The properties of the commit chains ensures that we can
4548                  * safely modify the shared ref. The repo references will
4549                  * always be similar for the same id. */
4550                 ref_list[ref_list_size]->next = 0;
4551                 ref_list_size++;
4552         }
4554         if (ref_list)
4555                 id_refs[id_refs_size++] = ref_list;
4557         return ref_list;
4560 static int
4561 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4563         struct ref *ref;
4564         bool tag = FALSE;
4565         bool remote = FALSE;
4567         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4568                 /* Commits referenced by tags has "^{}" appended. */
4569                 if (name[namelen - 1] != '}')
4570                         return OK;
4572                 while (namelen > 0 && name[namelen] != '^')
4573                         namelen--;
4575                 tag = TRUE;
4576                 namelen -= STRING_SIZE("refs/tags/");
4577                 name    += STRING_SIZE("refs/tags/");
4579         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4580                 remote = TRUE;
4581                 namelen -= STRING_SIZE("refs/remotes/");
4582                 name    += STRING_SIZE("refs/remotes/");
4584         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4585                 namelen -= STRING_SIZE("refs/heads/");
4586                 name    += STRING_SIZE("refs/heads/");
4588         } else if (!strcmp(name, "HEAD")) {
4589                 return OK;
4590         }
4592         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4593         if (!refs)
4594                 return ERR;
4596         ref = &refs[refs_size++];
4597         ref->name = malloc(namelen + 1);
4598         if (!ref->name)
4599                 return ERR;
4601         strncpy(ref->name, name, namelen);
4602         ref->name[namelen] = 0;
4603         ref->tag = tag;
4604         ref->remote = remote;
4605         string_copy_rev(ref->id, id);
4607         return OK;
4610 static int
4611 load_refs(void)
4613         const char *cmd_env = getenv("TIG_LS_REMOTE");
4614         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4616         return read_properties(popen(cmd, "r"), "\t", read_ref);
4619 static int
4620 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4622         if (!strcmp(name, "i18n.commitencoding"))
4623                 string_ncopy(opt_encoding, value, valuelen);
4625         if (!strcmp(name, "core.editor"))
4626                 string_ncopy(opt_editor, value, valuelen);
4628         return OK;
4631 static int
4632 load_repo_config(void)
4634         return read_properties(popen(GIT_CONFIG " --list", "r"),
4635                                "=", read_repo_config_option);
4638 static int
4639 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4641         if (!opt_git_dir[0]) {
4642                 string_ncopy(opt_git_dir, name, namelen);
4644         } else if (opt_is_inside_work_tree == -1) {
4645                 /* This can be 3 different values depending on the
4646                  * version of git being used. If git-rev-parse does not
4647                  * understand --is-inside-work-tree it will simply echo
4648                  * the option else either "true" or "false" is printed.
4649                  * Default to true for the unknown case. */
4650                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4652         } else {
4653                 string_ncopy(opt_cdup, name, namelen);
4654         }
4656         return OK;
4659 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4660  * must be the last one! */
4661 static int
4662 load_repo_info(void)
4664         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4665                                "=", read_repo_info);
4668 static int
4669 read_properties(FILE *pipe, const char *separators,
4670                 int (*read_property)(char *, size_t, char *, size_t))
4672         char buffer[BUFSIZ];
4673         char *name;
4674         int state = OK;
4676         if (!pipe)
4677                 return ERR;
4679         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4680                 char *value;
4681                 size_t namelen;
4682                 size_t valuelen;
4684                 name = chomp_string(name);
4685                 namelen = strcspn(name, separators);
4687                 if (name[namelen]) {
4688                         name[namelen] = 0;
4689                         value = chomp_string(name + namelen + 1);
4690                         valuelen = strlen(value);
4692                 } else {
4693                         value = "";
4694                         valuelen = 0;
4695                 }
4697                 state = read_property(name, namelen, value, valuelen);
4698         }
4700         if (state != ERR && ferror(pipe))
4701                 state = ERR;
4703         pclose(pipe);
4705         return state;
4709 /*
4710  * Main
4711  */
4713 static void __NORETURN
4714 quit(int sig)
4716         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4717         if (cursed)
4718                 endwin();
4719         exit(0);
4722 static void __NORETURN
4723 die(const char *err, ...)
4725         va_list args;
4727         endwin();
4729         va_start(args, err);
4730         fputs("tig: ", stderr);
4731         vfprintf(stderr, err, args);
4732         fputs("\n", stderr);
4733         va_end(args);
4735         exit(1);
4738 int
4739 main(int argc, char *argv[])
4741         struct view *view;
4742         enum request request;
4743         size_t i;
4745         signal(SIGINT, quit);
4747         if (setlocale(LC_ALL, "")) {
4748                 char *codeset = nl_langinfo(CODESET);
4750                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4751         }
4753         if (load_repo_info() == ERR)
4754                 die("Failed to load repo info.");
4756         if (load_options() == ERR)
4757                 die("Failed to load user config.");
4759         /* Load the repo config file so options can be overwritten from
4760          * the command line. */
4761         if (load_repo_config() == ERR)
4762                 die("Failed to load repo config.");
4764         if (!parse_options(argc, argv))
4765                 return 0;
4767         /* Require a git repository unless when running in pager mode. */
4768         if (!opt_git_dir[0])
4769                 die("Not a git repository");
4771         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4772                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4773                 if (opt_iconv == ICONV_NONE)
4774                         die("Failed to initialize character set conversion");
4775         }
4777         if (load_refs() == ERR)
4778                 die("Failed to load refs.");
4780         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4781                 view->cmd_env = getenv(view->cmd_env);
4783         request = opt_request;
4785         init_display();
4787         while (view_driver(display[current_view], request)) {
4788                 int key;
4789                 int i;
4791                 foreach_view (view, i)
4792                         update_view(view);
4794                 /* Refresh, accept single keystroke of input */
4795                 key = wgetch(status_win);
4797                 /* wgetch() with nodelay() enabled returns ERR when there's no
4798                  * input. */
4799                 if (key == ERR) {
4800                         request = REQ_NONE;
4801                         continue;
4802                 }
4804                 request = get_keybinding(display[current_view]->keymap, key);
4806                 /* Some low-level request handling. This keeps access to
4807                  * status_win restricted. */
4808                 switch (request) {
4809                 case REQ_PROMPT:
4810                 {
4811                         char *cmd = read_prompt(":");
4813                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4814                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4815                                         opt_request = REQ_VIEW_DIFF;
4816                                 } else {
4817                                         opt_request = REQ_VIEW_PAGER;
4818                                 }
4819                                 break;
4820                         }
4822                         request = REQ_NONE;
4823                         break;
4824                 }
4825                 case REQ_SEARCH:
4826                 case REQ_SEARCH_BACK:
4827                 {
4828                         const char *prompt = request == REQ_SEARCH
4829                                            ? "/" : "?";
4830                         char *search = read_prompt(prompt);
4832                         if (search)
4833                                 string_ncopy(opt_search, search, strlen(search));
4834                         else
4835                                 request = REQ_NONE;
4836                         break;
4837                 }
4838                 case REQ_SCREEN_RESIZE:
4839                 {
4840                         int height, width;
4842                         getmaxyx(stdscr, height, width);
4844                         /* Resize the status view and let the view driver take
4845                          * care of resizing the displayed views. */
4846                         wresize(status_win, 1, width);
4847                         mvwin(status_win, height - 1, 0);
4848                         wrefresh(status_win);
4849                         break;
4850                 }
4851                 default:
4852                         break;
4853                 }
4854         }
4856         quit(0);
4858         return 0;