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