Code

Add support for launching git-mergetool from the status view
[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_(NONE,              "Do nothing"), \
347         REQ_(PROMPT,            "Bring up the prompt"), \
348         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
349         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
350         REQ_(SHOW_VERSION,      "Show version information"), \
351         REQ_(STOP_LOADING,      "Stop all loading views"), \
352         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
353         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
354         REQ_(STATUS_UPDATE,     "Update file status"), \
355         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
356         REQ_(EDIT,              "Open in editor"), \
357         REQ_(CHERRY_PICK,       "Cherry-pick commit to current branch")
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,
368         REQ_UNKNOWN,
370 #undef  REQ_GROUP
371 #undef  REQ_
372 };
374 struct request_info {
375         enum request request;
376         char *name;
377         int namelen;
378         char *help;
379 };
381 static struct request_info req_info[] = {
382 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
383 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
384         REQ_INFO
385 #undef  REQ_GROUP
386 #undef  REQ_
387 };
389 static enum request
390 get_request(const char *name)
392         int namelen = strlen(name);
393         int i;
395         for (i = 0; i < ARRAY_SIZE(req_info); i++)
396                 if (req_info[i].namelen == namelen &&
397                     !string_enum_compare(req_info[i].name, name, namelen))
398                         return req_info[i].request;
400         return REQ_UNKNOWN;
404 /*
405  * Options
406  */
408 static const char usage[] =
409 "tig " TIG_VERSION " (" __DATE__ ")\n"
410 "\n"
411 "Usage: tig [options]\n"
412 "   or: tig [options] [--] [git log options]\n"
413 "   or: tig [options] log  [git log options]\n"
414 "   or: tig [options] diff [git diff options]\n"
415 "   or: tig [options] show [git show options]\n"
416 "   or: tig [options] <    [git command output]\n"
417 "\n"
418 "Options:\n"
419 "  -l                          Start up in log view\n"
420 "  -d                          Start up in diff view\n"
421 "  -S                          Start up in status view\n"
422 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
423 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
424 "  --                          Mark end of tig options\n"
425 "  -v, --version               Show version and exit\n"
426 "  -h, --help                  Show help message and exit\n";
428 /* Option and state variables. */
429 static bool opt_line_number             = FALSE;
430 static bool opt_rev_graph               = FALSE;
431 static int opt_num_interval             = NUMBER_INTERVAL;
432 static int opt_tab_size                 = TABSIZE;
433 static enum request opt_request         = REQ_VIEW_MAIN;
434 static char opt_cmd[SIZEOF_STR]         = "";
435 static char opt_path[SIZEOF_STR]        = "";
436 static FILE *opt_pipe                   = NULL;
437 static char opt_encoding[20]            = "UTF-8";
438 static bool opt_utf8                    = TRUE;
439 static char opt_codeset[20]             = "UTF-8";
440 static iconv_t opt_iconv                = ICONV_NONE;
441 static char opt_search[SIZEOF_STR]      = "";
442 static char opt_cdup[SIZEOF_STR]        = "";
443 static char opt_git_dir[SIZEOF_STR]     = "";
444 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
445 static char opt_editor[SIZEOF_STR]      = "";
447 enum option_type {
448         OPT_NONE,
449         OPT_INT,
450 };
452 static bool
453 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
455         va_list args;
456         char *value = "";
457         int *number;
459         if (opt[0] != '-')
460                 return FALSE;
462         if (opt[1] == '-') {
463                 int namelen = strlen(name);
465                 opt += 2;
467                 if (strncmp(opt, name, namelen))
468                         return FALSE;
470                 if (opt[namelen] == '=')
471                         value = opt + namelen + 1;
473         } else {
474                 if (!short_name || opt[1] != short_name)
475                         return FALSE;
476                 value = opt + 2;
477         }
479         va_start(args, type);
480         if (type == OPT_INT) {
481                 number = va_arg(args, int *);
482                 if (isdigit(*value))
483                         *number = atoi(value);
484         }
485         va_end(args);
487         return TRUE;
490 /* Returns the index of log or diff command or -1 to exit. */
491 static bool
492 parse_options(int argc, char *argv[])
494         int i;
496         for (i = 1; i < argc; i++) {
497                 char *opt = argv[i];
499                 if (!strcmp(opt, "log") ||
500                     !strcmp(opt, "diff") ||
501                     !strcmp(opt, "show")) {
502                         opt_request = opt[0] == 'l'
503                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
504                         break;
505                 }
507                 if (opt[0] && opt[0] != '-')
508                         break;
510                 if (!strcmp(opt, "-l")) {
511                         opt_request = REQ_VIEW_LOG;
512                         continue;
513                 }
515                 if (!strcmp(opt, "-d")) {
516                         opt_request = REQ_VIEW_DIFF;
517                         continue;
518                 }
520                 if (!strcmp(opt, "-S")) {
521                         opt_request = REQ_VIEW_STATUS;
522                         continue;
523                 }
525                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
526                         opt_line_number = TRUE;
527                         continue;
528                 }
530                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
531                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
532                         continue;
533                 }
535                 if (check_option(opt, 'v', "version", OPT_NONE)) {
536                         printf("tig version %s\n", TIG_VERSION);
537                         return FALSE;
538                 }
540                 if (check_option(opt, 'h', "help", OPT_NONE)) {
541                         printf(usage);
542                         return FALSE;
543                 }
545                 if (!strcmp(opt, "--")) {
546                         i++;
547                         break;
548                 }
550                 die("unknown option '%s'\n\n%s", opt, usage);
551         }
553         if (!isatty(STDIN_FILENO)) {
554                 opt_request = REQ_VIEW_PAGER;
555                 opt_pipe = stdin;
557         } else if (i < argc) {
558                 size_t buf_size;
560                 if (opt_request == REQ_VIEW_MAIN)
561                         /* XXX: This is vulnerable to the user overriding
562                          * options required for the main view parser. */
563                         string_copy(opt_cmd, "git log --pretty=raw");
564                 else
565                         string_copy(opt_cmd, "git");
566                 buf_size = strlen(opt_cmd);
568                 while (buf_size < sizeof(opt_cmd) && i < argc) {
569                         opt_cmd[buf_size++] = ' ';
570                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
571                 }
573                 if (buf_size >= sizeof(opt_cmd))
574                         die("command too long");
576                 opt_cmd[buf_size] = 0;
577         }
579         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
580                 opt_utf8 = FALSE;
582         return TRUE;
586 /*
587  * Line-oriented content detection.
588  */
590 #define LINE_INFO \
591 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
593 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
595 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
596 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
603 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
604 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
605 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
607 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
608 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
610 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
611 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
612 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
613 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
614 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
615 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
616 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
617 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
619 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
620 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
621 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
622 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
623 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
624 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
625 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
626 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
628 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
630 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
631 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
632 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
633 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
634 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
635 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
636 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
637 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
639 enum line_type {
640 #define LINE(type, line, fg, bg, attr) \
641         LINE_##type
642         LINE_INFO
643 #undef  LINE
644 };
646 struct line_info {
647         const char *name;       /* Option name. */
648         int namelen;            /* Size of option name. */
649         const char *line;       /* The start of line to match. */
650         int linelen;            /* Size of string to match. */
651         int fg, bg, attr;       /* Color and text attributes for the lines. */
652 };
654 static struct line_info line_info[] = {
655 #define LINE(type, line, fg, bg, attr) \
656         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
657         LINE_INFO
658 #undef  LINE
659 };
661 static enum line_type
662 get_line_type(char *line)
664         int linelen = strlen(line);
665         enum line_type type;
667         for (type = 0; type < ARRAY_SIZE(line_info); type++)
668                 /* Case insensitive search matches Signed-off-by lines better. */
669                 if (linelen >= line_info[type].linelen &&
670                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
671                         return type;
673         return LINE_DEFAULT;
676 static inline int
677 get_line_attr(enum line_type type)
679         assert(type < ARRAY_SIZE(line_info));
680         return COLOR_PAIR(type) | line_info[type].attr;
683 static struct line_info *
684 get_line_info(char *name, int namelen)
686         enum line_type type;
688         for (type = 0; type < ARRAY_SIZE(line_info); type++)
689                 if (namelen == line_info[type].namelen &&
690                     !string_enum_compare(line_info[type].name, name, namelen))
691                         return &line_info[type];
693         return NULL;
696 static void
697 init_colors(void)
699         int default_bg = COLOR_BLACK;
700         int default_fg = COLOR_WHITE;
701         enum line_type type;
703         start_color();
705         if (use_default_colors() != ERR) {
706                 default_bg = -1;
707                 default_fg = -1;
708         }
710         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
711                 struct line_info *info = &line_info[type];
712                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
713                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
715                 init_pair(type, fg, bg);
716         }
719 struct line {
720         enum line_type type;
722         /* State flags */
723         unsigned int selected:1;
725         void *data;             /* User data */
726 };
729 /*
730  * Keys
731  */
733 struct keybinding {
734         int alias;
735         enum request request;
736         struct keybinding *next;
737 };
739 static struct keybinding default_keybindings[] = {
740         /* View switching */
741         { 'm',          REQ_VIEW_MAIN },
742         { 'd',          REQ_VIEW_DIFF },
743         { 'l',          REQ_VIEW_LOG },
744         { 't',          REQ_VIEW_TREE },
745         { 'f',          REQ_VIEW_BLOB },
746         { 'p',          REQ_VIEW_PAGER },
747         { 'h',          REQ_VIEW_HELP },
748         { 'S',          REQ_VIEW_STATUS },
749         { 'c',          REQ_VIEW_STAGE },
751         /* View manipulation */
752         { 'q',          REQ_VIEW_CLOSE },
753         { KEY_TAB,      REQ_VIEW_NEXT },
754         { KEY_RETURN,   REQ_ENTER },
755         { KEY_UP,       REQ_PREVIOUS },
756         { KEY_DOWN,     REQ_NEXT },
757         { 'R',          REQ_REFRESH },
759         /* Cursor navigation */
760         { 'k',          REQ_MOVE_UP },
761         { 'j',          REQ_MOVE_DOWN },
762         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
763         { KEY_END,      REQ_MOVE_LAST_LINE },
764         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
765         { ' ',          REQ_MOVE_PAGE_DOWN },
766         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
767         { 'b',          REQ_MOVE_PAGE_UP },
768         { '-',          REQ_MOVE_PAGE_UP },
770         /* Scrolling */
771         { KEY_IC,       REQ_SCROLL_LINE_UP },
772         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
773         { 'w',          REQ_SCROLL_PAGE_UP },
774         { 's',          REQ_SCROLL_PAGE_DOWN },
776         /* Searching */
777         { '/',          REQ_SEARCH },
778         { '?',          REQ_SEARCH_BACK },
779         { 'n',          REQ_FIND_NEXT },
780         { 'N',          REQ_FIND_PREV },
782         /* Misc */
783         { 'Q',          REQ_QUIT },
784         { 'z',          REQ_STOP_LOADING },
785         { 'v',          REQ_SHOW_VERSION },
786         { 'r',          REQ_SCREEN_REDRAW },
787         { '.',          REQ_TOGGLE_LINENO },
788         { 'g',          REQ_TOGGLE_REV_GRAPH },
789         { ':',          REQ_PROMPT },
790         { 'u',          REQ_STATUS_UPDATE },
791         { 'M',          REQ_STATUS_MERGE },
792         { 'e',          REQ_EDIT },
793         { 'C',          REQ_CHERRY_PICK },
795         /* Using the ncurses SIGWINCH handler. */
796         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
797 };
799 #define KEYMAP_INFO \
800         KEYMAP_(GENERIC), \
801         KEYMAP_(MAIN), \
802         KEYMAP_(DIFF), \
803         KEYMAP_(LOG), \
804         KEYMAP_(TREE), \
805         KEYMAP_(BLOB), \
806         KEYMAP_(PAGER), \
807         KEYMAP_(HELP), \
808         KEYMAP_(STATUS), \
809         KEYMAP_(STAGE)
811 enum keymap {
812 #define KEYMAP_(name) KEYMAP_##name
813         KEYMAP_INFO
814 #undef  KEYMAP_
815 };
817 static struct int_map keymap_table[] = {
818 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
819         KEYMAP_INFO
820 #undef  KEYMAP_
821 };
823 #define set_keymap(map, name) \
824         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
826 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
828 static void
829 add_keybinding(enum keymap keymap, enum request request, int key)
831         struct keybinding *keybinding;
833         keybinding = calloc(1, sizeof(*keybinding));
834         if (!keybinding)
835                 die("Failed to allocate keybinding");
837         keybinding->alias = key;
838         keybinding->request = request;
839         keybinding->next = keybindings[keymap];
840         keybindings[keymap] = keybinding;
843 /* Looks for a key binding first in the given map, then in the generic map, and
844  * lastly in the default keybindings. */
845 static enum request
846 get_keybinding(enum keymap keymap, int key)
848         struct keybinding *kbd;
849         int i;
851         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
852                 if (kbd->alias == key)
853                         return kbd->request;
855         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
856                 if (kbd->alias == key)
857                         return kbd->request;
859         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
860                 if (default_keybindings[i].alias == key)
861                         return default_keybindings[i].request;
863         return (enum request) key;
867 struct key {
868         char *name;
869         int value;
870 };
872 static struct key key_table[] = {
873         { "Enter",      KEY_RETURN },
874         { "Space",      ' ' },
875         { "Backspace",  KEY_BACKSPACE },
876         { "Tab",        KEY_TAB },
877         { "Escape",     KEY_ESC },
878         { "Left",       KEY_LEFT },
879         { "Right",      KEY_RIGHT },
880         { "Up",         KEY_UP },
881         { "Down",       KEY_DOWN },
882         { "Insert",     KEY_IC },
883         { "Delete",     KEY_DC },
884         { "Hash",       '#' },
885         { "Home",       KEY_HOME },
886         { "End",        KEY_END },
887         { "PageUp",     KEY_PPAGE },
888         { "PageDown",   KEY_NPAGE },
889         { "F1",         KEY_F(1) },
890         { "F2",         KEY_F(2) },
891         { "F3",         KEY_F(3) },
892         { "F4",         KEY_F(4) },
893         { "F5",         KEY_F(5) },
894         { "F6",         KEY_F(6) },
895         { "F7",         KEY_F(7) },
896         { "F8",         KEY_F(8) },
897         { "F9",         KEY_F(9) },
898         { "F10",        KEY_F(10) },
899         { "F11",        KEY_F(11) },
900         { "F12",        KEY_F(12) },
901 };
903 static int
904 get_key_value(const char *name)
906         int i;
908         for (i = 0; i < ARRAY_SIZE(key_table); i++)
909                 if (!strcasecmp(key_table[i].name, name))
910                         return key_table[i].value;
912         if (strlen(name) == 1 && isprint(*name))
913                 return (int) *name;
915         return ERR;
918 static char *
919 get_key(enum request request)
921         static char buf[BUFSIZ];
922         static char key_char[] = "'X'";
923         size_t pos = 0;
924         char *sep = "";
925         int i;
927         buf[pos] = 0;
929         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
930                 struct keybinding *keybinding = &default_keybindings[i];
931                 char *seq = NULL;
932                 int key;
934                 if (keybinding->request != request)
935                         continue;
937                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
938                         if (key_table[key].value == keybinding->alias)
939                                 seq = key_table[key].name;
941                 if (seq == NULL &&
942                     keybinding->alias < 127 &&
943                     isprint(keybinding->alias)) {
944                         key_char[1] = (char) keybinding->alias;
945                         seq = key_char;
946                 }
948                 if (!seq)
949                         seq = "'?'";
951                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
952                         return "Too many keybindings!";
953                 sep = ", ";
954         }
956         return buf;
960 /*
961  * User config file handling.
962  */
964 static struct int_map color_map[] = {
965 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
966         COLOR_MAP(DEFAULT),
967         COLOR_MAP(BLACK),
968         COLOR_MAP(BLUE),
969         COLOR_MAP(CYAN),
970         COLOR_MAP(GREEN),
971         COLOR_MAP(MAGENTA),
972         COLOR_MAP(RED),
973         COLOR_MAP(WHITE),
974         COLOR_MAP(YELLOW),
975 };
977 #define set_color(color, name) \
978         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
980 static struct int_map attr_map[] = {
981 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
982         ATTR_MAP(NORMAL),
983         ATTR_MAP(BLINK),
984         ATTR_MAP(BOLD),
985         ATTR_MAP(DIM),
986         ATTR_MAP(REVERSE),
987         ATTR_MAP(STANDOUT),
988         ATTR_MAP(UNDERLINE),
989 };
991 #define set_attribute(attr, name) \
992         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
994 static int   config_lineno;
995 static bool  config_errors;
996 static char *config_msg;
998 /* Wants: object fgcolor bgcolor [attr] */
999 static int
1000 option_color_command(int argc, char *argv[])
1002         struct line_info *info;
1004         if (argc != 3 && argc != 4) {
1005                 config_msg = "Wrong number of arguments given to color command";
1006                 return ERR;
1007         }
1009         info = get_line_info(argv[0], strlen(argv[0]));
1010         if (!info) {
1011                 config_msg = "Unknown color name";
1012                 return ERR;
1013         }
1015         if (set_color(&info->fg, argv[1]) == ERR ||
1016             set_color(&info->bg, argv[2]) == ERR) {
1017                 config_msg = "Unknown color";
1018                 return ERR;
1019         }
1021         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1022                 config_msg = "Unknown attribute";
1023                 return ERR;
1024         }
1026         return OK;
1029 /* Wants: name = value */
1030 static int
1031 option_set_command(int argc, char *argv[])
1033         if (argc != 3) {
1034                 config_msg = "Wrong number of arguments given to set command";
1035                 return ERR;
1036         }
1038         if (strcmp(argv[1], "=")) {
1039                 config_msg = "No value assigned";
1040                 return ERR;
1041         }
1043         if (!strcmp(argv[0], "show-rev-graph")) {
1044                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1045                                  !strcmp(argv[2], "true") ||
1046                                  !strcmp(argv[2], "yes"));
1047                 return OK;
1048         }
1050         if (!strcmp(argv[0], "line-number-interval")) {
1051                 opt_num_interval = atoi(argv[2]);
1052                 return OK;
1053         }
1055         if (!strcmp(argv[0], "tab-size")) {
1056                 opt_tab_size = atoi(argv[2]);
1057                 return OK;
1058         }
1060         if (!strcmp(argv[0], "commit-encoding")) {
1061                 char *arg = argv[2];
1062                 int delimiter = *arg;
1063                 int i;
1065                 switch (delimiter) {
1066                 case '"':
1067                 case '\'':
1068                         for (arg++, i = 0; arg[i]; i++)
1069                                 if (arg[i] == delimiter) {
1070                                         arg[i] = 0;
1071                                         break;
1072                                 }
1073                 default:
1074                         string_ncopy(opt_encoding, arg, strlen(arg));
1075                         return OK;
1076                 }
1077         }
1079         config_msg = "Unknown variable name";
1080         return ERR;
1083 /* Wants: mode request key */
1084 static int
1085 option_bind_command(int argc, char *argv[])
1087         enum request request;
1088         int keymap;
1089         int key;
1091         if (argc != 3) {
1092                 config_msg = "Wrong number of arguments given to bind command";
1093                 return ERR;
1094         }
1096         if (set_keymap(&keymap, argv[0]) == ERR) {
1097                 config_msg = "Unknown key map";
1098                 return ERR;
1099         }
1101         key = get_key_value(argv[1]);
1102         if (key == ERR) {
1103                 config_msg = "Unknown key";
1104                 return ERR;
1105         }
1107         request = get_request(argv[2]);
1108         if (request == REQ_UNKNOWN) {
1109                 config_msg = "Unknown request name";
1110                 return ERR;
1111         }
1113         add_keybinding(keymap, request, key);
1115         return OK;
1118 static int
1119 set_option(char *opt, char *value)
1121         char *argv[16];
1122         int valuelen;
1123         int argc = 0;
1125         /* Tokenize */
1126         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1127                 argv[argc++] = value;
1129                 value += valuelen;
1130                 if (!*value)
1131                         break;
1133                 *value++ = 0;
1134                 while (isspace(*value))
1135                         value++;
1136         }
1138         if (!strcmp(opt, "color"))
1139                 return option_color_command(argc, argv);
1141         if (!strcmp(opt, "set"))
1142                 return option_set_command(argc, argv);
1144         if (!strcmp(opt, "bind"))
1145                 return option_bind_command(argc, argv);
1147         config_msg = "Unknown option command";
1148         return ERR;
1151 static int
1152 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1154         int status = OK;
1156         config_lineno++;
1157         config_msg = "Internal error";
1159         /* Check for comment markers, since read_properties() will
1160          * only ensure opt and value are split at first " \t". */
1161         optlen = strcspn(opt, "#");
1162         if (optlen == 0)
1163                 return OK;
1165         if (opt[optlen] != 0) {
1166                 config_msg = "No option value";
1167                 status = ERR;
1169         }  else {
1170                 /* Look for comment endings in the value. */
1171                 size_t len = strcspn(value, "#");
1173                 if (len < valuelen) {
1174                         valuelen = len;
1175                         value[valuelen] = 0;
1176                 }
1178                 status = set_option(opt, value);
1179         }
1181         if (status == ERR) {
1182                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1183                         config_lineno, (int) optlen, opt, config_msg);
1184                 config_errors = TRUE;
1185         }
1187         /* Always keep going if errors are encountered. */
1188         return OK;
1191 static int
1192 load_options(void)
1194         char *home = getenv("HOME");
1195         char buf[SIZEOF_STR];
1196         FILE *file;
1198         config_lineno = 0;
1199         config_errors = FALSE;
1201         if (!home || !string_format(buf, "%s/.tigrc", home))
1202                 return ERR;
1204         /* It's ok that the file doesn't exist. */
1205         file = fopen(buf, "r");
1206         if (!file)
1207                 return OK;
1209         if (read_properties(file, " \t", read_option) == ERR ||
1210             config_errors == TRUE)
1211                 fprintf(stderr, "Errors while loading %s.\n", buf);
1213         return OK;
1217 /*
1218  * The viewer
1219  */
1221 struct view;
1222 struct view_ops;
1224 /* The display array of active views and the index of the current view. */
1225 static struct view *display[2];
1226 static unsigned int current_view;
1228 /* Reading from the prompt? */
1229 static bool input_mode = FALSE;
1231 #define foreach_displayed_view(view, i) \
1232         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1234 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1236 /* Current head and commit ID */
1237 static char ref_blob[SIZEOF_REF]        = "";
1238 static char ref_commit[SIZEOF_REF]      = "HEAD";
1239 static char ref_head[SIZEOF_REF]        = "HEAD";
1241 struct view {
1242         const char *name;       /* View name */
1243         const char *cmd_fmt;    /* Default command line format */
1244         const char *cmd_env;    /* Command line set via environment */
1245         const char *id;         /* Points to either of ref_{head,commit,blob} */
1247         struct view_ops *ops;   /* View operations */
1249         enum keymap keymap;     /* What keymap does this view have */
1251         char cmd[SIZEOF_STR];   /* Command buffer */
1252         char ref[SIZEOF_REF];   /* Hovered commit reference */
1253         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1255         int height, width;      /* The width and height of the main window */
1256         WINDOW *win;            /* The main window */
1257         WINDOW *title;          /* The title window living below the main window */
1259         /* Navigation */
1260         unsigned long offset;   /* Offset of the window top */
1261         unsigned long lineno;   /* Current line number */
1263         /* Searching */
1264         char grep[SIZEOF_STR];  /* Search string */
1265         regex_t *regex;         /* Pre-compiled regex */
1267         /* If non-NULL, points to the view that opened this view. If this view
1268          * is closed tig will switch back to the parent view. */
1269         struct view *parent;
1271         /* Buffering */
1272         unsigned long lines;    /* Total number of lines */
1273         struct line *line;      /* Line index */
1274         unsigned long line_size;/* Total number of allocated lines */
1275         unsigned int digits;    /* Number of digits in the lines member. */
1277         /* Loading */
1278         FILE *pipe;
1279         time_t start_time;
1280 };
1282 struct view_ops {
1283         /* What type of content being displayed. Used in the title bar. */
1284         const char *type;
1285         /* Open and reads in all view content. */
1286         bool (*open)(struct view *view);
1287         /* Read one line; updates view->line. */
1288         bool (*read)(struct view *view, char *data);
1289         /* Draw one line; @lineno must be < view->height. */
1290         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1291         /* Depending on view handle a special requests. */
1292         enum request (*request)(struct view *view, enum request request, struct line *line);
1293         /* Search for regex in a line. */
1294         bool (*grep)(struct view *view, struct line *line);
1295         /* Select line */
1296         void (*select)(struct view *view, struct line *line);
1297 };
1299 static struct view_ops pager_ops;
1300 static struct view_ops main_ops;
1301 static struct view_ops tree_ops;
1302 static struct view_ops blob_ops;
1303 static struct view_ops help_ops;
1304 static struct view_ops status_ops;
1305 static struct view_ops stage_ops;
1307 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1308         { name, cmd, #env, ref, ops, map}
1310 #define VIEW_(id, name, ops, ref) \
1311         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1314 static struct view views[] = {
1315         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1316         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1317         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1318         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1319         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1320         VIEW_(HELP,   "help",   &help_ops,   ""),
1321         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1322         VIEW_(STATUS, "status", &status_ops, ""),
1323         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1324 };
1326 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1328 #define foreach_view(view, i) \
1329         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1331 #define view_is_displayed(view) \
1332         (view == display[0] || view == display[1])
1334 static bool
1335 draw_view_line(struct view *view, unsigned int lineno)
1337         struct line *line;
1338         bool selected = (view->offset + lineno == view->lineno);
1339         bool draw_ok;
1341         assert(view_is_displayed(view));
1343         if (view->offset + lineno >= view->lines)
1344                 return FALSE;
1346         line = &view->line[view->offset + lineno];
1348         if (selected) {
1349                 line->selected = TRUE;
1350                 view->ops->select(view, line);
1351         } else if (line->selected) {
1352                 line->selected = FALSE;
1353                 wmove(view->win, lineno, 0);
1354                 wclrtoeol(view->win);
1355         }
1357         scrollok(view->win, FALSE);
1358         draw_ok = view->ops->draw(view, line, lineno, selected);
1359         scrollok(view->win, TRUE);
1361         return draw_ok;
1364 static void
1365 redraw_view_from(struct view *view, int lineno)
1367         assert(0 <= lineno && lineno < view->height);
1369         for (; lineno < view->height; lineno++) {
1370                 if (!draw_view_line(view, lineno))
1371                         break;
1372         }
1374         redrawwin(view->win);
1375         if (input_mode)
1376                 wnoutrefresh(view->win);
1377         else
1378                 wrefresh(view->win);
1381 static void
1382 redraw_view(struct view *view)
1384         wclear(view->win);
1385         redraw_view_from(view, 0);
1389 static void
1390 update_view_title(struct view *view)
1392         char buf[SIZEOF_STR];
1393         char state[SIZEOF_STR];
1394         size_t bufpos = 0, statelen = 0;
1396         assert(view_is_displayed(view));
1398         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1399                 unsigned int view_lines = view->offset + view->height;
1400                 unsigned int lines = view->lines
1401                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1402                                    : 0;
1404                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1405                                    view->ops->type,
1406                                    view->lineno + 1,
1407                                    view->lines,
1408                                    lines);
1410                 if (view->pipe) {
1411                         time_t secs = time(NULL) - view->start_time;
1413                         /* Three git seconds are a long time ... */
1414                         if (secs > 2)
1415                                 string_format_from(state, &statelen, " %lds", secs);
1416                 }
1417         }
1419         string_format_from(buf, &bufpos, "[%s]", view->name);
1420         if (*view->ref && bufpos < view->width) {
1421                 size_t refsize = strlen(view->ref);
1422                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1424                 if (minsize < view->width)
1425                         refsize = view->width - minsize + 7;
1426                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1427         }
1429         if (statelen && bufpos < view->width) {
1430                 string_format_from(buf, &bufpos, " %s", state);
1431         }
1433         if (view == display[current_view])
1434                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1435         else
1436                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1438         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1439         wclrtoeol(view->title);
1440         wmove(view->title, 0, view->width - 1);
1442         if (input_mode)
1443                 wnoutrefresh(view->title);
1444         else
1445                 wrefresh(view->title);
1448 static void
1449 resize_display(void)
1451         int offset, i;
1452         struct view *base = display[0];
1453         struct view *view = display[1] ? display[1] : display[0];
1455         /* Setup window dimensions */
1457         getmaxyx(stdscr, base->height, base->width);
1459         /* Make room for the status window. */
1460         base->height -= 1;
1462         if (view != base) {
1463                 /* Horizontal split. */
1464                 view->width   = base->width;
1465                 view->height  = SCALE_SPLIT_VIEW(base->height);
1466                 base->height -= view->height;
1468                 /* Make room for the title bar. */
1469                 view->height -= 1;
1470         }
1472         /* Make room for the title bar. */
1473         base->height -= 1;
1475         offset = 0;
1477         foreach_displayed_view (view, i) {
1478                 if (!view->win) {
1479                         view->win = newwin(view->height, 0, offset, 0);
1480                         if (!view->win)
1481                                 die("Failed to create %s view", view->name);
1483                         scrollok(view->win, TRUE);
1485                         view->title = newwin(1, 0, offset + view->height, 0);
1486                         if (!view->title)
1487                                 die("Failed to create title window");
1489                 } else {
1490                         wresize(view->win, view->height, view->width);
1491                         mvwin(view->win,   offset, 0);
1492                         mvwin(view->title, offset + view->height, 0);
1493                 }
1495                 offset += view->height + 1;
1496         }
1499 static void
1500 redraw_display(void)
1502         struct view *view;
1503         int i;
1505         foreach_displayed_view (view, i) {
1506                 redraw_view(view);
1507                 update_view_title(view);
1508         }
1511 static void
1512 update_display_cursor(struct view *view)
1514         /* Move the cursor to the right-most column of the cursor line.
1515          *
1516          * XXX: This could turn out to be a bit expensive, but it ensures that
1517          * the cursor does not jump around. */
1518         if (view->lines) {
1519                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1520                 wrefresh(view->win);
1521         }
1524 /*
1525  * Navigation
1526  */
1528 /* Scrolling backend */
1529 static void
1530 do_scroll_view(struct view *view, int lines)
1532         bool redraw_current_line = FALSE;
1534         /* The rendering expects the new offset. */
1535         view->offset += lines;
1537         assert(0 <= view->offset && view->offset < view->lines);
1538         assert(lines);
1540         /* Move current line into the view. */
1541         if (view->lineno < view->offset) {
1542                 view->lineno = view->offset;
1543                 redraw_current_line = TRUE;
1544         } else if (view->lineno >= view->offset + view->height) {
1545                 view->lineno = view->offset + view->height - 1;
1546                 redraw_current_line = TRUE;
1547         }
1549         assert(view->offset <= view->lineno && view->lineno < view->lines);
1551         /* Redraw the whole screen if scrolling is pointless. */
1552         if (view->height < ABS(lines)) {
1553                 redraw_view(view);
1555         } else {
1556                 int line = lines > 0 ? view->height - lines : 0;
1557                 int end = line + ABS(lines);
1559                 wscrl(view->win, lines);
1561                 for (; line < end; line++) {
1562                         if (!draw_view_line(view, line))
1563                                 break;
1564                 }
1566                 if (redraw_current_line)
1567                         draw_view_line(view, view->lineno - view->offset);
1568         }
1570         redrawwin(view->win);
1571         wrefresh(view->win);
1572         report("");
1575 /* Scroll frontend */
1576 static void
1577 scroll_view(struct view *view, enum request request)
1579         int lines = 1;
1581         assert(view_is_displayed(view));
1583         switch (request) {
1584         case REQ_SCROLL_PAGE_DOWN:
1585                 lines = view->height;
1586         case REQ_SCROLL_LINE_DOWN:
1587                 if (view->offset + lines > view->lines)
1588                         lines = view->lines - view->offset;
1590                 if (lines == 0 || view->offset + view->height >= view->lines) {
1591                         report("Cannot scroll beyond the last line");
1592                         return;
1593                 }
1594                 break;
1596         case REQ_SCROLL_PAGE_UP:
1597                 lines = view->height;
1598         case REQ_SCROLL_LINE_UP:
1599                 if (lines > view->offset)
1600                         lines = view->offset;
1602                 if (lines == 0) {
1603                         report("Cannot scroll beyond the first line");
1604                         return;
1605                 }
1607                 lines = -lines;
1608                 break;
1610         default:
1611                 die("request %d not handled in switch", request);
1612         }
1614         do_scroll_view(view, lines);
1617 /* Cursor moving */
1618 static void
1619 move_view(struct view *view, enum request request)
1621         int scroll_steps = 0;
1622         int steps;
1624         switch (request) {
1625         case REQ_MOVE_FIRST_LINE:
1626                 steps = -view->lineno;
1627                 break;
1629         case REQ_MOVE_LAST_LINE:
1630                 steps = view->lines - view->lineno - 1;
1631                 break;
1633         case REQ_MOVE_PAGE_UP:
1634                 steps = view->height > view->lineno
1635                       ? -view->lineno : -view->height;
1636                 break;
1638         case REQ_MOVE_PAGE_DOWN:
1639                 steps = view->lineno + view->height >= view->lines
1640                       ? view->lines - view->lineno - 1 : view->height;
1641                 break;
1643         case REQ_MOVE_UP:
1644                 steps = -1;
1645                 break;
1647         case REQ_MOVE_DOWN:
1648                 steps = 1;
1649                 break;
1651         default:
1652                 die("request %d not handled in switch", request);
1653         }
1655         if (steps <= 0 && view->lineno == 0) {
1656                 report("Cannot move beyond the first line");
1657                 return;
1659         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1660                 report("Cannot move beyond the last line");
1661                 return;
1662         }
1664         /* Move the current line */
1665         view->lineno += steps;
1666         assert(0 <= view->lineno && view->lineno < view->lines);
1668         /* Check whether the view needs to be scrolled */
1669         if (view->lineno < view->offset ||
1670             view->lineno >= view->offset + view->height) {
1671                 scroll_steps = steps;
1672                 if (steps < 0 && -steps > view->offset) {
1673                         scroll_steps = -view->offset;
1675                 } else if (steps > 0) {
1676                         if (view->lineno == view->lines - 1 &&
1677                             view->lines > view->height) {
1678                                 scroll_steps = view->lines - view->offset - 1;
1679                                 if (scroll_steps >= view->height)
1680                                         scroll_steps -= view->height - 1;
1681                         }
1682                 }
1683         }
1685         if (!view_is_displayed(view)) {
1686                 view->offset += scroll_steps;
1687                 assert(0 <= view->offset && view->offset < view->lines);
1688                 view->ops->select(view, &view->line[view->lineno]);
1689                 return;
1690         }
1692         /* Repaint the old "current" line if we be scrolling */
1693         if (ABS(steps) < view->height)
1694                 draw_view_line(view, view->lineno - steps - view->offset);
1696         if (scroll_steps) {
1697                 do_scroll_view(view, scroll_steps);
1698                 return;
1699         }
1701         /* Draw the current line */
1702         draw_view_line(view, view->lineno - view->offset);
1704         redrawwin(view->win);
1705         wrefresh(view->win);
1706         report("");
1710 /*
1711  * Searching
1712  */
1714 static void search_view(struct view *view, enum request request);
1716 static bool
1717 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1719         assert(view_is_displayed(view));
1721         if (!view->ops->grep(view, line))
1722                 return FALSE;
1724         if (lineno - view->offset >= view->height) {
1725                 view->offset = lineno;
1726                 view->lineno = lineno;
1727                 redraw_view(view);
1729         } else {
1730                 unsigned long old_lineno = view->lineno - view->offset;
1732                 view->lineno = lineno;
1733                 draw_view_line(view, old_lineno);
1735                 draw_view_line(view, view->lineno - view->offset);
1736                 redrawwin(view->win);
1737                 wrefresh(view->win);
1738         }
1740         report("Line %ld matches '%s'", lineno + 1, view->grep);
1741         return TRUE;
1744 static void
1745 find_next(struct view *view, enum request request)
1747         unsigned long lineno = view->lineno;
1748         int direction;
1750         if (!*view->grep) {
1751                 if (!*opt_search)
1752                         report("No previous search");
1753                 else
1754                         search_view(view, request);
1755                 return;
1756         }
1758         switch (request) {
1759         case REQ_SEARCH:
1760         case REQ_FIND_NEXT:
1761                 direction = 1;
1762                 break;
1764         case REQ_SEARCH_BACK:
1765         case REQ_FIND_PREV:
1766                 direction = -1;
1767                 break;
1769         default:
1770                 return;
1771         }
1773         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1774                 lineno += direction;
1776         /* Note, lineno is unsigned long so will wrap around in which case it
1777          * will become bigger than view->lines. */
1778         for (; lineno < view->lines; lineno += direction) {
1779                 struct line *line = &view->line[lineno];
1781                 if (find_next_line(view, lineno, line))
1782                         return;
1783         }
1785         report("No match found for '%s'", view->grep);
1788 static void
1789 search_view(struct view *view, enum request request)
1791         int regex_err;
1793         if (view->regex) {
1794                 regfree(view->regex);
1795                 *view->grep = 0;
1796         } else {
1797                 view->regex = calloc(1, sizeof(*view->regex));
1798                 if (!view->regex)
1799                         return;
1800         }
1802         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1803         if (regex_err != 0) {
1804                 char buf[SIZEOF_STR] = "unknown error";
1806                 regerror(regex_err, view->regex, buf, sizeof(buf));
1807                 report("Search failed: %s", buf);
1808                 return;
1809         }
1811         string_copy(view->grep, opt_search);
1813         find_next(view, request);
1816 /*
1817  * Incremental updating
1818  */
1820 static void
1821 end_update(struct view *view)
1823         if (!view->pipe)
1824                 return;
1825         set_nonblocking_input(FALSE);
1826         if (view->pipe == stdin)
1827                 fclose(view->pipe);
1828         else
1829                 pclose(view->pipe);
1830         view->pipe = NULL;
1833 static bool
1834 begin_update(struct view *view)
1836         if (view->pipe)
1837                 end_update(view);
1839         if (opt_cmd[0]) {
1840                 string_copy(view->cmd, opt_cmd);
1841                 opt_cmd[0] = 0;
1842                 /* When running random commands, initially show the
1843                  * command in the title. However, it maybe later be
1844                  * overwritten if a commit line is selected. */
1845                 if (view == VIEW(REQ_VIEW_PAGER))
1846                         string_copy(view->ref, view->cmd);
1847                 else
1848                         view->ref[0] = 0;
1850         } else if (view == VIEW(REQ_VIEW_TREE)) {
1851                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1852                 char path[SIZEOF_STR];
1854                 if (strcmp(view->vid, view->id))
1855                         opt_path[0] = path[0] = 0;
1856                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1857                         return FALSE;
1859                 if (!string_format(view->cmd, format, view->id, path))
1860                         return FALSE;
1862         } else {
1863                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1864                 const char *id = view->id;
1866                 if (!string_format(view->cmd, format, id, id, id, id, id))
1867                         return FALSE;
1869                 /* Put the current ref_* value to the view title ref
1870                  * member. This is needed by the blob view. Most other
1871                  * views sets it automatically after loading because the
1872                  * first line is a commit line. */
1873                 string_copy_rev(view->ref, view->id);
1874         }
1876         /* Special case for the pager view. */
1877         if (opt_pipe) {
1878                 view->pipe = opt_pipe;
1879                 opt_pipe = NULL;
1880         } else {
1881                 view->pipe = popen(view->cmd, "r");
1882         }
1884         if (!view->pipe)
1885                 return FALSE;
1887         set_nonblocking_input(TRUE);
1889         view->offset = 0;
1890         view->lines  = 0;
1891         view->lineno = 0;
1892         string_copy_rev(view->vid, view->id);
1894         if (view->line) {
1895                 int i;
1897                 for (i = 0; i < view->lines; i++)
1898                         if (view->line[i].data)
1899                                 free(view->line[i].data);
1901                 free(view->line);
1902                 view->line = NULL;
1903         }
1905         view->start_time = time(NULL);
1907         return TRUE;
1910 static struct line *
1911 realloc_lines(struct view *view, size_t line_size)
1913         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1915         if (!tmp)
1916                 return NULL;
1918         view->line = tmp;
1919         view->line_size = line_size;
1920         return view->line;
1923 static bool
1924 update_view(struct view *view)
1926         char in_buffer[BUFSIZ];
1927         char out_buffer[BUFSIZ * 2];
1928         char *line;
1929         /* The number of lines to read. If too low it will cause too much
1930          * redrawing (and possible flickering), if too high responsiveness
1931          * will suffer. */
1932         unsigned long lines = view->height;
1933         int redraw_from = -1;
1935         if (!view->pipe)
1936                 return TRUE;
1938         /* Only redraw if lines are visible. */
1939         if (view->offset + view->height >= view->lines)
1940                 redraw_from = view->lines - view->offset;
1942         /* FIXME: This is probably not perfect for backgrounded views. */
1943         if (!realloc_lines(view, view->lines + lines))
1944                 goto alloc_error;
1946         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1947                 size_t linelen = strlen(line);
1949                 if (linelen)
1950                         line[linelen - 1] = 0;
1952                 if (opt_iconv != ICONV_NONE) {
1953                         ICONV_CONST char *inbuf = line;
1954                         size_t inlen = linelen;
1956                         char *outbuf = out_buffer;
1957                         size_t outlen = sizeof(out_buffer);
1959                         size_t ret;
1961                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1962                         if (ret != (size_t) -1) {
1963                                 line = out_buffer;
1964                                 linelen = strlen(out_buffer);
1965                         }
1966                 }
1968                 if (!view->ops->read(view, line))
1969                         goto alloc_error;
1971                 if (lines-- == 1)
1972                         break;
1973         }
1975         {
1976                 int digits;
1978                 lines = view->lines;
1979                 for (digits = 0; lines; digits++)
1980                         lines /= 10;
1982                 /* Keep the displayed view in sync with line number scaling. */
1983                 if (digits != view->digits) {
1984                         view->digits = digits;
1985                         redraw_from = 0;
1986                 }
1987         }
1989         if (!view_is_displayed(view))
1990                 goto check_pipe;
1992         if (view == VIEW(REQ_VIEW_TREE)) {
1993                 /* Clear the view and redraw everything since the tree sorting
1994                  * might have rearranged things. */
1995                 redraw_view(view);
1997         } else if (redraw_from >= 0) {
1998                 /* If this is an incremental update, redraw the previous line
1999                  * since for commits some members could have changed when
2000                  * loading the main view. */
2001                 if (redraw_from > 0)
2002                         redraw_from--;
2004                 /* Since revision graph visualization requires knowledge
2005                  * about the parent commit, it causes a further one-off
2006                  * needed to be redrawn for incremental updates. */
2007                 if (redraw_from > 0 && opt_rev_graph)
2008                         redraw_from--;
2010                 /* Incrementally draw avoids flickering. */
2011                 redraw_view_from(view, redraw_from);
2012         }
2014         /* Update the title _after_ the redraw so that if the redraw picks up a
2015          * commit reference in view->ref it'll be available here. */
2016         update_view_title(view);
2018 check_pipe:
2019         if (ferror(view->pipe)) {
2020                 report("Failed to read: %s", strerror(errno));
2021                 goto end;
2023         } else if (feof(view->pipe)) {
2024                 report("");
2025                 goto end;
2026         }
2028         return TRUE;
2030 alloc_error:
2031         report("Allocation failure");
2033 end:
2034         view->ops->read(view, NULL);
2035         end_update(view);
2036         return FALSE;
2039 static struct line *
2040 add_line_data(struct view *view, void *data, enum line_type type)
2042         struct line *line = &view->line[view->lines++];
2044         memset(line, 0, sizeof(*line));
2045         line->type = type;
2046         line->data = data;
2048         return line;
2051 static struct line *
2052 add_line_text(struct view *view, char *data, enum line_type type)
2054         if (data)
2055                 data = strdup(data);
2057         return data ? add_line_data(view, data, type) : NULL;
2061 /*
2062  * View opening
2063  */
2065 enum open_flags {
2066         OPEN_DEFAULT = 0,       /* Use default view switching. */
2067         OPEN_SPLIT = 1,         /* Split current view. */
2068         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2069         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2070 };
2072 static void
2073 open_view(struct view *prev, enum request request, enum open_flags flags)
2075         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2076         bool split = !!(flags & OPEN_SPLIT);
2077         bool reload = !!(flags & OPEN_RELOAD);
2078         struct view *view = VIEW(request);
2079         int nviews = displayed_views();
2080         struct view *base_view = display[0];
2082         if (view == prev && nviews == 1 && !reload) {
2083                 report("Already in %s view", view->name);
2084                 return;
2085         }
2087         if (view->ops->open) {
2088                 if (!view->ops->open(view)) {
2089                         report("Failed to load %s view", view->name);
2090                         return;
2091                 }
2093         } else if ((reload || strcmp(view->vid, view->id)) &&
2094                    !begin_update(view)) {
2095                 report("Failed to load %s view", view->name);
2096                 return;
2097         }
2099         if (split) {
2100                 display[1] = view;
2101                 if (!backgrounded)
2102                         current_view = 1;
2103         } else {
2104                 /* Maximize the current view. */
2105                 memset(display, 0, sizeof(display));
2106                 current_view = 0;
2107                 display[current_view] = view;
2108         }
2110         /* Resize the view when switching between split- and full-screen,
2111          * or when switching between two different full-screen views. */
2112         if (nviews != displayed_views() ||
2113             (nviews == 1 && base_view != display[0]))
2114                 resize_display();
2116         if (split && prev->lineno - prev->offset >= prev->height) {
2117                 /* Take the title line into account. */
2118                 int lines = prev->lineno - prev->offset - prev->height + 1;
2120                 /* Scroll the view that was split if the current line is
2121                  * outside the new limited view. */
2122                 do_scroll_view(prev, lines);
2123         }
2125         if (prev && view != prev) {
2126                 if (split && !backgrounded) {
2127                         /* "Blur" the previous view. */
2128                         update_view_title(prev);
2129                 }
2131                 view->parent = prev;
2132         }
2134         if (view->pipe && view->lines == 0) {
2135                 /* Clear the old view and let the incremental updating refill
2136                  * the screen. */
2137                 wclear(view->win);
2138                 report("");
2139         } else {
2140                 redraw_view(view);
2141                 report("");
2142         }
2144         /* If the view is backgrounded the above calls to report()
2145          * won't redraw the view title. */
2146         if (backgrounded)
2147                 update_view_title(view);
2150 static void
2151 open_external_viewer(const char *cmd)
2153         def_prog_mode();           /* save current tty modes */
2154         endwin();                  /* restore original tty modes */
2155         system(cmd);
2156         fprintf(stderr, "Press Enter to continue");
2157         getc(stdin);
2158         reset_prog_mode();
2159         redraw_display();
2162 static void
2163 open_mergetool(const char *file)
2165         char cmd[SIZEOF_STR];
2166         char file_sq[SIZEOF_STR];
2168         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2169             string_format(cmd, "git mergetool %s", file_sq)) {
2170                 open_external_viewer(cmd);
2171         }
2174 static void
2175 open_editor(bool from_root, const char *file)
2177         char cmd[SIZEOF_STR];
2178         char file_sq[SIZEOF_STR];
2179         char *editor;
2180         char *prefix = from_root ? opt_cdup : "";
2182         editor = getenv("GIT_EDITOR");
2183         if (!editor && *opt_editor)
2184                 editor = opt_editor;
2185         if (!editor)
2186                 editor = getenv("VISUAL");
2187         if (!editor)
2188                 editor = getenv("EDITOR");
2189         if (!editor)
2190                 editor = "vi";
2192         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2193             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2194                 open_external_viewer(cmd);
2195         }
2198 /*
2199  * User request switch noodle
2200  */
2202 static int
2203 view_driver(struct view *view, enum request request)
2205         int i;
2207         if (request == REQ_NONE) {
2208                 doupdate();
2209                 return TRUE;
2210         }
2212         if (view && view->lines) {
2213                 request = view->ops->request(view, request, &view->line[view->lineno]);
2214                 if (request == REQ_NONE)
2215                         return TRUE;
2216         }
2218         switch (request) {
2219         case REQ_MOVE_UP:
2220         case REQ_MOVE_DOWN:
2221         case REQ_MOVE_PAGE_UP:
2222         case REQ_MOVE_PAGE_DOWN:
2223         case REQ_MOVE_FIRST_LINE:
2224         case REQ_MOVE_LAST_LINE:
2225                 move_view(view, request);
2226                 break;
2228         case REQ_SCROLL_LINE_DOWN:
2229         case REQ_SCROLL_LINE_UP:
2230         case REQ_SCROLL_PAGE_DOWN:
2231         case REQ_SCROLL_PAGE_UP:
2232                 scroll_view(view, request);
2233                 break;
2235         case REQ_VIEW_BLOB:
2236                 if (!ref_blob[0]) {
2237                         report("No file chosen, press %s to open tree view",
2238                                get_key(REQ_VIEW_TREE));
2239                         break;
2240                 }
2241                 open_view(view, request, OPEN_DEFAULT);
2242                 break;
2244         case REQ_VIEW_PAGER:
2245                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2246                         report("No pager content, press %s to run command from prompt",
2247                                get_key(REQ_PROMPT));
2248                         break;
2249                 }
2250                 open_view(view, request, OPEN_DEFAULT);
2251                 break;
2253         case REQ_VIEW_STAGE:
2254                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2255                         report("No stage content, press %s to open the status view and choose file",
2256                                get_key(REQ_VIEW_STATUS));
2257                         break;
2258                 }
2259                 open_view(view, request, OPEN_DEFAULT);
2260                 break;
2262         case REQ_VIEW_STATUS:
2263                 if (opt_is_inside_work_tree == FALSE) {
2264                         report("The status view requires a working tree");
2265                         break;
2266                 }
2267                 open_view(view, request, OPEN_DEFAULT);
2268                 break;
2270         case REQ_VIEW_MAIN:
2271         case REQ_VIEW_DIFF:
2272         case REQ_VIEW_LOG:
2273         case REQ_VIEW_TREE:
2274         case REQ_VIEW_HELP:
2275                 open_view(view, request, OPEN_DEFAULT);
2276                 break;
2278         case REQ_NEXT:
2279         case REQ_PREVIOUS:
2280                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2282                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2283                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2284                    (view == VIEW(REQ_VIEW_STAGE) &&
2285                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2286                    (view == VIEW(REQ_VIEW_BLOB) &&
2287                      view->parent == VIEW(REQ_VIEW_TREE))) {
2288                         int line;
2290                         view = view->parent;
2291                         line = view->lineno;
2292                         move_view(view, request);
2293                         if (view_is_displayed(view))
2294                                 update_view_title(view);
2295                         if (line != view->lineno)
2296                                 view->ops->request(view, REQ_ENTER,
2297                                                    &view->line[view->lineno]);
2299                 } else {
2300                         move_view(view, request);
2301                 }
2302                 break;
2304         case REQ_VIEW_NEXT:
2305         {
2306                 int nviews = displayed_views();
2307                 int next_view = (current_view + 1) % nviews;
2309                 if (next_view == current_view) {
2310                         report("Only one view is displayed");
2311                         break;
2312                 }
2314                 current_view = next_view;
2315                 /* Blur out the title of the previous view. */
2316                 update_view_title(view);
2317                 report("");
2318                 break;
2319         }
2320         case REQ_REFRESH:
2321                 report("Refreshing is not yet supported for the %s view", view->name);
2322                 break;
2324         case REQ_TOGGLE_LINENO:
2325                 opt_line_number = !opt_line_number;
2326                 redraw_display();
2327                 break;
2329         case REQ_TOGGLE_REV_GRAPH:
2330                 opt_rev_graph = !opt_rev_graph;
2331                 redraw_display();
2332                 break;
2334         case REQ_PROMPT:
2335                 /* Always reload^Wrerun commands from the prompt. */
2336                 open_view(view, opt_request, OPEN_RELOAD);
2337                 break;
2339         case REQ_SEARCH:
2340         case REQ_SEARCH_BACK:
2341                 search_view(view, request);
2342                 break;
2344         case REQ_FIND_NEXT:
2345         case REQ_FIND_PREV:
2346                 find_next(view, request);
2347                 break;
2349         case REQ_STOP_LOADING:
2350                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2351                         view = &views[i];
2352                         if (view->pipe)
2353                                 report("Stopped loading the %s view", view->name),
2354                         end_update(view);
2355                 }
2356                 break;
2358         case REQ_SHOW_VERSION:
2359                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2360                 return TRUE;
2362         case REQ_SCREEN_RESIZE:
2363                 resize_display();
2364                 /* Fall-through */
2365         case REQ_SCREEN_REDRAW:
2366                 redraw_display();
2367                 break;
2369         case REQ_EDIT:
2370                 report("Nothing to edit");
2371                 break;
2373         case REQ_CHERRY_PICK:
2374                 report("Nothing to cherry-pick");
2375                 break;
2377         case REQ_ENTER:
2378                 report("Nothing to enter");
2379                 break;
2382         case REQ_VIEW_CLOSE:
2383                 /* XXX: Mark closed views by letting view->parent point to the
2384                  * view itself. Parents to closed view should never be
2385                  * followed. */
2386                 if (view->parent &&
2387                     view->parent->parent != view->parent) {
2388                         memset(display, 0, sizeof(display));
2389                         current_view = 0;
2390                         display[current_view] = view->parent;
2391                         view->parent = view;
2392                         resize_display();
2393                         redraw_display();
2394                         break;
2395                 }
2396                 /* Fall-through */
2397         case REQ_QUIT:
2398                 return FALSE;
2400         default:
2401                 /* An unknown key will show most commonly used commands. */
2402                 report("Unknown key, press 'h' for help");
2403                 return TRUE;
2404         }
2406         return TRUE;
2410 /*
2411  * Pager backend
2412  */
2414 static bool
2415 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2417         char *text = line->data;
2418         enum line_type type = line->type;
2419         int textlen = strlen(text);
2420         int attr;
2422         wmove(view->win, lineno, 0);
2424         if (selected) {
2425                 type = LINE_CURSOR;
2426                 wchgat(view->win, -1, 0, type, NULL);
2427         }
2429         attr = get_line_attr(type);
2430         wattrset(view->win, attr);
2432         if (opt_line_number || opt_tab_size < TABSIZE) {
2433                 static char spaces[] = "                    ";
2434                 int col_offset = 0, col = 0;
2436                 if (opt_line_number) {
2437                         unsigned long real_lineno = view->offset + lineno + 1;
2439                         if (real_lineno == 1 ||
2440                             (real_lineno % opt_num_interval) == 0) {
2441                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2443                         } else {
2444                                 waddnstr(view->win, spaces,
2445                                          MIN(view->digits, STRING_SIZE(spaces)));
2446                         }
2447                         waddstr(view->win, ": ");
2448                         col_offset = view->digits + 2;
2449                 }
2451                 while (text && col_offset + col < view->width) {
2452                         int cols_max = view->width - col_offset - col;
2453                         char *pos = text;
2454                         int cols;
2456                         if (*text == '\t') {
2457                                 text++;
2458                                 assert(sizeof(spaces) > TABSIZE);
2459                                 pos = spaces;
2460                                 cols = opt_tab_size - (col % opt_tab_size);
2462                         } else {
2463                                 text = strchr(text, '\t');
2464                                 cols = line ? text - pos : strlen(pos);
2465                         }
2467                         waddnstr(view->win, pos, MIN(cols, cols_max));
2468                         col += cols;
2469                 }
2471         } else {
2472                 int col = 0, pos = 0;
2474                 for (; pos < textlen && col < view->width; pos++, col++)
2475                         if (text[pos] == '\t')
2476                                 col += TABSIZE - (col % TABSIZE) - 1;
2478                 waddnstr(view->win, text, pos);
2479         }
2481         return TRUE;
2484 static bool
2485 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2487         char refbuf[SIZEOF_STR];
2488         char *ref = NULL;
2489         FILE *pipe;
2491         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2492                 return TRUE;
2494         pipe = popen(refbuf, "r");
2495         if (!pipe)
2496                 return TRUE;
2498         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2499                 ref = chomp_string(ref);
2500         pclose(pipe);
2502         if (!ref || !*ref)
2503                 return TRUE;
2505         /* This is the only fatal call, since it can "corrupt" the buffer. */
2506         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2507                 return FALSE;
2509         return TRUE;
2512 static void
2513 add_pager_refs(struct view *view, struct line *line)
2515         char buf[SIZEOF_STR];
2516         char *commit_id = line->data + STRING_SIZE("commit ");
2517         struct ref **refs;
2518         size_t bufpos = 0, refpos = 0;
2519         const char *sep = "Refs: ";
2520         bool is_tag = FALSE;
2522         assert(line->type == LINE_COMMIT);
2524         refs = get_refs(commit_id);
2525         if (!refs) {
2526                 if (view == VIEW(REQ_VIEW_DIFF))
2527                         goto try_add_describe_ref;
2528                 return;
2529         }
2531         do {
2532                 struct ref *ref = refs[refpos];
2533                 char *fmt = ref->tag    ? "%s[%s]" :
2534                             ref->remote ? "%s<%s>" : "%s%s";
2536                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2537                         return;
2538                 sep = ", ";
2539                 if (ref->tag)
2540                         is_tag = TRUE;
2541         } while (refs[refpos++]->next);
2543         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2544 try_add_describe_ref:
2545                 /* Add <tag>-g<commit_id> "fake" reference. */
2546                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2547                         return;
2548         }
2550         if (bufpos == 0)
2551                 return;
2553         if (!realloc_lines(view, view->line_size + 1))
2554                 return;
2556         add_line_text(view, buf, LINE_PP_REFS);
2559 static bool
2560 pager_read(struct view *view, char *data)
2562         struct line *line;
2564         if (!data)
2565                 return TRUE;
2567         line = add_line_text(view, data, get_line_type(data));
2568         if (!line)
2569                 return FALSE;
2571         if (line->type == LINE_COMMIT &&
2572             (view == VIEW(REQ_VIEW_DIFF) ||
2573              view == VIEW(REQ_VIEW_LOG)))
2574                 add_pager_refs(view, line);
2576         return TRUE;
2579 static enum request
2580 pager_request(struct view *view, enum request request, struct line *line)
2582         int split = 0;
2584         if (request != REQ_ENTER)
2585                 return request;
2587         if (line->type == LINE_COMMIT &&
2588            (view == VIEW(REQ_VIEW_LOG) ||
2589             view == VIEW(REQ_VIEW_PAGER))) {
2590                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2591                 split = 1;
2592         }
2594         /* Always scroll the view even if it was split. That way
2595          * you can use Enter to scroll through the log view and
2596          * split open each commit diff. */
2597         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2599         /* FIXME: A minor workaround. Scrolling the view will call report("")
2600          * but if we are scrolling a non-current view this won't properly
2601          * update the view title. */
2602         if (split)
2603                 update_view_title(view);
2605         return REQ_NONE;
2608 static bool
2609 pager_grep(struct view *view, struct line *line)
2611         regmatch_t pmatch;
2612         char *text = line->data;
2614         if (!*text)
2615                 return FALSE;
2617         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2618                 return FALSE;
2620         return TRUE;
2623 static void
2624 pager_select(struct view *view, struct line *line)
2626         if (line->type == LINE_COMMIT) {
2627                 char *text = line->data + STRING_SIZE("commit ");
2629                 if (view != VIEW(REQ_VIEW_PAGER))
2630                         string_copy_rev(view->ref, text);
2631                 string_copy_rev(ref_commit, text);
2632         }
2635 static struct view_ops pager_ops = {
2636         "line",
2637         NULL,
2638         pager_read,
2639         pager_draw,
2640         pager_request,
2641         pager_grep,
2642         pager_select,
2643 };
2646 /*
2647  * Help backend
2648  */
2650 static bool
2651 help_open(struct view *view)
2653         char buf[BUFSIZ];
2654         int lines = ARRAY_SIZE(req_info) + 2;
2655         int i;
2657         if (view->lines > 0)
2658                 return TRUE;
2660         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2661                 if (!req_info[i].request)
2662                         lines++;
2664         view->line = calloc(lines, sizeof(*view->line));
2665         if (!view->line)
2666                 return FALSE;
2668         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2670         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2671                 char *key;
2673                 if (req_info[i].request == REQ_NONE)
2674                         continue;
2676                 if (!req_info[i].request) {
2677                         add_line_text(view, "", LINE_DEFAULT);
2678                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2679                         continue;
2680                 }
2682                 key = get_key(req_info[i].request);
2683                 if (!*key)
2684                         key = "(no key defined)";
2686                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2687                         continue;
2689                 add_line_text(view, buf, LINE_DEFAULT);
2690         }
2692         return TRUE;
2695 static struct view_ops help_ops = {
2696         "line",
2697         help_open,
2698         NULL,
2699         pager_draw,
2700         pager_request,
2701         pager_grep,
2702         pager_select,
2703 };
2706 /*
2707  * Tree backend
2708  */
2710 struct tree_stack_entry {
2711         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2712         unsigned long lineno;           /* Line number to restore */
2713         char *name;                     /* Position of name in opt_path */
2714 };
2716 /* The top of the path stack. */
2717 static struct tree_stack_entry *tree_stack = NULL;
2718 unsigned long tree_lineno = 0;
2720 static void
2721 pop_tree_stack_entry(void)
2723         struct tree_stack_entry *entry = tree_stack;
2725         tree_lineno = entry->lineno;
2726         entry->name[0] = 0;
2727         tree_stack = entry->prev;
2728         free(entry);
2731 static void
2732 push_tree_stack_entry(char *name, unsigned long lineno)
2734         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2735         size_t pathlen = strlen(opt_path);
2737         if (!entry)
2738                 return;
2740         entry->prev = tree_stack;
2741         entry->name = opt_path + pathlen;
2742         tree_stack = entry;
2744         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2745                 pop_tree_stack_entry();
2746                 return;
2747         }
2749         /* Move the current line to the first tree entry. */
2750         tree_lineno = 1;
2751         entry->lineno = lineno;
2754 /* Parse output from git-ls-tree(1):
2755  *
2756  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2757  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2758  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2759  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2760  */
2762 #define SIZEOF_TREE_ATTR \
2763         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2765 #define TREE_UP_FORMAT "040000 tree %s\t.."
2767 static int
2768 tree_compare_entry(enum line_type type1, char *name1,
2769                    enum line_type type2, char *name2)
2771         if (type1 != type2) {
2772                 if (type1 == LINE_TREE_DIR)
2773                         return -1;
2774                 return 1;
2775         }
2777         return strcmp(name1, name2);
2780 static bool
2781 tree_read(struct view *view, char *text)
2783         size_t textlen = text ? strlen(text) : 0;
2784         char buf[SIZEOF_STR];
2785         unsigned long pos;
2786         enum line_type type;
2787         bool first_read = view->lines == 0;
2789         if (textlen <= SIZEOF_TREE_ATTR)
2790                 return FALSE;
2792         type = text[STRING_SIZE("100644 ")] == 't'
2793              ? LINE_TREE_DIR : LINE_TREE_FILE;
2795         if (first_read) {
2796                 /* Add path info line */
2797                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2798                     !realloc_lines(view, view->line_size + 1) ||
2799                     !add_line_text(view, buf, LINE_DEFAULT))
2800                         return FALSE;
2802                 /* Insert "link" to parent directory. */
2803                 if (*opt_path) {
2804                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2805                             !realloc_lines(view, view->line_size + 1) ||
2806                             !add_line_text(view, buf, LINE_TREE_DIR))
2807                                 return FALSE;
2808                 }
2809         }
2811         /* Strip the path part ... */
2812         if (*opt_path) {
2813                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2814                 size_t striplen = strlen(opt_path);
2815                 char *path = text + SIZEOF_TREE_ATTR;
2817                 if (pathlen > striplen)
2818                         memmove(path, path + striplen,
2819                                 pathlen - striplen + 1);
2820         }
2822         /* Skip "Directory ..." and ".." line. */
2823         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2824                 struct line *line = &view->line[pos];
2825                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2826                 char *path2 = text + SIZEOF_TREE_ATTR;
2827                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2829                 if (cmp <= 0)
2830                         continue;
2832                 text = strdup(text);
2833                 if (!text)
2834                         return FALSE;
2836                 if (view->lines > pos)
2837                         memmove(&view->line[pos + 1], &view->line[pos],
2838                                 (view->lines - pos) * sizeof(*line));
2840                 line = &view->line[pos];
2841                 line->data = text;
2842                 line->type = type;
2843                 view->lines++;
2844                 return TRUE;
2845         }
2847         if (!add_line_text(view, text, type))
2848                 return FALSE;
2850         if (tree_lineno > view->lineno) {
2851                 view->lineno = tree_lineno;
2852                 tree_lineno = 0;
2853         }
2855         return TRUE;
2858 static enum request
2859 tree_request(struct view *view, enum request request, struct line *line)
2861         enum open_flags flags;
2863         if (request != REQ_ENTER)
2864                 return request;
2866         /* Cleanup the stack if the tree view is at a different tree. */
2867         while (!*opt_path && tree_stack)
2868                 pop_tree_stack_entry();
2870         switch (line->type) {
2871         case LINE_TREE_DIR:
2872                 /* Depending on whether it is a subdir or parent (updir?) link
2873                  * mangle the path buffer. */
2874                 if (line == &view->line[1] && *opt_path) {
2875                         pop_tree_stack_entry();
2877                 } else {
2878                         char *data = line->data;
2879                         char *basename = data + SIZEOF_TREE_ATTR;
2881                         push_tree_stack_entry(basename, view->lineno);
2882                 }
2884                 /* Trees and subtrees share the same ID, so they are not not
2885                  * unique like blobs. */
2886                 flags = OPEN_RELOAD;
2887                 request = REQ_VIEW_TREE;
2888                 break;
2890         case LINE_TREE_FILE:
2891                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2892                 request = REQ_VIEW_BLOB;
2893                 break;
2895         default:
2896                 return TRUE;
2897         }
2899         open_view(view, request, flags);
2900         if (request == REQ_VIEW_TREE) {
2901                 view->lineno = tree_lineno;
2902         }
2904         return REQ_NONE;
2907 static void
2908 tree_select(struct view *view, struct line *line)
2910         char *text = line->data + STRING_SIZE("100644 blob ");
2912         if (line->type == LINE_TREE_FILE) {
2913                 string_copy_rev(ref_blob, text);
2915         } else if (line->type != LINE_TREE_DIR) {
2916                 return;
2917         }
2919         string_copy_rev(view->ref, text);
2922 static struct view_ops tree_ops = {
2923         "file",
2924         NULL,
2925         tree_read,
2926         pager_draw,
2927         tree_request,
2928         pager_grep,
2929         tree_select,
2930 };
2932 static bool
2933 blob_read(struct view *view, char *line)
2935         return add_line_text(view, line, LINE_DEFAULT) != NULL;
2938 static struct view_ops blob_ops = {
2939         "line",
2940         NULL,
2941         blob_read,
2942         pager_draw,
2943         pager_request,
2944         pager_grep,
2945         pager_select,
2946 };
2949 /*
2950  * Status backend
2951  */
2953 struct status {
2954         char status;
2955         struct {
2956                 mode_t mode;
2957                 char rev[SIZEOF_REV];
2958         } old;
2959         struct {
2960                 mode_t mode;
2961                 char rev[SIZEOF_REV];
2962         } new;
2963         char name[SIZEOF_STR];
2964 };
2966 static struct status stage_status;
2967 static enum line_type stage_line_type;
2969 /* Get fields from the diff line:
2970  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2971  */
2972 static inline bool
2973 status_get_diff(struct status *file, char *buf, size_t bufsize)
2975         char *old_mode = buf +  1;
2976         char *new_mode = buf +  8;
2977         char *old_rev  = buf + 15;
2978         char *new_rev  = buf + 56;
2979         char *status   = buf + 97;
2981         if (bufsize != 99 ||
2982             old_mode[-1] != ':' ||
2983             new_mode[-1] != ' ' ||
2984             old_rev[-1]  != ' ' ||
2985             new_rev[-1]  != ' ' ||
2986             status[-1]   != ' ')
2987                 return FALSE;
2989         file->status = *status;
2991         string_copy_rev(file->old.rev, old_rev);
2992         string_copy_rev(file->new.rev, new_rev);
2994         file->old.mode = strtoul(old_mode, NULL, 8);
2995         file->new.mode = strtoul(new_mode, NULL, 8);
2997         file->name[0] = 0;
2999         return TRUE;
3002 static bool
3003 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3005         struct status *file = NULL;
3006         struct status *unmerged = NULL;
3007         char buf[SIZEOF_STR * 4];
3008         size_t bufsize = 0;
3009         FILE *pipe;
3011         pipe = popen(cmd, "r");
3012         if (!pipe)
3013                 return FALSE;
3015         add_line_data(view, NULL, type);
3017         while (!feof(pipe) && !ferror(pipe)) {
3018                 char *sep;
3019                 size_t readsize;
3021                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3022                 if (!readsize)
3023                         break;
3024                 bufsize += readsize;
3026                 /* Process while we have NUL chars. */
3027                 while ((sep = memchr(buf, 0, bufsize))) {
3028                         size_t sepsize = sep - buf + 1;
3030                         if (!file) {
3031                                 if (!realloc_lines(view, view->line_size + 1))
3032                                         goto error_out;
3034                                 file = calloc(1, sizeof(*file));
3035                                 if (!file)
3036                                         goto error_out;
3038                                 add_line_data(view, file, type);
3039                         }
3041                         /* Parse diff info part. */
3042                         if (!diff) {
3043                                 file->status = '?';
3045                         } else if (!file->status) {
3046                                 if (!status_get_diff(file, buf, sepsize))
3047                                         goto error_out;
3049                                 bufsize -= sepsize;
3050                                 memmove(buf, sep + 1, bufsize);
3052                                 sep = memchr(buf, 0, bufsize);
3053                                 if (!sep)
3054                                         break;
3055                                 sepsize = sep - buf + 1;
3057                                 /* Collapse all 'M'odified entries that
3058                                  * follow a associated 'U'nmerged entry.
3059                                  */
3060                                 if (file->status == 'U') {
3061                                         unmerged = file;
3063                                 } else if (unmerged) {
3064                                         int collapse = !strcmp(buf, unmerged->name);
3066                                         unmerged = NULL;
3067                                         if (collapse) {
3068                                                 free(file);
3069                                                 view->lines--;
3070                                                 continue;
3071                                         }
3072                                 }
3073                         }
3075                         /* git-ls-files just delivers a NUL separated
3076                          * list of file names similar to the second half
3077                          * of the git-diff-* output. */
3078                         string_ncopy(file->name, buf, sepsize);
3079                         bufsize -= sepsize;
3080                         memmove(buf, sep + 1, bufsize);
3081                         file = NULL;
3082                 }
3083         }
3085         if (ferror(pipe)) {
3086 error_out:
3087                 pclose(pipe);
3088                 return FALSE;
3089         }
3091         if (!view->line[view->lines - 1].data)
3092                 add_line_data(view, NULL, LINE_STAT_NONE);
3094         pclose(pipe);
3095         return TRUE;
3098 /* Don't show unmerged entries in the staged section. */
3099 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3100 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3101 #define STATUS_LIST_OTHER_CMD \
3102         "git ls-files -z --others --exclude-per-directory=.gitignore"
3104 #define STATUS_DIFF_SHOW_CMD \
3105         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3107 /* First parse staged info using git-diff-index(1), then parse unstaged
3108  * info using git-diff-files(1), and finally untracked files using
3109  * git-ls-files(1). */
3110 static bool
3111 status_open(struct view *view)
3113         struct stat statbuf;
3114         char exclude[SIZEOF_STR];
3115         char cmd[SIZEOF_STR];
3116         unsigned long prev_lineno = view->lineno;
3117         size_t i;
3119         for (i = 0; i < view->lines; i++)
3120                 free(view->line[i].data);
3121         free(view->line);
3122         view->lines = view->line_size = view->lineno = 0;
3123         view->line = NULL;
3125         if (!realloc_lines(view, view->line_size + 6))
3126                 return FALSE;
3128         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3129                 return FALSE;
3131         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3133         if (stat(exclude, &statbuf) >= 0) {
3134                 size_t cmdsize = strlen(cmd);
3136                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3137                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3138                         return FALSE;
3139         }
3141         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3142             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3143             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3144                 return FALSE;
3146         /* If all went well restore the previous line number to stay in
3147          * the context. */
3148         if (prev_lineno < view->lines)
3149                 view->lineno = prev_lineno;
3150         else
3151                 view->lineno = view->lines - 1;
3153         return TRUE;
3156 static bool
3157 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3159         struct status *status = line->data;
3161         wmove(view->win, lineno, 0);
3163         if (selected) {
3164                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3165                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3167         } else if (!status && line->type != LINE_STAT_NONE) {
3168                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3169                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3171         } else {
3172                 wattrset(view->win, get_line_attr(line->type));
3173         }
3175         if (!status) {
3176                 char *text;
3178                 switch (line->type) {
3179                 case LINE_STAT_STAGED:
3180                         text = "Changes to be committed:";
3181                         break;
3183                 case LINE_STAT_UNSTAGED:
3184                         text = "Changed but not updated:";
3185                         break;
3187                 case LINE_STAT_UNTRACKED:
3188                         text = "Untracked files:";
3189                         break;
3191                 case LINE_STAT_NONE:
3192                         text = "    (no files)";
3193                         break;
3195                 default:
3196                         return FALSE;
3197                 }
3199                 waddstr(view->win, text);
3200                 return TRUE;
3201         }
3203         waddch(view->win, status->status);
3204         if (!selected)
3205                 wattrset(view->win, A_NORMAL);
3206         wmove(view->win, lineno, 4);
3207         waddstr(view->win, status->name);
3209         return TRUE;
3212 static enum request
3213 status_enter(struct view *view, struct line *line)
3215         struct status *status = line->data;
3216         char path[SIZEOF_STR] = "";
3217         char *info;
3218         size_t cmdsize = 0;
3220         if (line->type == LINE_STAT_NONE ||
3221             (!status && line[1].type == LINE_STAT_NONE)) {
3222                 report("No file to diff");
3223                 return REQ_NONE;
3224         }
3226         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3227                 return REQ_QUIT;
3229         if (opt_cdup[0] &&
3230             line->type != LINE_STAT_UNTRACKED &&
3231             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3232                 return REQ_QUIT;
3234         switch (line->type) {
3235         case LINE_STAT_STAGED:
3236                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3237                                         "--cached", path))
3238                         return REQ_QUIT;
3239                 if (status)
3240                         info = "Staged changes to %s";
3241                 else
3242                         info = "Staged changes";
3243                 break;
3245         case LINE_STAT_UNSTAGED:
3246                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3247                                         "", path))
3248                         return REQ_QUIT;
3249                 if (status)
3250                         info = "Unstaged changes to %s";
3251                 else
3252                         info = "Unstaged changes";
3253                 break;
3255         case LINE_STAT_UNTRACKED:
3256                 if (opt_pipe)
3257                         return REQ_QUIT;
3260                 if (!status) {
3261                         report("No file to show");
3262                         return REQ_NONE;
3263                 }
3265                 opt_pipe = fopen(status->name, "r");
3266                 info = "Untracked file %s";
3267                 break;
3269         default:
3270                 die("w00t");
3271         }
3273         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3274         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3275                 if (status) {
3276                         stage_status = *status;
3277                 } else {
3278                         memset(&stage_status, 0, sizeof(stage_status));
3279                 }
3281                 stage_line_type = line->type;
3282                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3283         }
3285         return REQ_NONE;
3289 static bool
3290 status_update_file(struct view *view, struct status *status, enum line_type type)
3292         char cmd[SIZEOF_STR];
3293         char buf[SIZEOF_STR];
3294         size_t cmdsize = 0;
3295         size_t bufsize = 0;
3296         size_t written = 0;
3297         FILE *pipe;
3299         if (opt_cdup[0] &&
3300             type != LINE_STAT_UNTRACKED &&
3301             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3302                 return FALSE;
3304         switch (type) {
3305         case LINE_STAT_STAGED:
3306                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3307                                         status->old.mode,
3308                                         status->old.rev,
3309                                         status->name, 0))
3310                         return FALSE;
3312                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3313                 break;
3315         case LINE_STAT_UNSTAGED:
3316         case LINE_STAT_UNTRACKED:
3317                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3318                         return FALSE;
3320                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3321                 break;
3323         default:
3324                 die("w00t");
3325         }
3327         pipe = popen(cmd, "w");
3328         if (!pipe)
3329                 return FALSE;
3331         while (!ferror(pipe) && written < bufsize) {
3332                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3333         }
3335         pclose(pipe);
3337         if (written != bufsize)
3338                 return FALSE;
3340         return TRUE;
3343 static void
3344 status_update(struct view *view)
3346         struct line *line = &view->line[view->lineno];
3348         assert(view->lines);
3350         if (!line->data) {
3351                 while (++line < view->line + view->lines && line->data) {
3352                         if (!status_update_file(view, line->data, line->type))
3353                                 report("Failed to update file status");
3354                 }
3356                 if (!line[-1].data) {
3357                         report("Nothing to update");
3358                         return;
3359                 }
3361         } else if (!status_update_file(view, line->data, line->type)) {
3362                 report("Failed to update file status");
3363         }
3365         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3368 static enum request
3369 status_request(struct view *view, enum request request, struct line *line)
3371         struct status *status = line->data;
3373         switch (request) {
3374         case REQ_STATUS_UPDATE:
3375                 status_update(view);
3376                 break;
3378         case REQ_STATUS_MERGE:
3379                 open_mergetool(status->name);
3380                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3381                 break;
3383         case REQ_EDIT:
3384                 if (!status)
3385                         return request;
3387                 open_editor(status->status != '?', status->name);
3388                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3389                 break;
3391         case REQ_ENTER:
3392                 status_enter(view, line);
3393                 break;
3395         case REQ_REFRESH:
3396                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3397                 break;
3399         default:
3400                 return request;
3401         }
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;