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