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