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