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