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