draw_text: minor cleanup to use fewer local variables
[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
1465         if (max_len > 0) {
1466                 int trimmed = FALSE;
1467
1468                 if (opt_utf8) {
1469                         len = utf8_length(string, max_len, &trimmed, use_tilde);
1470                 } else {
1471                         len = strlen(string);
1472                         if (len > max_len) {
1473                                 if (use_tilde) {
1474                                         max_len -= 1;
1475                                 }
1476                                 len = max_len;
1477                                 trimmed = TRUE;
1478                         }
1479                 }
1480                 waddnstr(view->win, string, len);
1481                 if (trimmed && use_tilde) {
1482                         if (tilde_attr != -1)
1483                                 wattrset(view->win, tilde_attr);
1484                         waddch(view->win, '~');
1485                         len++;
1486                 }
1487         }
1488
1489         return len;
1490 }
1491
1492 static bool
1493 draw_view_line(struct view *view, unsigned int lineno)
1494 {
1495         struct line *line;
1496         bool selected = (view->offset + lineno == view->lineno);
1497         bool draw_ok;
1498
1499         assert(view_is_displayed(view));
1500
1501         if (view->offset + lineno >= view->lines)
1502                 return FALSE;
1503
1504         line = &view->line[view->offset + lineno];
1505
1506         if (selected) {
1507                 line->selected = TRUE;
1508                 view->ops->select(view, line);
1509         } else if (line->selected) {
1510                 line->selected = FALSE;
1511                 wmove(view->win, lineno, 0);
1512                 wclrtoeol(view->win);
1513         }
1514
1515         scrollok(view->win, FALSE);
1516         draw_ok = view->ops->draw(view, line, lineno, selected);
1517         scrollok(view->win, TRUE);
1518
1519         return draw_ok;
1520 }
1521
1522 static void
1523 redraw_view_from(struct view *view, int lineno)
1524 {
1525         assert(0 <= lineno && lineno < view->height);
1526
1527         for (; lineno < view->height; lineno++) {
1528                 if (!draw_view_line(view, lineno))
1529                         break;
1530         }
1531
1532         redrawwin(view->win);
1533         if (input_mode)
1534                 wnoutrefresh(view->win);
1535         else
1536                 wrefresh(view->win);
1537 }
1538
1539 static void
1540 redraw_view(struct view *view)
1541 {
1542         wclear(view->win);
1543         redraw_view_from(view, 0);
1544 }
1545
1546
1547 static void
1548 update_view_title(struct view *view)
1549 {
1550         char buf[SIZEOF_STR];
1551         char state[SIZEOF_STR];
1552         size_t bufpos = 0, statelen = 0;
1553
1554         assert(view_is_displayed(view));
1555
1556         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1557                 unsigned int view_lines = view->offset + view->height;
1558                 unsigned int lines = view->lines
1559                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1560                                    : 0;
1561
1562                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1563                                    view->ops->type,
1564                                    view->lineno + 1,
1565                                    view->lines,
1566                                    lines);
1567
1568                 if (view->pipe) {
1569                         time_t secs = time(NULL) - view->start_time;
1570
1571                         /* Three git seconds are a long time ... */
1572                         if (secs > 2)
1573                                 string_format_from(state, &statelen, " %lds", secs);
1574                 }
1575         }
1576
1577         string_format_from(buf, &bufpos, "[%s]", view->name);
1578         if (*view->ref && bufpos < view->width) {
1579                 size_t refsize = strlen(view->ref);
1580                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1581
1582                 if (minsize < view->width)
1583                         refsize = view->width - minsize + 7;
1584                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1585         }
1586
1587         if (statelen && bufpos < view->width) {
1588                 string_format_from(buf, &bufpos, " %s", state);
1589         }
1590
1591         if (view == display[current_view])
1592                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1593         else
1594                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1595
1596         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1597         wclrtoeol(view->title);
1598         wmove(view->title, 0, view->width - 1);
1599
1600         if (input_mode)
1601                 wnoutrefresh(view->title);
1602         else
1603                 wrefresh(view->title);
1604 }
1605
1606 static void
1607 resize_display(void)
1608 {
1609         int offset, i;
1610         struct view *base = display[0];
1611         struct view *view = display[1] ? display[1] : display[0];
1612
1613         /* Setup window dimensions */
1614
1615         getmaxyx(stdscr, base->height, base->width);
1616
1617         /* Make room for the status window. */
1618         base->height -= 1;
1619
1620         if (view != base) {
1621                 /* Horizontal split. */
1622                 view->width   = base->width;
1623                 view->height  = SCALE_SPLIT_VIEW(base->height);
1624                 base->height -= view->height;
1625
1626                 /* Make room for the title bar. */
1627                 view->height -= 1;
1628         }
1629
1630         /* Make room for the title bar. */
1631         base->height -= 1;
1632
1633         offset = 0;
1634
1635         foreach_displayed_view (view, i) {
1636                 if (!view->win) {
1637                         view->win = newwin(view->height, 0, offset, 0);
1638                         if (!view->win)
1639                                 die("Failed to create %s view", view->name);
1640
1641                         scrollok(view->win, TRUE);
1642
1643                         view->title = newwin(1, 0, offset + view->height, 0);
1644                         if (!view->title)
1645                                 die("Failed to create title window");
1646
1647                 } else {
1648                         wresize(view->win, view->height, view->width);
1649                         mvwin(view->win,   offset, 0);
1650                         mvwin(view->title, offset + view->height, 0);
1651                 }
1652
1653                 offset += view->height + 1;
1654         }
1655 }
1656
1657 static void
1658 redraw_display(void)
1659 {
1660         struct view *view;
1661         int i;
1662
1663         foreach_displayed_view (view, i) {
1664                 redraw_view(view);
1665                 update_view_title(view);
1666         }
1667 }
1668
1669 static void
1670 update_display_cursor(struct view *view)
1671 {
1672         /* Move the cursor to the right-most column of the cursor line.
1673          *
1674          * XXX: This could turn out to be a bit expensive, but it ensures that
1675          * the cursor does not jump around. */
1676         if (view->lines) {
1677                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1678                 wrefresh(view->win);
1679         }
1680 }
1681
1682 /*
1683  * Navigation
1684  */
1685
1686 /* Scrolling backend */
1687 static void
1688 do_scroll_view(struct view *view, int lines)
1689 {
1690         bool redraw_current_line = FALSE;
1691
1692         /* The rendering expects the new offset. */
1693         view->offset += lines;
1694
1695         assert(0 <= view->offset && view->offset < view->lines);
1696         assert(lines);
1697
1698         /* Move current line into the view. */
1699         if (view->lineno < view->offset) {
1700                 view->lineno = view->offset;
1701                 redraw_current_line = TRUE;
1702         } else if (view->lineno >= view->offset + view->height) {
1703                 view->lineno = view->offset + view->height - 1;
1704                 redraw_current_line = TRUE;
1705         }
1706
1707         assert(view->offset <= view->lineno && view->lineno < view->lines);
1708
1709         /* Redraw the whole screen if scrolling is pointless. */
1710         if (view->height < ABS(lines)) {
1711                 redraw_view(view);
1712
1713         } else {
1714                 int line = lines > 0 ? view->height - lines : 0;
1715                 int end = line + ABS(lines);
1716
1717                 wscrl(view->win, lines);
1718
1719                 for (; line < end; line++) {
1720                         if (!draw_view_line(view, line))
1721                                 break;
1722                 }
1723
1724                 if (redraw_current_line)
1725                         draw_view_line(view, view->lineno - view->offset);
1726         }
1727
1728         redrawwin(view->win);
1729         wrefresh(view->win);
1730         report("");
1731 }
1732
1733 /* Scroll frontend */
1734 static void
1735 scroll_view(struct view *view, enum request request)
1736 {
1737         int lines = 1;
1738
1739         assert(view_is_displayed(view));
1740
1741         switch (request) {
1742         case REQ_SCROLL_PAGE_DOWN:
1743                 lines = view->height;
1744         case REQ_SCROLL_LINE_DOWN:
1745                 if (view->offset + lines > view->lines)
1746                         lines = view->lines - view->offset;
1747
1748                 if (lines == 0 || view->offset + view->height >= view->lines) {
1749                         report("Cannot scroll beyond the last line");
1750                         return;
1751                 }
1752                 break;
1753
1754         case REQ_SCROLL_PAGE_UP:
1755                 lines = view->height;
1756         case REQ_SCROLL_LINE_UP:
1757                 if (lines > view->offset)
1758                         lines = view->offset;
1759
1760                 if (lines == 0) {
1761                         report("Cannot scroll beyond the first line");
1762                         return;
1763                 }
1764
1765                 lines = -lines;
1766                 break;
1767
1768         default:
1769                 die("request %d not handled in switch", request);
1770         }
1771
1772         do_scroll_view(view, lines);
1773 }
1774
1775 /* Cursor moving */
1776 static void
1777 move_view(struct view *view, enum request request)
1778 {
1779         int scroll_steps = 0;
1780         int steps;
1781
1782         switch (request) {
1783         case REQ_MOVE_FIRST_LINE:
1784                 steps = -view->lineno;
1785                 break;
1786
1787         case REQ_MOVE_LAST_LINE:
1788                 steps = view->lines - view->lineno - 1;
1789                 break;
1790
1791         case REQ_MOVE_PAGE_UP:
1792                 steps = view->height > view->lineno
1793                       ? -view->lineno : -view->height;
1794                 break;
1795
1796         case REQ_MOVE_PAGE_DOWN:
1797                 steps = view->lineno + view->height >= view->lines
1798                       ? view->lines - view->lineno - 1 : view->height;
1799                 break;
1800
1801         case REQ_MOVE_UP:
1802                 steps = -1;
1803                 break;
1804
1805         case REQ_MOVE_DOWN:
1806                 steps = 1;
1807                 break;
1808
1809         default:
1810                 die("request %d not handled in switch", request);
1811         }
1812
1813         if (steps <= 0 && view->lineno == 0) {
1814                 report("Cannot move beyond the first line");
1815                 return;
1816
1817         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1818                 report("Cannot move beyond the last line");
1819                 return;
1820         }
1821
1822         /* Move the current line */
1823         view->lineno += steps;
1824         assert(0 <= view->lineno && view->lineno < view->lines);
1825
1826         /* Check whether the view needs to be scrolled */
1827         if (view->lineno < view->offset ||
1828             view->lineno >= view->offset + view->height) {
1829                 scroll_steps = steps;
1830                 if (steps < 0 && -steps > view->offset) {
1831                         scroll_steps = -view->offset;
1832
1833                 } else if (steps > 0) {
1834                         if (view->lineno == view->lines - 1 &&
1835                             view->lines > view->height) {
1836                                 scroll_steps = view->lines - view->offset - 1;
1837                                 if (scroll_steps >= view->height)
1838                                         scroll_steps -= view->height - 1;
1839                         }
1840                 }
1841         }
1842
1843         if (!view_is_displayed(view)) {
1844                 view->offset += scroll_steps;
1845                 assert(0 <= view->offset && view->offset < view->lines);
1846                 view->ops->select(view, &view->line[view->lineno]);
1847                 return;
1848         }
1849
1850         /* Repaint the old "current" line if we be scrolling */
1851         if (ABS(steps) < view->height)
1852                 draw_view_line(view, view->lineno - steps - view->offset);
1853
1854         if (scroll_steps) {
1855                 do_scroll_view(view, scroll_steps);
1856                 return;
1857         }
1858
1859         /* Draw the current line */
1860         draw_view_line(view, view->lineno - view->offset);
1861
1862         redrawwin(view->win);
1863         wrefresh(view->win);
1864         report("");
1865 }
1866
1867
1868 /*
1869  * Searching
1870  */
1871
1872 static void search_view(struct view *view, enum request request);
1873
1874 static bool
1875 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1876 {
1877         assert(view_is_displayed(view));
1878
1879         if (!view->ops->grep(view, line))
1880                 return FALSE;
1881
1882         if (lineno - view->offset >= view->height) {
1883                 view->offset = lineno;
1884                 view->lineno = lineno;
1885                 redraw_view(view);
1886
1887         } else {
1888                 unsigned long old_lineno = view->lineno - view->offset;
1889
1890                 view->lineno = lineno;
1891                 draw_view_line(view, old_lineno);
1892
1893                 draw_view_line(view, view->lineno - view->offset);
1894                 redrawwin(view->win);
1895                 wrefresh(view->win);
1896         }
1897
1898         report("Line %ld matches '%s'", lineno + 1, view->grep);
1899         return TRUE;
1900 }
1901
1902 static void
1903 find_next(struct view *view, enum request request)
1904 {
1905         unsigned long lineno = view->lineno;
1906         int direction;
1907
1908         if (!*view->grep) {
1909                 if (!*opt_search)
1910                         report("No previous search");
1911                 else
1912                         search_view(view, request);
1913                 return;
1914         }
1915
1916         switch (request) {
1917         case REQ_SEARCH:
1918         case REQ_FIND_NEXT:
1919                 direction = 1;
1920                 break;
1921
1922         case REQ_SEARCH_BACK:
1923         case REQ_FIND_PREV:
1924                 direction = -1;
1925                 break;
1926
1927         default:
1928                 return;
1929         }
1930
1931         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1932                 lineno += direction;
1933
1934         /* Note, lineno is unsigned long so will wrap around in which case it
1935          * will become bigger than view->lines. */
1936         for (; lineno < view->lines; lineno += direction) {
1937                 struct line *line = &view->line[lineno];
1938
1939                 if (find_next_line(view, lineno, line))
1940                         return;
1941         }
1942
1943         report("No match found for '%s'", view->grep);
1944 }
1945
1946 static void
1947 search_view(struct view *view, enum request request)
1948 {
1949         int regex_err;
1950
1951         if (view->regex) {
1952                 regfree(view->regex);
1953                 *view->grep = 0;
1954         } else {
1955                 view->regex = calloc(1, sizeof(*view->regex));
1956                 if (!view->regex)
1957                         return;
1958         }
1959
1960         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1961         if (regex_err != 0) {
1962                 char buf[SIZEOF_STR] = "unknown error";
1963
1964                 regerror(regex_err, view->regex, buf, sizeof(buf));
1965                 report("Search failed: %s", buf);
1966                 return;
1967         }
1968
1969         string_copy(view->grep, opt_search);
1970
1971         find_next(view, request);
1972 }
1973
1974 /*
1975  * Incremental updating
1976  */
1977
1978 static void
1979 end_update(struct view *view)
1980 {
1981         if (!view->pipe)
1982                 return;
1983         set_nonblocking_input(FALSE);
1984         if (view->pipe == stdin)
1985                 fclose(view->pipe);
1986         else
1987                 pclose(view->pipe);
1988         view->pipe = NULL;
1989 }
1990
1991 static bool
1992 begin_update(struct view *view)
1993 {
1994         if (view->pipe)
1995                 end_update(view);
1996
1997         if (opt_cmd[0]) {
1998                 string_copy(view->cmd, opt_cmd);
1999                 opt_cmd[0] = 0;
2000                 /* When running random commands, initially show the
2001                  * command in the title. However, it maybe later be
2002                  * overwritten if a commit line is selected. */
2003                 if (view == VIEW(REQ_VIEW_PAGER))
2004                         string_copy(view->ref, view->cmd);
2005                 else
2006                         view->ref[0] = 0;
2007
2008         } else if (view == VIEW(REQ_VIEW_TREE)) {
2009                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2010                 char path[SIZEOF_STR];
2011
2012                 if (strcmp(view->vid, view->id))
2013                         opt_path[0] = path[0] = 0;
2014                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2015                         return FALSE;
2016
2017                 if (!string_format(view->cmd, format, view->id, path))
2018                         return FALSE;
2019
2020         } else {
2021                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2022                 const char *id = view->id;
2023
2024                 if (!string_format(view->cmd, format, id, id, id, id, id))
2025                         return FALSE;
2026
2027                 /* Put the current ref_* value to the view title ref
2028                  * member. This is needed by the blob view. Most other
2029                  * views sets it automatically after loading because the
2030                  * first line is a commit line. */
2031                 string_copy_rev(view->ref, view->id);
2032         }
2033
2034         /* Special case for the pager view. */
2035         if (opt_pipe) {
2036                 view->pipe = opt_pipe;
2037                 opt_pipe = NULL;
2038         } else {
2039                 view->pipe = popen(view->cmd, "r");
2040         }
2041
2042         if (!view->pipe)
2043                 return FALSE;
2044
2045         set_nonblocking_input(TRUE);
2046
2047         view->offset = 0;
2048         view->lines  = 0;
2049         view->lineno = 0;
2050         string_copy_rev(view->vid, view->id);
2051
2052         if (view->line) {
2053                 int i;
2054
2055                 for (i = 0; i < view->lines; i++)
2056                         if (view->line[i].data)
2057                                 free(view->line[i].data);
2058
2059                 free(view->line);
2060                 view->line = NULL;
2061         }
2062
2063         view->start_time = time(NULL);
2064
2065         return TRUE;
2066 }
2067
2068 static struct line *
2069 realloc_lines(struct view *view, size_t line_size)
2070 {
2071         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2072
2073         if (!tmp)
2074                 return NULL;
2075
2076         view->line = tmp;
2077         view->line_size = line_size;
2078         return view->line;
2079 }
2080
2081 static bool
2082 update_view(struct view *view)
2083 {
2084         char in_buffer[BUFSIZ];
2085         char out_buffer[BUFSIZ * 2];
2086         char *line;
2087         /* The number of lines to read. If too low it will cause too much
2088          * redrawing (and possible flickering), if too high responsiveness
2089          * will suffer. */
2090         unsigned long lines = view->height;
2091         int redraw_from = -1;
2092
2093         if (!view->pipe)
2094                 return TRUE;
2095
2096         /* Only redraw if lines are visible. */
2097         if (view->offset + view->height >= view->lines)
2098                 redraw_from = view->lines - view->offset;
2099
2100         /* FIXME: This is probably not perfect for backgrounded views. */
2101         if (!realloc_lines(view, view->lines + lines))
2102                 goto alloc_error;
2103
2104         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2105                 size_t linelen = strlen(line);
2106
2107                 if (linelen)
2108                         line[linelen - 1] = 0;
2109
2110                 if (opt_iconv != ICONV_NONE) {
2111                         ICONV_CONST char *inbuf = line;
2112                         size_t inlen = linelen;
2113
2114                         char *outbuf = out_buffer;
2115                         size_t outlen = sizeof(out_buffer);
2116
2117                         size_t ret;
2118
2119                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2120                         if (ret != (size_t) -1) {
2121                                 line = out_buffer;
2122                                 linelen = strlen(out_buffer);
2123                         }
2124                 }
2125
2126                 if (!view->ops->read(view, line))
2127                         goto alloc_error;
2128
2129                 if (lines-- == 1)
2130                         break;
2131         }
2132
2133         {
2134                 int digits;
2135
2136                 lines = view->lines;
2137                 for (digits = 0; lines; digits++)
2138                         lines /= 10;
2139
2140                 /* Keep the displayed view in sync with line number scaling. */
2141                 if (digits != view->digits) {
2142                         view->digits = digits;
2143                         redraw_from = 0;
2144                 }
2145         }
2146
2147         if (!view_is_displayed(view))
2148                 goto check_pipe;
2149
2150         if (view == VIEW(REQ_VIEW_TREE)) {
2151                 /* Clear the view and redraw everything since the tree sorting
2152                  * might have rearranged things. */
2153                 redraw_view(view);
2154
2155         } else if (redraw_from >= 0) {
2156                 /* If this is an incremental update, redraw the previous line
2157                  * since for commits some members could have changed when
2158                  * loading the main view. */
2159                 if (redraw_from > 0)
2160                         redraw_from--;
2161
2162                 /* Since revision graph visualization requires knowledge
2163                  * about the parent commit, it causes a further one-off
2164                  * needed to be redrawn for incremental updates. */
2165                 if (redraw_from > 0 && opt_rev_graph)
2166                         redraw_from--;
2167
2168                 /* Incrementally draw avoids flickering. */
2169                 redraw_view_from(view, redraw_from);
2170         }
2171
2172         /* Update the title _after_ the redraw so that if the redraw picks up a
2173          * commit reference in view->ref it'll be available here. */
2174         update_view_title(view);
2175
2176 check_pipe:
2177         if (ferror(view->pipe)) {
2178                 report("Failed to read: %s", strerror(errno));
2179                 goto end;
2180
2181         } else if (feof(view->pipe)) {
2182                 report("");
2183                 goto end;
2184         }
2185
2186         return TRUE;
2187
2188 alloc_error:
2189         report("Allocation failure");
2190
2191 end:
2192         view->ops->read(view, NULL);
2193         end_update(view);
2194         return FALSE;
2195 }
2196
2197 static struct line *
2198 add_line_data(struct view *view, void *data, enum line_type type)
2199 {
2200         struct line *line = &view->line[view->lines++];
2201
2202         memset(line, 0, sizeof(*line));
2203         line->type = type;
2204         line->data = data;
2205
2206         return line;
2207 }
2208
2209 static struct line *
2210 add_line_text(struct view *view, char *data, enum line_type type)
2211 {
2212         if (data)
2213                 data = strdup(data);
2214
2215         return data ? add_line_data(view, data, type) : NULL;
2216 }
2217
2218
2219 /*
2220  * View opening
2221  */
2222
2223 enum open_flags {
2224         OPEN_DEFAULT = 0,       /* Use default view switching. */
2225         OPEN_SPLIT = 1,         /* Split current view. */
2226         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2227         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2228 };
2229
2230 static void
2231 open_view(struct view *prev, enum request request, enum open_flags flags)
2232 {
2233         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2234         bool split = !!(flags & OPEN_SPLIT);
2235         bool reload = !!(flags & OPEN_RELOAD);
2236         struct view *view = VIEW(request);
2237         int nviews = displayed_views();
2238         struct view *base_view = display[0];
2239
2240         if (view == prev && nviews == 1 && !reload) {
2241                 report("Already in %s view", view->name);
2242                 return;
2243         }
2244
2245         if (view->ops->open) {
2246                 if (!view->ops->open(view)) {
2247                         report("Failed to load %s view", view->name);
2248                         return;
2249                 }
2250
2251         } else if ((reload || strcmp(view->vid, view->id)) &&
2252                    !begin_update(view)) {
2253                 report("Failed to load %s view", view->name);
2254                 return;
2255         }
2256
2257         if (split) {
2258                 display[1] = view;
2259                 if (!backgrounded)
2260                         current_view = 1;
2261         } else {
2262                 /* Maximize the current view. */
2263                 memset(display, 0, sizeof(display));
2264                 current_view = 0;
2265                 display[current_view] = view;
2266         }
2267
2268         /* Resize the view when switching between split- and full-screen,
2269          * or when switching between two different full-screen views. */
2270         if (nviews != displayed_views() ||
2271             (nviews == 1 && base_view != display[0]))
2272                 resize_display();
2273
2274         if (split && prev->lineno - prev->offset >= prev->height) {
2275                 /* Take the title line into account. */
2276                 int lines = prev->lineno - prev->offset - prev->height + 1;
2277
2278                 /* Scroll the view that was split if the current line is
2279                  * outside the new limited view. */
2280                 do_scroll_view(prev, lines);
2281         }
2282
2283         if (prev && view != prev) {
2284                 if (split && !backgrounded) {
2285                         /* "Blur" the previous view. */
2286                         update_view_title(prev);
2287                 }
2288
2289                 view->parent = prev;
2290         }
2291
2292         if (view->pipe && view->lines == 0) {
2293                 /* Clear the old view and let the incremental updating refill
2294                  * the screen. */
2295                 wclear(view->win);
2296                 report("");
2297         } else {
2298                 redraw_view(view);
2299                 report("");
2300         }
2301
2302         /* If the view is backgrounded the above calls to report()
2303          * won't redraw the view title. */
2304         if (backgrounded)
2305                 update_view_title(view);
2306 }
2307
2308 static void
2309 open_external_viewer(const char *cmd)
2310 {
2311         def_prog_mode();           /* save current tty modes */
2312         endwin();                  /* restore original tty modes */
2313         system(cmd);
2314         fprintf(stderr, "Press Enter to continue");
2315         getc(stdin);
2316         reset_prog_mode();
2317         redraw_display();
2318 }
2319
2320 static void
2321 open_mergetool(const char *file)
2322 {
2323         char cmd[SIZEOF_STR];
2324         char file_sq[SIZEOF_STR];
2325
2326         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2327             string_format(cmd, "git mergetool %s", file_sq)) {
2328                 open_external_viewer(cmd);
2329         }
2330 }
2331
2332 static void
2333 open_editor(bool from_root, const char *file)
2334 {
2335         char cmd[SIZEOF_STR];
2336         char file_sq[SIZEOF_STR];
2337         char *editor;
2338         char *prefix = from_root ? opt_cdup : "";
2339
2340         editor = getenv("GIT_EDITOR");
2341         if (!editor && *opt_editor)
2342                 editor = opt_editor;
2343         if (!editor)
2344                 editor = getenv("VISUAL");
2345         if (!editor)
2346                 editor = getenv("EDITOR");
2347         if (!editor)
2348                 editor = "vi";
2349
2350         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2351             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2352                 open_external_viewer(cmd);
2353         }
2354 }
2355
2356 static void
2357 open_run_request(enum request request)
2358 {
2359         struct run_request *req = get_run_request(request);
2360         char buf[SIZEOF_STR * 2];
2361         size_t bufpos;
2362         char *cmd;
2363
2364         if (!req) {
2365                 report("Unknown run request");
2366                 return;
2367         }
2368
2369         bufpos = 0;
2370         cmd = req->cmd;
2371
2372         while (cmd) {
2373                 char *next = strstr(cmd, "%(");
2374                 int len = next - cmd;
2375                 char *value;
2376
2377                 if (!next) {
2378                         len = strlen(cmd);
2379                         value = "";
2380
2381                 } else if (!strncmp(next, "%(head)", 7)) {
2382                         value = ref_head;
2383
2384                 } else if (!strncmp(next, "%(commit)", 9)) {
2385                         value = ref_commit;
2386
2387                 } else if (!strncmp(next, "%(blob)", 7)) {
2388                         value = ref_blob;
2389
2390                 } else {
2391                         report("Unknown replacement in run request: `%s`", req->cmd);
2392                         return;
2393                 }
2394
2395                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2396                         return;
2397
2398                 if (next)
2399                         next = strchr(next, ')') + 1;
2400                 cmd = next;
2401         }
2402
2403         open_external_viewer(buf);
2404 }
2405
2406 /*
2407  * User request switch noodle
2408  */
2409
2410 static int
2411 view_driver(struct view *view, enum request request)
2412 {
2413         int i;
2414
2415         if (request == REQ_NONE) {
2416                 doupdate();
2417                 return TRUE;
2418         }
2419
2420         if (request > REQ_NONE) {
2421                 open_run_request(request);
2422                 return TRUE;
2423         }
2424
2425         if (view && view->lines) {
2426                 request = view->ops->request(view, request, &view->line[view->lineno]);
2427                 if (request == REQ_NONE)
2428                         return TRUE;
2429         }
2430
2431         switch (request) {
2432         case REQ_MOVE_UP:
2433         case REQ_MOVE_DOWN:
2434         case REQ_MOVE_PAGE_UP:
2435         case REQ_MOVE_PAGE_DOWN:
2436         case REQ_MOVE_FIRST_LINE:
2437         case REQ_MOVE_LAST_LINE:
2438                 move_view(view, request);
2439                 break;
2440
2441         case REQ_SCROLL_LINE_DOWN:
2442         case REQ_SCROLL_LINE_UP:
2443         case REQ_SCROLL_PAGE_DOWN:
2444         case REQ_SCROLL_PAGE_UP:
2445                 scroll_view(view, request);
2446                 break;
2447
2448         case REQ_VIEW_BLOB:
2449                 if (!ref_blob[0]) {
2450                         report("No file chosen, press %s to open tree view",
2451                                get_key(REQ_VIEW_TREE));
2452                         break;
2453                 }
2454                 open_view(view, request, OPEN_DEFAULT);
2455                 break;
2456
2457         case REQ_VIEW_PAGER:
2458                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2459                         report("No pager content, press %s to run command from prompt",
2460                                get_key(REQ_PROMPT));
2461                         break;
2462                 }
2463                 open_view(view, request, OPEN_DEFAULT);
2464                 break;
2465
2466         case REQ_VIEW_STAGE:
2467                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2468                         report("No stage content, press %s to open the status view and choose file",
2469                                get_key(REQ_VIEW_STATUS));
2470                         break;
2471                 }
2472                 open_view(view, request, OPEN_DEFAULT);
2473                 break;
2474
2475         case REQ_VIEW_STATUS:
2476                 if (opt_is_inside_work_tree == FALSE) {
2477                         report("The status view requires a working tree");
2478                         break;
2479                 }
2480                 open_view(view, request, OPEN_DEFAULT);
2481                 break;
2482
2483         case REQ_VIEW_MAIN:
2484         case REQ_VIEW_DIFF:
2485         case REQ_VIEW_LOG:
2486         case REQ_VIEW_TREE:
2487         case REQ_VIEW_HELP:
2488                 open_view(view, request, OPEN_DEFAULT);
2489                 break;
2490
2491         case REQ_NEXT:
2492         case REQ_PREVIOUS:
2493                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2494
2495                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2496                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2497                    (view == VIEW(REQ_VIEW_STAGE) &&
2498                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2499                    (view == VIEW(REQ_VIEW_BLOB) &&
2500                      view->parent == VIEW(REQ_VIEW_TREE))) {
2501                         int line;
2502
2503                         view = view->parent;
2504                         line = view->lineno;
2505                         move_view(view, request);
2506                         if (view_is_displayed(view))
2507                                 update_view_title(view);
2508                         if (line != view->lineno)
2509                                 view->ops->request(view, REQ_ENTER,
2510                                                    &view->line[view->lineno]);
2511
2512                 } else {
2513                         move_view(view, request);
2514                 }
2515                 break;
2516
2517         case REQ_VIEW_NEXT:
2518         {
2519                 int nviews = displayed_views();
2520                 int next_view = (current_view + 1) % nviews;
2521
2522                 if (next_view == current_view) {
2523                         report("Only one view is displayed");
2524                         break;
2525                 }
2526
2527                 current_view = next_view;
2528                 /* Blur out the title of the previous view. */
2529                 update_view_title(view);
2530                 report("");
2531                 break;
2532         }
2533         case REQ_REFRESH:
2534                 report("Refreshing is not yet supported for the %s view", view->name);
2535                 break;
2536
2537         case REQ_TOGGLE_LINENO:
2538                 opt_line_number = !opt_line_number;
2539                 redraw_display();
2540                 break;
2541
2542         case REQ_TOGGLE_REV_GRAPH:
2543                 opt_rev_graph = !opt_rev_graph;
2544                 redraw_display();
2545                 break;
2546
2547         case REQ_PROMPT:
2548                 /* Always reload^Wrerun commands from the prompt. */
2549                 open_view(view, opt_request, OPEN_RELOAD);
2550                 break;
2551
2552         case REQ_SEARCH:
2553         case REQ_SEARCH_BACK:
2554                 search_view(view, request);
2555                 break;
2556
2557         case REQ_FIND_NEXT:
2558         case REQ_FIND_PREV:
2559                 find_next(view, request);
2560                 break;
2561
2562         case REQ_STOP_LOADING:
2563                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2564                         view = &views[i];
2565                         if (view->pipe)
2566                                 report("Stopped loading the %s view", view->name),
2567                         end_update(view);
2568                 }
2569                 break;
2570
2571         case REQ_SHOW_VERSION:
2572                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2573                 return TRUE;
2574
2575         case REQ_SCREEN_RESIZE:
2576                 resize_display();
2577                 /* Fall-through */
2578         case REQ_SCREEN_REDRAW:
2579                 redraw_display();
2580                 break;
2581
2582         case REQ_EDIT:
2583                 report("Nothing to edit");
2584                 break;
2585
2586
2587         case REQ_ENTER:
2588                 report("Nothing to enter");
2589                 break;
2590
2591
2592         case REQ_VIEW_CLOSE:
2593                 /* XXX: Mark closed views by letting view->parent point to the
2594                  * view itself. Parents to closed view should never be
2595                  * followed. */
2596                 if (view->parent &&
2597                     view->parent->parent != view->parent) {
2598                         memset(display, 0, sizeof(display));
2599                         current_view = 0;
2600                         display[current_view] = view->parent;
2601                         view->parent = view;
2602                         resize_display();
2603                         redraw_display();
2604                         break;
2605                 }
2606                 /* Fall-through */
2607         case REQ_QUIT:
2608                 return FALSE;
2609
2610         default:
2611                 /* An unknown key will show most commonly used commands. */
2612                 report("Unknown key, press 'h' for help");
2613                 return TRUE;
2614         }
2615
2616         return TRUE;
2617 }
2618
2619
2620 /*
2621  * Pager backend
2622  */
2623
2624 static bool
2625 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2626 {
2627         char *text = line->data;
2628         enum line_type type = line->type;
2629         int attr;
2630
2631         wmove(view->win, lineno, 0);
2632
2633         if (selected) {
2634                 type = LINE_CURSOR;
2635                 wchgat(view->win, -1, 0, type, NULL);
2636         }
2637
2638         attr = get_line_attr(type);
2639         wattrset(view->win, attr);
2640
2641         if (opt_line_number || opt_tab_size < TABSIZE) {
2642                 static char spaces[] = "                    ";
2643                 int col_offset = 0, col = 0;
2644
2645                 if (opt_line_number) {
2646                         unsigned long real_lineno = view->offset + lineno + 1;
2647
2648                         if (real_lineno == 1 ||
2649                             (real_lineno % opt_num_interval) == 0) {
2650                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2651
2652                         } else {
2653                                 waddnstr(view->win, spaces,
2654                                          MIN(view->digits, STRING_SIZE(spaces)));
2655                         }
2656                         waddstr(view->win, ": ");
2657                         col_offset = view->digits + 2;
2658                 }
2659
2660                 while (text && col_offset + col < view->width) {
2661                         int cols_max = view->width - col_offset - col;
2662                         char *pos = text;
2663                         int cols;
2664
2665                         if (*text == '\t') {
2666                                 text++;
2667                                 assert(sizeof(spaces) > TABSIZE);
2668                                 pos = spaces;
2669                                 cols = opt_tab_size - (col % opt_tab_size);
2670
2671                         } else {
2672                                 text = strchr(text, '\t');
2673                                 cols = line ? text - pos : strlen(pos);
2674                         }
2675
2676                         waddnstr(view->win, pos, MIN(cols, cols_max));
2677                         col += cols;
2678                 }
2679
2680         } else {
2681                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2682
2683                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2684         }
2685
2686         return TRUE;
2687 }
2688
2689 static bool
2690 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2691 {
2692         char refbuf[SIZEOF_STR];
2693         char *ref = NULL;
2694         FILE *pipe;
2695
2696         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2697                 return TRUE;
2698
2699         pipe = popen(refbuf, "r");
2700         if (!pipe)
2701                 return TRUE;
2702
2703         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2704                 ref = chomp_string(ref);
2705         pclose(pipe);
2706
2707         if (!ref || !*ref)
2708                 return TRUE;
2709
2710         /* This is the only fatal call, since it can "corrupt" the buffer. */
2711         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2712                 return FALSE;
2713
2714         return TRUE;
2715 }
2716
2717 static void
2718 add_pager_refs(struct view *view, struct line *line)
2719 {
2720         char buf[SIZEOF_STR];
2721         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2722         struct ref **refs;
2723         size_t bufpos = 0, refpos = 0;
2724         const char *sep = "Refs: ";
2725         bool is_tag = FALSE;
2726
2727         assert(line->type == LINE_COMMIT);
2728
2729         refs = get_refs(commit_id);
2730         if (!refs) {
2731                 if (view == VIEW(REQ_VIEW_DIFF))
2732                         goto try_add_describe_ref;
2733                 return;
2734         }
2735
2736         do {
2737                 struct ref *ref = refs[refpos];
2738                 char *fmt = ref->tag    ? "%s[%s]" :
2739                             ref->remote ? "%s<%s>" : "%s%s";
2740
2741                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2742                         return;
2743                 sep = ", ";
2744                 if (ref->tag)
2745                         is_tag = TRUE;
2746         } while (refs[refpos++]->next);
2747
2748         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2749 try_add_describe_ref:
2750                 /* Add <tag>-g<commit_id> "fake" reference. */
2751                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2752                         return;
2753         }
2754
2755         if (bufpos == 0)
2756                 return;
2757
2758         if (!realloc_lines(view, view->line_size + 1))
2759                 return;
2760
2761         add_line_text(view, buf, LINE_PP_REFS);
2762 }
2763
2764 static bool
2765 pager_read(struct view *view, char *data)
2766 {
2767         struct line *line;
2768
2769         if (!data)
2770                 return TRUE;
2771
2772         line = add_line_text(view, data, get_line_type(data));
2773         if (!line)
2774                 return FALSE;
2775
2776         if (line->type == LINE_COMMIT &&
2777             (view == VIEW(REQ_VIEW_DIFF) ||
2778              view == VIEW(REQ_VIEW_LOG)))
2779                 add_pager_refs(view, line);
2780
2781         return TRUE;
2782 }
2783
2784 static enum request
2785 pager_request(struct view *view, enum request request, struct line *line)
2786 {
2787         int split = 0;
2788
2789         if (request != REQ_ENTER)
2790                 return request;
2791
2792         if (line->type == LINE_COMMIT &&
2793            (view == VIEW(REQ_VIEW_LOG) ||
2794             view == VIEW(REQ_VIEW_PAGER))) {
2795                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2796                 split = 1;
2797         }
2798
2799         /* Always scroll the view even if it was split. That way
2800          * you can use Enter to scroll through the log view and
2801          * split open each commit diff. */
2802         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2803
2804         /* FIXME: A minor workaround. Scrolling the view will call report("")
2805          * but if we are scrolling a non-current view this won't properly
2806          * update the view title. */
2807         if (split)
2808                 update_view_title(view);
2809
2810         return REQ_NONE;
2811 }
2812
2813 static bool
2814 pager_grep(struct view *view, struct line *line)
2815 {
2816         regmatch_t pmatch;
2817         char *text = line->data;
2818
2819         if (!*text)
2820                 return FALSE;
2821
2822         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2823                 return FALSE;
2824
2825         return TRUE;
2826 }
2827
2828 static void
2829 pager_select(struct view *view, struct line *line)
2830 {
2831         if (line->type == LINE_COMMIT) {
2832                 char *text = (char *)line->data + STRING_SIZE("commit ");
2833
2834                 if (view != VIEW(REQ_VIEW_PAGER))
2835                         string_copy_rev(view->ref, text);
2836                 string_copy_rev(ref_commit, text);
2837         }
2838 }
2839
2840 static struct view_ops pager_ops = {
2841         "line",
2842         NULL,
2843         pager_read,
2844         pager_draw,
2845         pager_request,
2846         pager_grep,
2847         pager_select,
2848 };
2849
2850
2851 /*
2852  * Help backend
2853  */
2854
2855 static bool
2856 help_open(struct view *view)
2857 {
2858         char buf[BUFSIZ];
2859         int lines = ARRAY_SIZE(req_info) + 2;
2860         int i;
2861
2862         if (view->lines > 0)
2863                 return TRUE;
2864
2865         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2866                 if (!req_info[i].request)
2867                         lines++;
2868
2869         lines += run_requests + 1;
2870
2871         view->line = calloc(lines, sizeof(*view->line));
2872         if (!view->line)
2873                 return FALSE;
2874
2875         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2876
2877         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2878                 char *key;
2879
2880                 if (req_info[i].request == REQ_NONE)
2881                         continue;
2882
2883                 if (!req_info[i].request) {
2884                         add_line_text(view, "", LINE_DEFAULT);
2885                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2886                         continue;
2887                 }
2888
2889                 key = get_key(req_info[i].request);
2890                 if (!*key)
2891                         key = "(no key defined)";
2892
2893                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2894                         continue;
2895
2896                 add_line_text(view, buf, LINE_DEFAULT);
2897         }
2898
2899         if (run_requests) {
2900                 add_line_text(view, "", LINE_DEFAULT);
2901                 add_line_text(view, "External commands:", LINE_DEFAULT);
2902         }
2903
2904         for (i = 0; i < run_requests; i++) {
2905                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2906                 char *key;
2907
2908                 if (!req)
2909                         continue;
2910
2911                 key = get_key_name(req->key);
2912                 if (!*key)
2913                         key = "(no key defined)";
2914
2915                 if (!string_format(buf, "    %-10s %-14s `%s`",
2916                                    keymap_table[req->keymap].name,
2917                                    key, req->cmd))
2918                         continue;
2919
2920                 add_line_text(view, buf, LINE_DEFAULT);
2921         }
2922
2923         return TRUE;
2924 }
2925
2926 static struct view_ops help_ops = {
2927         "line",
2928         help_open,
2929         NULL,
2930         pager_draw,
2931         pager_request,
2932         pager_grep,
2933         pager_select,
2934 };
2935
2936
2937 /*
2938  * Tree backend
2939  */
2940
2941 struct tree_stack_entry {
2942         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2943         unsigned long lineno;           /* Line number to restore */
2944         char *name;                     /* Position of name in opt_path */
2945 };
2946
2947 /* The top of the path stack. */
2948 static struct tree_stack_entry *tree_stack = NULL;
2949 unsigned long tree_lineno = 0;
2950
2951 static void
2952 pop_tree_stack_entry(void)
2953 {
2954         struct tree_stack_entry *entry = tree_stack;
2955
2956         tree_lineno = entry->lineno;
2957         entry->name[0] = 0;
2958         tree_stack = entry->prev;
2959         free(entry);
2960 }
2961
2962 static void
2963 push_tree_stack_entry(char *name, unsigned long lineno)
2964 {
2965         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2966         size_t pathlen = strlen(opt_path);
2967
2968         if (!entry)
2969                 return;
2970
2971         entry->prev = tree_stack;
2972         entry->name = opt_path + pathlen;
2973         tree_stack = entry;
2974
2975         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2976                 pop_tree_stack_entry();
2977                 return;
2978         }
2979
2980         /* Move the current line to the first tree entry. */
2981         tree_lineno = 1;
2982         entry->lineno = lineno;
2983 }
2984
2985 /* Parse output from git-ls-tree(1):
2986  *
2987  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2988  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2989  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2990  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2991  */
2992
2993 #define SIZEOF_TREE_ATTR \
2994         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2995
2996 #define TREE_UP_FORMAT "040000 tree %s\t.."
2997
2998 static int
2999 tree_compare_entry(enum line_type type1, char *name1,
3000                    enum line_type type2, char *name2)
3001 {
3002         if (type1 != type2) {
3003                 if (type1 == LINE_TREE_DIR)
3004                         return -1;
3005                 return 1;
3006         }
3007
3008         return strcmp(name1, name2);
3009 }
3010
3011 static bool
3012 tree_read(struct view *view, char *text)
3013 {
3014         size_t textlen = text ? strlen(text) : 0;
3015         char buf[SIZEOF_STR];
3016         unsigned long pos;
3017         enum line_type type;
3018         bool first_read = view->lines == 0;
3019
3020         if (textlen <= SIZEOF_TREE_ATTR)
3021                 return FALSE;
3022
3023         type = text[STRING_SIZE("100644 ")] == 't'
3024              ? LINE_TREE_DIR : LINE_TREE_FILE;
3025
3026         if (first_read) {
3027                 /* Add path info line */
3028                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3029                     !realloc_lines(view, view->line_size + 1) ||
3030                     !add_line_text(view, buf, LINE_DEFAULT))
3031                         return FALSE;
3032
3033                 /* Insert "link" to parent directory. */
3034                 if (*opt_path) {
3035                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3036                             !realloc_lines(view, view->line_size + 1) ||
3037                             !add_line_text(view, buf, LINE_TREE_DIR))
3038                                 return FALSE;
3039                 }
3040         }
3041
3042         /* Strip the path part ... */
3043         if (*opt_path) {
3044                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3045                 size_t striplen = strlen(opt_path);
3046                 char *path = text + SIZEOF_TREE_ATTR;
3047
3048                 if (pathlen > striplen)
3049                         memmove(path, path + striplen,
3050                                 pathlen - striplen + 1);
3051         }
3052
3053         /* Skip "Directory ..." and ".." line. */
3054         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3055                 struct line *line = &view->line[pos];
3056                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3057                 char *path2 = text + SIZEOF_TREE_ATTR;
3058                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3059
3060                 if (cmp <= 0)
3061                         continue;
3062
3063                 text = strdup(text);
3064                 if (!text)
3065                         return FALSE;
3066
3067                 if (view->lines > pos)
3068                         memmove(&view->line[pos + 1], &view->line[pos],
3069                                 (view->lines - pos) * sizeof(*line));
3070
3071                 line = &view->line[pos];
3072                 line->data = text;
3073                 line->type = type;
3074                 view->lines++;
3075                 return TRUE;
3076         }
3077
3078         if (!add_line_text(view, text, type))
3079                 return FALSE;
3080
3081         if (tree_lineno > view->lineno) {
3082                 view->lineno = tree_lineno;
3083                 tree_lineno = 0;
3084         }
3085
3086         return TRUE;
3087 }
3088
3089 static enum request
3090 tree_request(struct view *view, enum request request, struct line *line)
3091 {
3092         enum open_flags flags;
3093
3094         if (request == REQ_TREE_PARENT) {
3095                 if (*opt_path) {
3096                         /* fake 'cd  ..' */
3097                         request = REQ_ENTER;
3098                         line = &view->line[1];
3099                 } else {
3100                         /* quit view if at top of tree */
3101                         return REQ_VIEW_CLOSE;
3102                 }
3103         }
3104         if (request != REQ_ENTER)
3105                 return request;
3106
3107         /* Cleanup the stack if the tree view is at a different tree. */
3108         while (!*opt_path && tree_stack)
3109                 pop_tree_stack_entry();
3110
3111         switch (line->type) {
3112         case LINE_TREE_DIR:
3113                 /* Depending on whether it is a subdir or parent (updir?) link
3114                  * mangle the path buffer. */
3115                 if (line == &view->line[1] && *opt_path) {
3116                         pop_tree_stack_entry();
3117
3118                 } else {
3119                         char *data = line->data;
3120                         char *basename = data + SIZEOF_TREE_ATTR;
3121
3122                         push_tree_stack_entry(basename, view->lineno);
3123                 }
3124
3125                 /* Trees and subtrees share the same ID, so they are not not
3126                  * unique like blobs. */
3127                 flags = OPEN_RELOAD;
3128                 request = REQ_VIEW_TREE;
3129                 break;
3130
3131         case LINE_TREE_FILE:
3132                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3133                 request = REQ_VIEW_BLOB;
3134                 break;
3135
3136         default:
3137                 return TRUE;
3138         }
3139
3140         open_view(view, request, flags);
3141         if (request == REQ_VIEW_TREE) {
3142                 view->lineno = tree_lineno;
3143         }
3144
3145         return REQ_NONE;
3146 }
3147
3148 static void
3149 tree_select(struct view *view, struct line *line)
3150 {
3151         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3152
3153         if (line->type == LINE_TREE_FILE) {
3154                 string_copy_rev(ref_blob, text);
3155
3156         } else if (line->type != LINE_TREE_DIR) {
3157                 return;
3158         }
3159
3160         string_copy_rev(view->ref, text);
3161 }
3162
3163 static struct view_ops tree_ops = {
3164         "file",
3165         NULL,
3166         tree_read,
3167         pager_draw,
3168         tree_request,
3169         pager_grep,
3170         tree_select,
3171 };
3172
3173 static bool
3174 blob_read(struct view *view, char *line)
3175 {
3176         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3177 }
3178
3179 static struct view_ops blob_ops = {
3180         "line",
3181         NULL,
3182         blob_read,
3183         pager_draw,
3184         pager_request,
3185         pager_grep,
3186         pager_select,
3187 };
3188
3189
3190 /*
3191  * Status backend
3192  */
3193
3194 struct status {
3195         char status;
3196         struct {
3197                 mode_t mode;
3198                 char rev[SIZEOF_REV];
3199                 char name[SIZEOF_STR];
3200         } old;
3201         struct {
3202                 mode_t mode;
3203                 char rev[SIZEOF_REV];
3204                 char name[SIZEOF_STR];
3205         } new;
3206 };
3207
3208 static struct status stage_status;
3209 static enum line_type stage_line_type;
3210
3211 /* Get fields from the diff line:
3212  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3213  */
3214 static inline bool
3215 status_get_diff(struct status *file, char *buf, size_t bufsize)
3216 {
3217         char *old_mode = buf +  1;
3218         char *new_mode = buf +  8;
3219         char *old_rev  = buf + 15;
3220         char *new_rev  = buf + 56;
3221         char *status   = buf + 97;
3222
3223         if (bufsize < 99 ||
3224             old_mode[-1] != ':' ||
3225             new_mode[-1] != ' ' ||
3226             old_rev[-1]  != ' ' ||
3227             new_rev[-1]  != ' ' ||
3228             status[-1]   != ' ')
3229                 return FALSE;
3230
3231         file->status = *status;
3232
3233         string_copy_rev(file->old.rev, old_rev);
3234         string_copy_rev(file->new.rev, new_rev);
3235
3236         file->old.mode = strtoul(old_mode, NULL, 8);
3237         file->new.mode = strtoul(new_mode, NULL, 8);
3238
3239         file->old.name[0] = file->new.name[0] = 0;
3240
3241         return TRUE;
3242 }
3243
3244 static bool
3245 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3246 {
3247         struct status *file = NULL;
3248         struct status *unmerged = NULL;
3249         char buf[SIZEOF_STR * 4];
3250         size_t bufsize = 0;
3251         FILE *pipe;
3252
3253         pipe = popen(cmd, "r");
3254         if (!pipe)
3255                 return FALSE;
3256
3257         add_line_data(view, NULL, type);
3258
3259         while (!feof(pipe) && !ferror(pipe)) {
3260                 char *sep;
3261                 size_t readsize;
3262
3263                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3264                 if (!readsize)
3265                         break;
3266                 bufsize += readsize;
3267
3268                 /* Process while we have NUL chars. */
3269                 while ((sep = memchr(buf, 0, bufsize))) {
3270                         size_t sepsize = sep - buf + 1;
3271
3272                         if (!file) {
3273                                 if (!realloc_lines(view, view->line_size + 1))
3274                                         goto error_out;
3275
3276                                 file = calloc(1, sizeof(*file));
3277                                 if (!file)
3278                                         goto error_out;
3279
3280                                 add_line_data(view, file, type);
3281                         }
3282
3283                         /* Parse diff info part. */
3284                         if (!diff) {
3285                                 file->status = '?';
3286
3287                         } else if (!file->status) {
3288                                 if (!status_get_diff(file, buf, sepsize))
3289                                         goto error_out;
3290
3291                                 bufsize -= sepsize;
3292                                 memmove(buf, sep + 1, bufsize);
3293
3294                                 sep = memchr(buf, 0, bufsize);
3295                                 if (!sep)
3296                                         break;
3297                                 sepsize = sep - buf + 1;
3298
3299                                 /* Collapse all 'M'odified entries that
3300                                  * follow a associated 'U'nmerged entry.
3301                                  */
3302                                 if (file->status == 'U') {
3303                                         unmerged = file;
3304
3305                                 } else if (unmerged) {
3306                                         int collapse = !strcmp(buf, unmerged->new.name);
3307
3308                                         unmerged = NULL;
3309                                         if (collapse) {
3310                                                 free(file);
3311                                                 view->lines--;
3312                                                 continue;
3313                                         }
3314                                 }
3315                         }
3316
3317                         /* Grab the old name for rename/copy. */
3318                         if (!*file->old.name &&
3319                             (file->status == 'R' || file->status == 'C')) {
3320                                 sepsize = sep - buf + 1;
3321                                 string_ncopy(file->old.name, buf, sepsize);
3322                                 bufsize -= sepsize;
3323                                 memmove(buf, sep + 1, bufsize);
3324
3325                                 sep = memchr(buf, 0, bufsize);
3326                                 if (!sep)
3327                                         break;
3328                                 sepsize = sep - buf + 1;
3329                         }
3330
3331                         /* git-ls-files just delivers a NUL separated
3332                          * list of file names similar to the second half
3333                          * of the git-diff-* output. */
3334                         string_ncopy(file->new.name, buf, sepsize);
3335                         if (!*file->old.name)
3336                                 string_copy(file->old.name, file->new.name);
3337                         bufsize -= sepsize;
3338                         memmove(buf, sep + 1, bufsize);
3339                         file = NULL;
3340                 }
3341         }
3342
3343         if (ferror(pipe)) {
3344 error_out:
3345                 pclose(pipe);
3346                 return FALSE;
3347         }
3348
3349         if (!view->line[view->lines - 1].data)
3350                 add_line_data(view, NULL, LINE_STAT_NONE);
3351
3352         pclose(pipe);
3353         return TRUE;
3354 }
3355
3356 /* Don't show unmerged entries in the staged section. */
3357 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3358 #define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
3359 #define STATUS_LIST_OTHER_CMD \
3360         "git ls-files -z --others --exclude-per-directory=.gitignore"
3361
3362 #define STATUS_DIFF_INDEX_SHOW_CMD \
3363         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3364
3365 #define STATUS_DIFF_FILES_SHOW_CMD \
3366         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3367
3368 /* First parse staged info using git-diff-index(1), then parse unstaged
3369  * info using git-diff-files(1), and finally untracked files using
3370  * git-ls-files(1). */
3371 static bool
3372 status_open(struct view *view)
3373 {
3374         struct stat statbuf;
3375         char exclude[SIZEOF_STR];
3376         char cmd[SIZEOF_STR];
3377         unsigned long prev_lineno = view->lineno;
3378         size_t i;
3379
3380         for (i = 0; i < view->lines; i++)
3381                 free(view->line[i].data);
3382         free(view->line);
3383         view->lines = view->line_size = view->lineno = 0;
3384         view->line = NULL;
3385
3386         if (!realloc_lines(view, view->line_size + 6))
3387                 return FALSE;
3388
3389         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3390                 return FALSE;
3391
3392         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3393
3394         if (stat(exclude, &statbuf) >= 0) {
3395                 size_t cmdsize = strlen(cmd);
3396
3397                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3398                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3399                         return FALSE;
3400         }
3401
3402         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3403             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3404             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3405                 return FALSE;
3406
3407         /* If all went well restore the previous line number to stay in
3408          * the context. */
3409         if (prev_lineno < view->lines)
3410                 view->lineno = prev_lineno;
3411         else
3412                 view->lineno = view->lines - 1;
3413
3414         return TRUE;
3415 }
3416
3417 static bool
3418 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3419 {
3420         struct status *status = line->data;
3421         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3422
3423         wmove(view->win, lineno, 0);
3424
3425         if (selected) {
3426                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3427                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3428                 tilde_attr = -1;
3429
3430         } else if (!status && line->type != LINE_STAT_NONE) {
3431                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3432                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3433
3434         } else {
3435                 wattrset(view->win, get_line_attr(line->type));
3436         }
3437
3438         if (!status) {
3439                 char *text;
3440
3441                 switch (line->type) {
3442                 case LINE_STAT_STAGED:
3443                         text = "Changes to be committed:";
3444                         break;
3445
3446                 case LINE_STAT_UNSTAGED:
3447                         text = "Changed but not updated:";
3448                         break;
3449
3450                 case LINE_STAT_UNTRACKED:
3451                         text = "Untracked files:";
3452                         break;
3453
3454                 case LINE_STAT_NONE:
3455                         text = "    (no files)";
3456                         break;
3457
3458                 default:
3459                         return FALSE;
3460                 }
3461
3462                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3463                 return TRUE;
3464         }
3465
3466         waddch(view->win, status->status);
3467         if (!selected)
3468                 wattrset(view->win, A_NORMAL);
3469         wmove(view->win, lineno, 4);
3470         if (view->width < 5)
3471                 return TRUE;
3472
3473         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3474         return TRUE;
3475 }
3476
3477 static enum request
3478 status_enter(struct view *view, struct line *line)
3479 {
3480         struct status *status = line->data;
3481         char oldpath[SIZEOF_STR] = "";
3482         char newpath[SIZEOF_STR] = "";
3483         char *info;
3484         size_t cmdsize = 0;
3485
3486         if (line->type == LINE_STAT_NONE ||
3487             (!status && line[1].type == LINE_STAT_NONE)) {
3488                 report("No file to diff");
3489                 return REQ_NONE;
3490         }
3491
3492         if (status) {
3493                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3494                         return REQ_QUIT;
3495                 /* Diffs for unmerged entries are empty when pasing the
3496                  * new path, so leave it empty. */
3497                 if (status->status != 'U' &&
3498                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3499                         return REQ_QUIT;
3500         }
3501
3502         if (opt_cdup[0] &&
3503             line->type != LINE_STAT_UNTRACKED &&
3504             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3505                 return REQ_QUIT;
3506
3507         switch (line->type) {
3508         case LINE_STAT_STAGED:
3509                 if (!string_format_from(opt_cmd, &cmdsize,
3510                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3511                         return REQ_QUIT;
3512                 if (status)
3513                         info = "Staged changes to %s";
3514                 else
3515                         info = "Staged changes";
3516                 break;
3517
3518         case LINE_STAT_UNSTAGED:
3519                 if (!string_format_from(opt_cmd, &cmdsize,
3520                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3521                         return REQ_QUIT;
3522                 if (status)
3523                         info = "Unstaged changes to %s";
3524                 else
3525                         info = "Unstaged changes";
3526                 break;
3527
3528         case LINE_STAT_UNTRACKED:
3529                 if (opt_pipe)
3530                         return REQ_QUIT;
3531
3532
3533                 if (!status) {
3534                         report("No file to show");
3535                         return REQ_NONE;
3536                 }
3537
3538                 opt_pipe = fopen(status->new.name, "r");
3539                 info = "Untracked file %s";
3540                 break;
3541
3542         default:
3543                 die("line type %d not handled in switch", line->type);
3544         }
3545
3546         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3547         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3548                 if (status) {
3549                         stage_status = *status;
3550                 } else {
3551                         memset(&stage_status, 0, sizeof(stage_status));
3552                 }
3553
3554                 stage_line_type = line->type;
3555                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3556         }
3557
3558         return REQ_NONE;
3559 }
3560
3561
3562 static bool
3563 status_update_file(struct view *view, struct status *status, enum line_type type)
3564 {
3565         char cmd[SIZEOF_STR];
3566         char buf[SIZEOF_STR];
3567         size_t cmdsize = 0;
3568         size_t bufsize = 0;
3569         size_t written = 0;
3570         FILE *pipe;
3571
3572         if (opt_cdup[0] &&
3573             type != LINE_STAT_UNTRACKED &&
3574             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3575                 return FALSE;
3576
3577         switch (type) {
3578         case LINE_STAT_STAGED:
3579                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3580                                         status->old.mode,
3581                                         status->old.rev,
3582                                         status->old.name, 0))
3583                         return FALSE;
3584
3585                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3586                 break;
3587
3588         case LINE_STAT_UNSTAGED:
3589         case LINE_STAT_UNTRACKED:
3590                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3591                         return FALSE;
3592
3593                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3594                 break;
3595
3596         default:
3597                 die("line type %d not handled in switch", type);
3598         }
3599
3600         pipe = popen(cmd, "w");
3601         if (!pipe)
3602                 return FALSE;
3603
3604         while (!ferror(pipe) && written < bufsize) {
3605                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3606         }
3607
3608         pclose(pipe);
3609
3610         if (written != bufsize)
3611                 return FALSE;
3612
3613         return TRUE;
3614 }
3615
3616 static void
3617 status_update(struct view *view)
3618 {
3619         struct line *line = &view->line[view->lineno];
3620
3621         assert(view->lines);
3622
3623         if (!line->data) {
3624                 while (++line < view->line + view->lines && line->data) {
3625                         if (!status_update_file(view, line->data, line->type))
3626                                 report("Failed to update file status");
3627                 }
3628
3629                 if (!line[-1].data) {
3630                         report("Nothing to update");
3631                         return;
3632                 }
3633
3634         } else if (!status_update_file(view, line->data, line->type)) {
3635                 report("Failed to update file status");
3636         }
3637 }
3638
3639 static enum request
3640 status_request(struct view *view, enum request request, struct line *line)
3641 {
3642         struct status *status = line->data;
3643
3644         switch (request) {
3645         case REQ_STATUS_UPDATE:
3646                 status_update(view);
3647                 break;
3648
3649         case REQ_STATUS_MERGE:
3650                 if (!status || status->status != 'U') {
3651                         report("Merging only possible for files with unmerged status ('U').");
3652                         return REQ_NONE;
3653                 }
3654                 open_mergetool(status->new.name);
3655                 break;
3656
3657         case REQ_EDIT:
3658                 if (!status)
3659                         return request;
3660
3661                 open_editor(status->status != '?', status->new.name);
3662                 break;
3663
3664         case REQ_ENTER:
3665                 /* After returning the status view has been split to
3666                  * show the stage view. No further reloading is
3667                  * necessary. */
3668                 status_enter(view, line);
3669                 return REQ_NONE;
3670
3671         case REQ_REFRESH:
3672                 /* Simply reload the view. */
3673                 break;
3674
3675         default:
3676                 return request;
3677         }
3678
3679         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3680
3681         return REQ_NONE;
3682 }
3683
3684 static void
3685 status_select(struct view *view, struct line *line)
3686 {
3687         struct status *status = line->data;
3688         char file[SIZEOF_STR] = "all files";
3689         char *text;
3690         char *key;
3691
3692         if (status && !string_format(file, "'%s'", status->new.name))
3693                 return;
3694
3695         if (!status && line[1].type == LINE_STAT_NONE)
3696                 line++;
3697
3698         switch (line->type) {
3699         case LINE_STAT_STAGED:
3700                 text = "Press %s to unstage %s for commit";
3701                 break;
3702
3703         case LINE_STAT_UNSTAGED:
3704                 text = "Press %s to stage %s for commit";
3705                 break;
3706
3707         case LINE_STAT_UNTRACKED:
3708                 text = "Press %s to stage %s for addition";
3709                 break;
3710
3711         case LINE_STAT_NONE:
3712                 text = "Nothing to update";
3713                 break;
3714
3715         default:
3716                 die("line type %d not handled in switch", line->type);
3717         }
3718
3719         if (status && status->status == 'U') {
3720                 text = "Press %s to resolve conflict in %s";
3721                 key = get_key(REQ_STATUS_MERGE);
3722
3723         } else {
3724                 key = get_key(REQ_STATUS_UPDATE);
3725         }
3726
3727         string_format(view->ref, text, key, file);
3728 }
3729
3730 static bool
3731 status_grep(struct view *view, struct line *line)
3732 {
3733         struct status *status = line->data;
3734         enum { S_STATUS, S_NAME, S_END } state;
3735         char buf[2] = "?";
3736         regmatch_t pmatch;
3737
3738         if (!status)
3739                 return FALSE;
3740
3741         for (state = S_STATUS; state < S_END; state++) {
3742                 char *text;
3743
3744                 switch (state) {
3745                 case S_NAME:    text = status->new.name;        break;
3746                 case S_STATUS:
3747                         buf[0] = status->status;
3748                         text = buf;
3749                         break;
3750
3751                 default:
3752                         return FALSE;
3753                 }
3754
3755                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3756                         return TRUE;
3757         }
3758
3759         return FALSE;
3760 }
3761
3762 static struct view_ops status_ops = {
3763         "file",
3764         status_open,
3765         NULL,
3766         status_draw,
3767         status_request,
3768         status_grep,
3769         status_select,
3770 };
3771
3772
3773 static bool
3774 stage_diff_line(FILE *pipe, struct line *line)
3775 {
3776         char *buf = line->data;
3777         size_t bufsize = strlen(buf);
3778         size_t written = 0;
3779
3780         while (!ferror(pipe) && written < bufsize) {
3781                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3782         }
3783
3784         fputc('\n', pipe);
3785
3786         return written == bufsize;
3787 }
3788
3789 static struct line *
3790 stage_diff_hdr(struct view *view, struct line *line)
3791 {
3792         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3793         struct line *diff_hdr;
3794
3795         if (line->type == LINE_DIFF_CHUNK)
3796                 diff_hdr = line - 1;
3797         else
3798                 diff_hdr = view->line + 1;
3799
3800         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3801                 if (diff_hdr->type == LINE_DIFF_HEADER)
3802                         return diff_hdr;
3803
3804                 diff_hdr += diff_hdr_dir;
3805         }
3806
3807         return NULL;
3808 }
3809
3810 static bool
3811 stage_update_chunk(struct view *view, struct line *line)
3812 {
3813         char cmd[SIZEOF_STR];
3814         size_t cmdsize = 0;
3815         struct line *diff_hdr, *diff_chunk, *diff_end;
3816         FILE *pipe;
3817
3818         diff_hdr = stage_diff_hdr(view, line);
3819         if (!diff_hdr)
3820                 return FALSE;
3821
3822         if (opt_cdup[0] &&
3823             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3824                 return FALSE;
3825
3826         if (!string_format_from(cmd, &cmdsize,
3827                                 "git apply --cached %s - && "
3828                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3829                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3830                 return FALSE;
3831
3832         pipe = popen(cmd, "w");
3833         if (!pipe)
3834                 return FALSE;
3835
3836         diff_end = view->line + view->lines;
3837         if (line->type != LINE_DIFF_CHUNK) {
3838                 diff_chunk = diff_hdr;
3839
3840         } else {
3841                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3842                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3843                             diff_chunk->type == LINE_DIFF_HEADER)
3844                                 diff_end = diff_chunk;
3845
3846                 diff_chunk = line;
3847
3848                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3849                         switch (diff_hdr->type) {
3850                         case LINE_DIFF_HEADER:
3851                         case LINE_DIFF_INDEX:
3852                         case LINE_DIFF_ADD:
3853                         case LINE_DIFF_DEL:
3854                                 break;
3855
3856                         default:
3857                                 diff_hdr++;
3858                                 continue;
3859                         }
3860
3861                         if (!stage_diff_line(pipe, diff_hdr++)) {
3862                                 pclose(pipe);
3863                                 return FALSE;
3864                         }
3865                 }
3866         }
3867
3868         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3869                 diff_chunk++;
3870
3871         pclose(pipe);
3872
3873         if (diff_chunk != diff_end)
3874                 return FALSE;
3875
3876         return TRUE;
3877 }
3878
3879 static void
3880 stage_update(struct view *view, struct line *line)
3881 {
3882         if (stage_line_type != LINE_STAT_UNTRACKED &&
3883             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3884                 if (!stage_update_chunk(view, line)) {
3885                         report("Failed to apply chunk");
3886                         return;
3887                 }
3888
3889         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3890                 report("Failed to update file");
3891                 return;
3892         }
3893
3894         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3895
3896         view = VIEW(REQ_VIEW_STATUS);
3897         if (view_is_displayed(view))
3898                 status_enter(view, &view->line[view->lineno]);
3899 }
3900
3901 static enum request
3902 stage_request(struct view *view, enum request request, struct line *line)
3903 {
3904         switch (request) {
3905         case REQ_STATUS_UPDATE:
3906                 stage_update(view, line);
3907                 break;
3908
3909         case REQ_EDIT:
3910                 if (!stage_status.new.name[0])
3911                         return request;
3912
3913                 open_editor(stage_status.status != '?', stage_status.new.name);
3914                 break;
3915
3916         case REQ_ENTER:
3917                 pager_request(view, request, line);
3918                 break;
3919
3920         default:
3921                 return request;
3922         }
3923
3924         return REQ_NONE;
3925 }
3926
3927 static struct view_ops stage_ops = {
3928         "line",
3929         NULL,
3930         pager_read,
3931         pager_draw,
3932         stage_request,
3933         pager_grep,
3934         pager_select,
3935 };
3936
3937
3938 /*
3939  * Revision graph
3940  */
3941
3942 struct commit {
3943         char id[SIZEOF_REV];            /* SHA1 ID. */
3944         char title[128];                /* First line of the commit message. */
3945         char author[75];                /* Author of the commit. */
3946         struct tm time;                 /* Date from the author ident. */
3947         struct ref **refs;              /* Repository references. */
3948         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3949         size_t graph_size;              /* The width of the graph array. */
3950 };
3951
3952 /* Size of rev graph with no  "padding" columns */
3953 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3954
3955 struct rev_graph {
3956         struct rev_graph *prev, *next, *parents;
3957         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3958         size_t size;
3959         struct commit *commit;
3960         size_t pos;
3961         unsigned int boundary:1;
3962 };
3963
3964 /* Parents of the commit being visualized. */
3965 static struct rev_graph graph_parents[4];
3966
3967 /* The current stack of revisions on the graph. */
3968 static struct rev_graph graph_stacks[4] = {
3969         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3970         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3971         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3972         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3973 };
3974
3975 static inline bool
3976 graph_parent_is_merge(struct rev_graph *graph)
3977 {
3978         return graph->parents->size > 1;
3979 }
3980
3981 static inline void
3982 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3983 {
3984         struct commit *commit = graph->commit;
3985
3986         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3987                 commit->graph[commit->graph_size++] = symbol;
3988 }
3989
3990 static void
3991 done_rev_graph(struct rev_graph *graph)
3992 {
3993         if (graph_parent_is_merge(graph) &&
3994             graph->pos < graph->size - 1 &&
3995             graph->next->size == graph->size + graph->parents->size - 1) {
3996                 size_t i = graph->pos + graph->parents->size - 1;
3997
3998                 graph->commit->graph_size = i * 2;
3999                 while (i < graph->next->size - 1) {
4000                         append_to_rev_graph(graph, ' ');
4001                         append_to_rev_graph(graph, '\\');
4002                         i++;
4003                 }
4004         }
4005
4006         graph->size = graph->pos = 0;
4007         graph->commit = NULL;
4008         memset(graph->parents, 0, sizeof(*graph->parents));
4009 }
4010
4011 static void
4012 push_rev_graph(struct rev_graph *graph, char *parent)
4013 {
4014         int i;
4015
4016         /* "Collapse" duplicate parents lines.
4017          *
4018          * FIXME: This needs to also update update the drawn graph but
4019          * for now it just serves as a method for pruning graph lines. */
4020         for (i = 0; i < graph->size; i++)
4021                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4022                         return;
4023
4024         if (graph->size < SIZEOF_REVITEMS) {
4025                 string_copy_rev(graph->rev[graph->size++], parent);
4026         }
4027 }
4028
4029 static chtype
4030 get_rev_graph_symbol(struct rev_graph *graph)
4031 {
4032         chtype symbol;
4033
4034         if (graph->boundary)
4035                 symbol = REVGRAPH_BOUND;
4036         else if (graph->parents->size == 0)
4037                 symbol = REVGRAPH_INIT;
4038         else if (graph_parent_is_merge(graph))
4039                 symbol = REVGRAPH_MERGE;
4040         else if (graph->pos >= graph->size)
4041                 symbol = REVGRAPH_BRANCH;
4042         else
4043                 symbol = REVGRAPH_COMMIT;
4044
4045         return symbol;
4046 }
4047
4048 static void
4049 draw_rev_graph(struct rev_graph *graph)
4050 {
4051         struct rev_filler {
4052                 chtype separator, line;
4053         };
4054         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4055         static struct rev_filler fillers[] = {
4056                 { ' ',  REVGRAPH_LINE },
4057                 { '`',  '.' },
4058                 { '\'', ' ' },
4059                 { '/',  ' ' },
4060         };
4061         chtype symbol = get_rev_graph_symbol(graph);
4062         struct rev_filler *filler;
4063         size_t i;
4064
4065         filler = &fillers[DEFAULT];
4066
4067         for (i = 0; i < graph->pos; i++) {
4068                 append_to_rev_graph(graph, filler->line);
4069                 if (graph_parent_is_merge(graph->prev) &&
4070                     graph->prev->pos == i)
4071                         filler = &fillers[RSHARP];
4072
4073                 append_to_rev_graph(graph, filler->separator);
4074         }
4075
4076         /* Place the symbol for this revision. */
4077         append_to_rev_graph(graph, symbol);
4078
4079         if (graph->prev->size > graph->size)
4080                 filler = &fillers[RDIAG];
4081         else
4082                 filler = &fillers[DEFAULT];
4083
4084         i++;
4085
4086         for (; i < graph->size; i++) {
4087                 append_to_rev_graph(graph, filler->separator);
4088                 append_to_rev_graph(graph, filler->line);
4089                 if (graph_parent_is_merge(graph->prev) &&
4090                     i < graph->prev->pos + graph->parents->size)
4091                         filler = &fillers[RSHARP];
4092                 if (graph->prev->size > graph->size)
4093                         filler = &fillers[LDIAG];
4094         }
4095
4096         if (graph->prev->size > graph->size) {
4097                 append_to_rev_graph(graph, filler->separator);
4098                 if (filler->line != ' ')
4099                         append_to_rev_graph(graph, filler->line);
4100         }
4101 }
4102
4103 /* Prepare the next rev graph */
4104 static void
4105 prepare_rev_graph(struct rev_graph *graph)
4106 {
4107         size_t i;
4108
4109         /* First, traverse all lines of revisions up to the active one. */
4110         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4111                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4112                         break;
4113
4114                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4115         }
4116
4117         /* Interleave the new revision parent(s). */
4118         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4119                 push_rev_graph(graph->next, graph->parents->rev[i]);
4120
4121         /* Lastly, put any remaining revisions. */
4122         for (i = graph->pos + 1; i < graph->size; i++)
4123                 push_rev_graph(graph->next, graph->rev[i]);
4124 }
4125
4126 static void
4127 update_rev_graph(struct rev_graph *graph)
4128 {
4129         /* If this is the finalizing update ... */
4130         if (graph->commit)
4131                 prepare_rev_graph(graph);
4132
4133         /* Graph visualization needs a one rev look-ahead,
4134          * so the first update doesn't visualize anything. */
4135         if (!graph->prev->commit)
4136                 return;
4137
4138         draw_rev_graph(graph->prev);
4139         done_rev_graph(graph->prev->prev);
4140 }
4141
4142
4143 /*
4144  * Main view backend
4145  */
4146
4147 static bool
4148 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4149 {
4150         char buf[DATE_COLS + 1];
4151         struct commit *commit = line->data;
4152         enum line_type type;
4153         int col = 0;
4154         size_t timelen;
4155         int tilde_attr;
4156         int space;
4157
4158         if (!*commit->author)
4159                 return FALSE;
4160
4161         space = view->width;
4162         wmove(view->win, lineno, col);
4163
4164         if (selected) {
4165                 type = LINE_CURSOR;
4166                 wattrset(view->win, get_line_attr(type));
4167                 wchgat(view->win, -1, 0, type, NULL);
4168                 tilde_attr = -1;
4169         } else {
4170                 type = LINE_MAIN_COMMIT;
4171                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4172                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4173         }
4174
4175         {
4176                 int n;
4177
4178                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4179                 n = draw_text(
4180                         view, buf, view->width - col, col, FALSE, tilde_attr);
4181                 draw_text(
4182                         view, " ", view->width - col - n, col + n, FALSE,
4183                         tilde_attr);
4184
4185                 col += DATE_COLS;
4186                 wmove(view->win, lineno, col);
4187                 if (col >= view->width)
4188                         return TRUE;
4189         }
4190         if (type != LINE_CURSOR)
4191                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4192
4193         {
4194                 int max_len;
4195
4196                 max_len = view->width - col;
4197                 if (max_len > AUTHOR_COLS - 1)
4198                         max_len = AUTHOR_COLS - 1;
4199                 draw_text(
4200                         view, commit->author, max_len, col, TRUE, tilde_attr);
4201                 col += AUTHOR_COLS;
4202                 if (col >= view->width)
4203                         return TRUE;
4204         }
4205
4206         if (opt_rev_graph && commit->graph_size) {
4207                 size_t graph_size = view->width - col;
4208                 size_t i;
4209
4210                 if (type != LINE_CURSOR)
4211                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4212                 wmove(view->win, lineno, col);
4213                 if (graph_size > commit->graph_size)
4214                         graph_size = commit->graph_size;
4215                 /* Using waddch() instead of waddnstr() ensures that
4216                  * they'll be rendered correctly for the cursor line. */
4217                 for (i = 0; i < graph_size; i++)
4218                         waddch(view->win, commit->graph[i]);
4219
4220                 col += commit->graph_size + 1;
4221                 if (col >= view->width)
4222                         return TRUE;
4223                 waddch(view->win, ' ');
4224         }
4225         if (type != LINE_CURSOR)
4226                 wattrset(view->win, A_NORMAL);
4227
4228         wmove(view->win, lineno, col);
4229
4230         if (commit->refs) {
4231                 size_t i = 0;
4232
4233                 do {
4234                         if (type == LINE_CURSOR)
4235                                 ;
4236                         else if (commit->refs[i]->tag)
4237                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4238                         else if (commit->refs[i]->remote)
4239                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4240                         else
4241                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4242
4243                         col += draw_text(
4244                                 view, "[", view->width - col, col, TRUE,
4245                                 tilde_attr);
4246                         col += draw_text(
4247                                 view, commit->refs[i]->name, view->width - col,
4248                                 col, TRUE, tilde_attr);
4249                         col += draw_text(
4250                                 view, "]", view->width - col, col, TRUE,
4251                                 tilde_attr);
4252                         if (type != LINE_CURSOR)
4253                                 wattrset(view->win, A_NORMAL);
4254                         col += draw_text(
4255                                 view, " ", view->width - col, col, TRUE,
4256                                 tilde_attr);
4257                         if (col >= view->width)
4258                                 return TRUE;
4259                 } while (commit->refs[i++]->next);
4260         }
4261
4262         if (type != LINE_CURSOR)
4263                 wattrset(view->win, get_line_attr(type));
4264
4265         col += draw_text(
4266                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4267
4268         return TRUE;
4269 }
4270
4271 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4272 static bool
4273 main_read(struct view *view, char *line)
4274 {
4275         static struct rev_graph *graph = graph_stacks;
4276         enum line_type type;
4277         struct commit *commit;
4278
4279         if (!line) {
4280                 update_rev_graph(graph);
4281                 return TRUE;
4282         }
4283
4284         type = get_line_type(line);
4285         if (type == LINE_COMMIT) {
4286                 commit = calloc(1, sizeof(struct commit));
4287                 if (!commit)
4288                         return FALSE;
4289
4290                 line += STRING_SIZE("commit ");
4291                 if (*line == '-') {
4292                         graph->boundary = 1;
4293                         line++;
4294                 }
4295
4296                 string_copy_rev(commit->id, line);
4297                 commit->refs = get_refs(commit->id);
4298                 graph->commit = commit;
4299                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4300                 return TRUE;
4301         }
4302
4303         if (!view->lines)
4304                 return TRUE;
4305         commit = view->line[view->lines - 1].data;
4306
4307         switch (type) {
4308         case LINE_PARENT:
4309                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4310                 break;
4311
4312         case LINE_AUTHOR:
4313         {
4314                 /* Parse author lines where the name may be empty:
4315                  *      author  <email@address.tld> 1138474660 +0100
4316                  */
4317                 char *ident = line + STRING_SIZE("author ");
4318                 char *nameend = strchr(ident, '<');
4319                 char *emailend = strchr(ident, '>');
4320
4321                 if (!nameend || !emailend)
4322                         break;
4323
4324                 update_rev_graph(graph);
4325                 graph = graph->next;
4326
4327                 *nameend = *emailend = 0;
4328                 ident = chomp_string(ident);
4329                 if (!*ident) {
4330                         ident = chomp_string(nameend + 1);
4331                         if (!*ident)
4332                                 ident = "Unknown";
4333                 }
4334
4335                 string_ncopy(commit->author, ident, strlen(ident));
4336
4337                 /* Parse epoch and timezone */
4338                 if (emailend[1] == ' ') {
4339                         char *secs = emailend + 2;
4340                         char *zone = strchr(secs, ' ');
4341                         time_t time = (time_t) atol(secs);
4342
4343                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4344                                 long tz;
4345
4346                                 zone++;
4347                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4348                                 tz += ('0' - zone[2]) * 60 * 60;
4349                                 tz += ('0' - zone[3]) * 60;
4350                                 tz += ('0' - zone[4]) * 60;
4351
4352                                 if (zone[0] == '-')
4353                                         tz = -tz;
4354
4355                                 time -= tz;
4356                         }
4357
4358                         gmtime_r(&time, &commit->time);
4359                 }
4360                 break;
4361         }
4362         default:
4363                 /* Fill in the commit title if it has not already been set. */
4364                 if (commit->title[0])
4365                         break;
4366
4367                 /* Require titles to start with a non-space character at the
4368                  * offset used by git log. */
4369                 if (strncmp(line, "    ", 4))
4370                         break;
4371                 line += 4;
4372                 /* Well, if the title starts with a whitespace character,
4373                  * try to be forgiving.  Otherwise we end up with no title. */
4374                 while (isspace(*line))
4375                         line++;
4376                 if (*line == '\0')
4377                         break;
4378                 /* FIXME: More graceful handling of titles; append "..." to
4379                  * shortened titles, etc. */
4380
4381                 string_ncopy(commit->title, line, strlen(line));
4382         }
4383
4384         return TRUE;
4385 }
4386
4387 static enum request
4388 main_request(struct view *view, enum request request, struct line *line)
4389 {
4390         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4391
4392         if (request == REQ_ENTER)
4393                 open_view(view, REQ_VIEW_DIFF, flags);
4394         else
4395                 return request;
4396
4397         return REQ_NONE;
4398 }
4399
4400 static bool
4401 main_grep(struct view *view, struct line *line)
4402 {
4403         struct commit *commit = line->data;
4404         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4405         char buf[DATE_COLS + 1];
4406         regmatch_t pmatch;
4407
4408         for (state = S_TITLE; state < S_END; state++) {
4409                 char *text;
4410
4411                 switch (state) {
4412                 case S_TITLE:   text = commit->title;   break;
4413                 case S_AUTHOR:  text = commit->author;  break;
4414                 case S_DATE:
4415                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4416                                 continue;
4417                         text = buf;
4418                         break;
4419
4420                 default:
4421                         return FALSE;
4422                 }
4423
4424                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4425                         return TRUE;
4426         }
4427
4428         return FALSE;
4429 }
4430
4431 static void
4432 main_select(struct view *view, struct line *line)
4433 {
4434         struct commit *commit = line->data;
4435
4436         string_copy_rev(view->ref, commit->id);
4437         string_copy_rev(ref_commit, view->ref);
4438 }
4439
4440 static struct view_ops main_ops = {
4441         "commit",
4442         NULL,
4443         main_read,
4444         main_draw,
4445         main_request,
4446         main_grep,
4447         main_select,
4448 };
4449
4450
4451 /*
4452  * Unicode / UTF-8 handling
4453  *
4454  * NOTE: Much of the following code for dealing with unicode is derived from
4455  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4456  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4457  */
4458
4459 /* I've (over)annotated a lot of code snippets because I am not entirely
4460  * confident that the approach taken by this small UTF-8 interface is correct.
4461  * --jonas */
4462
4463 static inline int
4464 unicode_width(unsigned long c)
4465 {
4466         if (c >= 0x1100 &&
4467            (c <= 0x115f                         /* Hangul Jamo */
4468             || c == 0x2329
4469             || c == 0x232a
4470             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4471                                                 /* CJK ... Yi */
4472             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4473             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4474             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4475             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4476             || (c >= 0xffe0  && c <= 0xffe6)
4477             || (c >= 0x20000 && c <= 0x2fffd)
4478             || (c >= 0x30000 && c <= 0x3fffd)))
4479                 return 2;
4480
4481         if (c == '\t')
4482                 return opt_tab_size;
4483
4484         return 1;
4485 }
4486
4487 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4488  * Illegal bytes are set one. */
4489 static const unsigned char utf8_bytes[256] = {
4490         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,
4491         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,
4492         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,
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         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,
4497         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,
4498 };
4499
4500 /* Decode UTF-8 multi-byte representation into a unicode character. */
4501 static inline unsigned long
4502 utf8_to_unicode(const char *string, size_t length)
4503 {
4504         unsigned long unicode;
4505
4506         switch (length) {
4507         case 1:
4508                 unicode  =   string[0];
4509                 break;
4510         case 2:
4511                 unicode  =  (string[0] & 0x1f) << 6;
4512                 unicode +=  (string[1] & 0x3f);
4513                 break;
4514         case 3:
4515                 unicode  =  (string[0] & 0x0f) << 12;
4516                 unicode += ((string[1] & 0x3f) << 6);
4517                 unicode +=  (string[2] & 0x3f);
4518                 break;
4519         case 4:
4520                 unicode  =  (string[0] & 0x0f) << 18;
4521                 unicode += ((string[1] & 0x3f) << 12);
4522                 unicode += ((string[2] & 0x3f) << 6);
4523                 unicode +=  (string[3] & 0x3f);
4524                 break;
4525         case 5:
4526                 unicode  =  (string[0] & 0x0f) << 24;
4527                 unicode += ((string[1] & 0x3f) << 18);
4528                 unicode += ((string[2] & 0x3f) << 12);
4529                 unicode += ((string[3] & 0x3f) << 6);
4530                 unicode +=  (string[4] & 0x3f);
4531                 break;
4532         case 6:
4533                 unicode  =  (string[0] & 0x01) << 30;
4534                 unicode += ((string[1] & 0x3f) << 24);
4535                 unicode += ((string[2] & 0x3f) << 18);
4536                 unicode += ((string[3] & 0x3f) << 12);
4537                 unicode += ((string[4] & 0x3f) << 6);
4538                 unicode +=  (string[5] & 0x3f);
4539                 break;
4540         default:
4541                 die("Invalid unicode length");
4542         }
4543
4544         /* Invalid characters could return the special 0xfffd value but NUL
4545          * should be just as good. */
4546         return unicode > 0xffff ? 0 : unicode;
4547 }
4548
4549 /* Calculates how much of string can be shown within the given maximum width
4550  * and sets trimmed parameter to non-zero value if all of string could not be
4551  * shown. If the reserve flag is TRUE, it will reserve at least one
4552  * trailing character, which can be useful when drawing a delimiter.
4553  *
4554  * Returns the number of bytes to output from string to satisfy max_width. */
4555 static size_t
4556 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4557 {
4558         const char *start = string;
4559         const char *end = strchr(string, '\0');
4560         unsigned char last_bytes = 0;
4561         size_t width = 0;
4562
4563         *trimmed = 0;
4564
4565         while (string < end) {
4566                 int c = *(unsigned char *) string;
4567                 unsigned char bytes = utf8_bytes[c];
4568                 size_t ucwidth;
4569                 unsigned long unicode;
4570
4571                 if (string + bytes > end)
4572                         break;
4573
4574                 /* Change representation to figure out whether
4575                  * it is a single- or double-width character. */
4576
4577                 unicode = utf8_to_unicode(string, bytes);
4578                 /* FIXME: Graceful handling of invalid unicode character. */
4579                 if (!unicode)
4580                         break;
4581
4582                 ucwidth = unicode_width(unicode);
4583                 width  += ucwidth;
4584                 if (width > max_width) {
4585                         *trimmed = 1;
4586                         if (reserve && width - ucwidth == max_width) {
4587                                 string -= last_bytes;
4588                         }
4589                         break;
4590                 }
4591
4592                 string  += bytes;
4593                 last_bytes = bytes;
4594         }
4595
4596         return string - start;
4597 }
4598
4599
4600 /*
4601  * Status management
4602  */
4603
4604 /* Whether or not the curses interface has been initialized. */
4605 static bool cursed = FALSE;
4606
4607 /* The status window is used for polling keystrokes. */
4608 static WINDOW *status_win;
4609
4610 static bool status_empty = TRUE;
4611
4612 /* Update status and title window. */
4613 static void
4614 report(const char *msg, ...)
4615 {
4616         struct view *view = display[current_view];
4617
4618         if (input_mode)
4619                 return;
4620
4621         if (!view) {
4622                 char buf[SIZEOF_STR];
4623                 va_list args;
4624
4625                 va_start(args, msg);
4626                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4627                         buf[sizeof(buf) - 1] = 0;
4628                         buf[sizeof(buf) - 2] = '.';
4629                         buf[sizeof(buf) - 3] = '.';
4630                         buf[sizeof(buf) - 4] = '.';
4631                 }
4632                 va_end(args);
4633                 die("%s", buf);
4634         }
4635
4636         if (!status_empty || *msg) {
4637                 va_list args;
4638
4639                 va_start(args, msg);
4640
4641                 wmove(status_win, 0, 0);
4642                 if (*msg) {
4643                         vwprintw(status_win, msg, args);
4644                         status_empty = FALSE;
4645                 } else {
4646                         status_empty = TRUE;
4647                 }
4648                 wclrtoeol(status_win);
4649                 wrefresh(status_win);
4650
4651                 va_end(args);
4652         }
4653
4654         update_view_title(view);
4655         update_display_cursor(view);
4656 }
4657
4658 /* Controls when nodelay should be in effect when polling user input. */
4659 static void
4660 set_nonblocking_input(bool loading)
4661 {
4662         static unsigned int loading_views;
4663
4664         if ((loading == FALSE && loading_views-- == 1) ||
4665             (loading == TRUE  && loading_views++ == 0))
4666                 nodelay(status_win, loading);
4667 }
4668
4669 static void
4670 init_display(void)
4671 {
4672         int x, y;
4673
4674         /* Initialize the curses library */
4675         if (isatty(STDIN_FILENO)) {
4676                 cursed = !!initscr();
4677         } else {
4678                 /* Leave stdin and stdout alone when acting as a pager. */
4679                 FILE *io = fopen("/dev/tty", "r+");
4680
4681                 if (!io)
4682                         die("Failed to open /dev/tty");
4683                 cursed = !!newterm(NULL, io, io);
4684         }
4685
4686         if (!cursed)
4687                 die("Failed to initialize curses");
4688
4689         nonl();         /* Tell curses not to do NL->CR/NL on output */
4690         cbreak();       /* Take input chars one at a time, no wait for \n */
4691         noecho();       /* Don't echo input */
4692         leaveok(stdscr, TRUE);
4693
4694         if (has_colors())
4695                 init_colors();
4696
4697         getmaxyx(stdscr, y, x);
4698         status_win = newwin(1, 0, y - 1, 0);
4699         if (!status_win)
4700                 die("Failed to create status window");
4701
4702         /* Enable keyboard mapping */
4703         keypad(status_win, TRUE);
4704         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4705 }
4706
4707 static char *
4708 read_prompt(const char *prompt)
4709 {
4710         enum { READING, STOP, CANCEL } status = READING;
4711         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4712         int pos = 0;
4713
4714         while (status == READING) {
4715                 struct view *view;
4716                 int i, key;
4717
4718                 input_mode = TRUE;
4719
4720                 foreach_view (view, i)
4721                         update_view(view);
4722
4723                 input_mode = FALSE;
4724
4725                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4726                 wclrtoeol(status_win);
4727
4728                 /* Refresh, accept single keystroke of input */
4729                 key = wgetch(status_win);
4730                 switch (key) {
4731                 case KEY_RETURN:
4732                 case KEY_ENTER:
4733                 case '\n':
4734                         status = pos ? STOP : CANCEL;
4735                         break;
4736
4737                 case KEY_BACKSPACE:
4738                         if (pos > 0)
4739                                 pos--;
4740                         else
4741                                 status = CANCEL;
4742                         break;
4743
4744                 case KEY_ESC:
4745                         status = CANCEL;
4746                         break;
4747
4748                 case ERR:
4749                         break;
4750
4751                 default:
4752                         if (pos >= sizeof(buf)) {
4753                                 report("Input string too long");
4754                                 return NULL;
4755                         }
4756
4757                         if (isprint(key))
4758                                 buf[pos++] = (char) key;
4759                 }
4760         }
4761
4762         /* Clear the status window */
4763         status_empty = FALSE;
4764         report("");
4765
4766         if (status == CANCEL)
4767                 return NULL;
4768
4769         buf[pos++] = 0;
4770
4771         return buf;
4772 }
4773
4774 /*
4775  * Repository references
4776  */
4777
4778 static struct ref *refs;
4779 static size_t refs_size;
4780
4781 /* Id <-> ref store */
4782 static struct ref ***id_refs;
4783 static size_t id_refs_size;
4784
4785 static struct ref **
4786 get_refs(char *id)
4787 {
4788         struct ref ***tmp_id_refs;
4789         struct ref **ref_list = NULL;
4790         size_t ref_list_size = 0;
4791         size_t i;
4792
4793         for (i = 0; i < id_refs_size; i++)
4794                 if (!strcmp(id, id_refs[i][0]->id))
4795                         return id_refs[i];
4796
4797         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4798         if (!tmp_id_refs)
4799                 return NULL;
4800
4801         id_refs = tmp_id_refs;
4802
4803         for (i = 0; i < refs_size; i++) {
4804                 struct ref **tmp;
4805
4806                 if (strcmp(id, refs[i].id))
4807                         continue;
4808
4809                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4810                 if (!tmp) {
4811                         if (ref_list)
4812                                 free(ref_list);
4813                         return NULL;
4814                 }
4815
4816                 ref_list = tmp;
4817                 if (ref_list_size > 0)
4818                         ref_list[ref_list_size - 1]->next = 1;
4819                 ref_list[ref_list_size] = &refs[i];
4820
4821                 /* XXX: The properties of the commit chains ensures that we can
4822                  * safely modify the shared ref. The repo references will
4823                  * always be similar for the same id. */
4824                 ref_list[ref_list_size]->next = 0;
4825                 ref_list_size++;
4826         }
4827
4828         if (ref_list)
4829                 id_refs[id_refs_size++] = ref_list;
4830
4831         return ref_list;
4832 }
4833
4834 static int
4835 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4836 {
4837         struct ref *ref;
4838         bool tag = FALSE;
4839         bool remote = FALSE;
4840
4841         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4842                 /* Commits referenced by tags has "^{}" appended. */
4843                 if (name[namelen - 1] != '}')
4844                         return OK;
4845
4846                 while (namelen > 0 && name[namelen] != '^')
4847                         namelen--;
4848
4849                 tag = TRUE;
4850                 namelen -= STRING_SIZE("refs/tags/");
4851                 name    += STRING_SIZE("refs/tags/");
4852
4853         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4854                 remote = TRUE;
4855                 namelen -= STRING_SIZE("refs/remotes/");
4856                 name    += STRING_SIZE("refs/remotes/");
4857
4858         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4859                 namelen -= STRING_SIZE("refs/heads/");
4860                 name    += STRING_SIZE("refs/heads/");
4861
4862         } else if (!strcmp(name, "HEAD")) {
4863                 return OK;
4864         }
4865
4866         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4867         if (!refs)
4868                 return ERR;
4869
4870         ref = &refs[refs_size++];
4871         ref->name = malloc(namelen + 1);
4872         if (!ref->name)
4873                 return ERR;
4874
4875         strncpy(ref->name, name, namelen);
4876         ref->name[namelen] = 0;
4877         ref->tag = tag;
4878         ref->remote = remote;
4879         string_copy_rev(ref->id, id);
4880
4881         return OK;
4882 }
4883
4884 static int
4885 load_refs(void)
4886 {
4887         const char *cmd_env = getenv("TIG_LS_REMOTE");
4888         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4889
4890         return read_properties(popen(cmd, "r"), "\t", read_ref);
4891 }
4892
4893 static int
4894 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4895 {
4896         if (!strcmp(name, "i18n.commitencoding"))
4897                 string_ncopy(opt_encoding, value, valuelen);
4898
4899         if (!strcmp(name, "core.editor"))
4900                 string_ncopy(opt_editor, value, valuelen);
4901
4902         return OK;
4903 }
4904
4905 static int
4906 load_repo_config(void)
4907 {
4908         return read_properties(popen(GIT_CONFIG " --list", "r"),
4909                                "=", read_repo_config_option);
4910 }
4911
4912 static int
4913 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4914 {
4915         if (!opt_git_dir[0]) {
4916                 string_ncopy(opt_git_dir, name, namelen);
4917
4918         } else if (opt_is_inside_work_tree == -1) {
4919                 /* This can be 3 different values depending on the
4920                  * version of git being used. If git-rev-parse does not
4921                  * understand --is-inside-work-tree it will simply echo
4922                  * the option else either "true" or "false" is printed.
4923                  * Default to true for the unknown case. */
4924                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4925
4926         } else {
4927                 string_ncopy(opt_cdup, name, namelen);
4928         }
4929
4930         return OK;
4931 }
4932
4933 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4934  * must be the last one! */
4935 static int
4936 load_repo_info(void)
4937 {
4938         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4939                                "=", read_repo_info);
4940 }
4941
4942 static int
4943 read_properties(FILE *pipe, const char *separators,
4944                 int (*read_property)(char *, size_t, char *, size_t))
4945 {
4946         char buffer[BUFSIZ];
4947         char *name;
4948         int state = OK;
4949
4950         if (!pipe)
4951                 return ERR;
4952
4953         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4954                 char *value;
4955                 size_t namelen;
4956                 size_t valuelen;
4957
4958                 name = chomp_string(name);
4959                 namelen = strcspn(name, separators);
4960
4961                 if (name[namelen]) {
4962                         name[namelen] = 0;
4963                         value = chomp_string(name + namelen + 1);
4964                         valuelen = strlen(value);
4965
4966                 } else {
4967                         value = "";
4968                         valuelen = 0;
4969                 }
4970
4971                 state = read_property(name, namelen, value, valuelen);
4972         }
4973
4974         if (state != ERR && ferror(pipe))
4975                 state = ERR;
4976
4977         pclose(pipe);
4978
4979         return state;
4980 }
4981
4982
4983 /*
4984  * Main
4985  */
4986
4987 static void __NORETURN
4988 quit(int sig)
4989 {
4990         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4991         if (cursed)
4992                 endwin();
4993         exit(0);
4994 }
4995
4996 static void __NORETURN
4997 die(const char *err, ...)
4998 {
4999         va_list args;
5000
5001         endwin();
5002
5003         va_start(args, err);
5004         fputs("tig: ", stderr);
5005         vfprintf(stderr, err, args);
5006         fputs("\n", stderr);
5007         va_end(args);
5008
5009         exit(1);
5010 }
5011
5012 static void
5013 warn(const char *msg, ...)
5014 {
5015         va_list args;
5016
5017         va_start(args, msg);
5018         fputs("tig warning: ", stderr);
5019         vfprintf(stderr, msg, args);
5020         fputs("\n", stderr);
5021         va_end(args);
5022 }
5023
5024 int
5025 main(int argc, char *argv[])
5026 {
5027         struct view *view;
5028         enum request request;
5029         size_t i;
5030
5031         signal(SIGINT, quit);
5032
5033         if (setlocale(LC_ALL, "")) {
5034                 char *codeset = nl_langinfo(CODESET);
5035
5036                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5037         }
5038
5039         if (load_repo_info() == ERR)
5040                 die("Failed to load repo info.");
5041
5042         if (load_options() == ERR)
5043                 die("Failed to load user config.");
5044
5045         /* Load the repo config file so options can be overwritten from
5046          * the command line. */
5047         if (load_repo_config() == ERR)
5048                 die("Failed to load repo config.");
5049
5050         if (!parse_options(argc, argv))
5051                 return 0;
5052
5053         /* Require a git repository unless when running in pager mode. */
5054         if (!opt_git_dir[0])
5055                 die("Not a git repository");
5056
5057         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5058                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5059                 if (opt_iconv == ICONV_NONE)
5060                         die("Failed to initialize character set conversion");
5061         }
5062
5063         if (load_refs() == ERR)
5064                 die("Failed to load refs.");
5065
5066         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5067                 view->cmd_env = getenv(view->cmd_env);
5068
5069         request = opt_request;
5070
5071         init_display();
5072
5073         while (view_driver(display[current_view], request)) {
5074                 int key;
5075                 int i;
5076
5077                 foreach_view (view, i)
5078                         update_view(view);
5079
5080                 /* Refresh, accept single keystroke of input */
5081                 key = wgetch(status_win);
5082
5083                 /* wgetch() with nodelay() enabled returns ERR when there's no
5084                  * input. */
5085                 if (key == ERR) {
5086                         request = REQ_NONE;
5087                         continue;
5088                 }
5089
5090                 request = get_keybinding(display[current_view]->keymap, key);
5091
5092                 /* Some low-level request handling. This keeps access to
5093                  * status_win restricted. */
5094                 switch (request) {
5095                 case REQ_PROMPT:
5096                 {
5097                         char *cmd = read_prompt(":");
5098
5099                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5100                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5101                                         opt_request = REQ_VIEW_DIFF;
5102                                 } else {
5103                                         opt_request = REQ_VIEW_PAGER;
5104                                 }
5105                                 break;
5106                         }
5107
5108                         request = REQ_NONE;
5109                         break;
5110                 }
5111                 case REQ_SEARCH:
5112                 case REQ_SEARCH_BACK:
5113                 {
5114                         const char *prompt = request == REQ_SEARCH
5115                                            ? "/" : "?";
5116                         char *search = read_prompt(prompt);
5117
5118                         if (search)
5119                                 string_ncopy(opt_search, search, strlen(search));
5120                         else
5121                                 request = REQ_NONE;
5122                         break;
5123                 }
5124                 case REQ_SCREEN_RESIZE:
5125                 {
5126                         int height, width;
5127
5128                         getmaxyx(stdscr, height, width);
5129
5130                         /* Resize the status view and let the view driver take
5131                          * care of resizing the displayed views. */
5132                         wresize(status_win, 1, width);
5133                         mvwin(status_win, height - 1, 0);
5134                         wrefresh(status_win);
5135                         break;
5136                 }
5137                 default:
5138                         break;
5139                 }
5140         }
5141
5142         quit(0);
5143
5144         return 0;
5145 }