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