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