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