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