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