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