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