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