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