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