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