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