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