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