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