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