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