Ignore REQ_NONE in the help view and improve unbound request handling
[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 == REQ_NONE)
2644                         continue;
2645
2646                 if (!req_info[i].request) {
2647                         add_line_text(view, "", LINE_DEFAULT);
2648                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2649                         continue;
2650                 }
2651
2652                 key = get_key(req_info[i].request);
2653                 if (!*key)
2654                         key = "(no key defined)";
2655
2656                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2657                         continue;
2658
2659                 add_line_text(view, buf, LINE_DEFAULT);
2660         }
2661
2662         return TRUE;
2663 }
2664
2665 static struct view_ops help_ops = {
2666         "line",
2667         help_open,
2668         NULL,
2669         pager_draw,
2670         pager_request,
2671         pager_grep,
2672         pager_select,
2673 };
2674
2675
2676 /*
2677  * Tree backend
2678  */
2679
2680 struct tree_stack_entry {
2681         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2682         unsigned long lineno;           /* Line number to restore */
2683         char *name;                     /* Position of name in opt_path */
2684 };
2685
2686 /* The top of the path stack. */
2687 static struct tree_stack_entry *tree_stack = NULL;
2688 unsigned long tree_lineno = 0;
2689
2690 static void
2691 pop_tree_stack_entry(void)
2692 {
2693         struct tree_stack_entry *entry = tree_stack;
2694
2695         tree_lineno = entry->lineno;
2696         entry->name[0] = 0;
2697         tree_stack = entry->prev;
2698         free(entry);
2699 }
2700
2701 static void
2702 push_tree_stack_entry(char *name, unsigned long lineno)
2703 {
2704         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2705         size_t pathlen = strlen(opt_path);
2706
2707         if (!entry)
2708                 return;
2709
2710         entry->prev = tree_stack;
2711         entry->name = opt_path + pathlen;
2712         tree_stack = entry;
2713
2714         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2715                 pop_tree_stack_entry();
2716                 return;
2717         }
2718
2719         /* Move the current line to the first tree entry. */
2720         tree_lineno = 1;
2721         entry->lineno = lineno;
2722 }
2723
2724 /* Parse output from git-ls-tree(1):
2725  *
2726  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2727  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2728  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2729  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2730  */
2731
2732 #define SIZEOF_TREE_ATTR \
2733         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2734
2735 #define TREE_UP_FORMAT "040000 tree %s\t.."
2736
2737 static int
2738 tree_compare_entry(enum line_type type1, char *name1,
2739                    enum line_type type2, char *name2)
2740 {
2741         if (type1 != type2) {
2742                 if (type1 == LINE_TREE_DIR)
2743                         return -1;
2744                 return 1;
2745         }
2746
2747         return strcmp(name1, name2);
2748 }
2749
2750 static bool
2751 tree_read(struct view *view, char *text)
2752 {
2753         size_t textlen = text ? strlen(text) : 0;
2754         char buf[SIZEOF_STR];
2755         unsigned long pos;
2756         enum line_type type;
2757         bool first_read = view->lines == 0;
2758
2759         if (textlen <= SIZEOF_TREE_ATTR)
2760                 return FALSE;
2761
2762         type = text[STRING_SIZE("100644 ")] == 't'
2763              ? LINE_TREE_DIR : LINE_TREE_FILE;
2764
2765         if (first_read) {
2766                 /* Add path info line */
2767                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2768                     !realloc_lines(view, view->line_size + 1) ||
2769                     !add_line_text(view, buf, LINE_DEFAULT))
2770                         return FALSE;
2771
2772                 /* Insert "link" to parent directory. */
2773                 if (*opt_path) {
2774                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2775                             !realloc_lines(view, view->line_size + 1) ||
2776                             !add_line_text(view, buf, LINE_TREE_DIR))
2777                                 return FALSE;
2778                 }
2779         }
2780
2781         /* Strip the path part ... */
2782         if (*opt_path) {
2783                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2784                 size_t striplen = strlen(opt_path);
2785                 char *path = text + SIZEOF_TREE_ATTR;
2786
2787                 if (pathlen > striplen)
2788                         memmove(path, path + striplen,
2789                                 pathlen - striplen + 1);
2790         }
2791
2792         /* Skip "Directory ..." and ".." line. */
2793         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2794                 struct line *line = &view->line[pos];
2795                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2796                 char *path2 = text + SIZEOF_TREE_ATTR;
2797                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2798
2799                 if (cmp <= 0)
2800                         continue;
2801
2802                 text = strdup(text);
2803                 if (!text)
2804                         return FALSE;
2805
2806                 if (view->lines > pos)
2807                         memmove(&view->line[pos + 1], &view->line[pos],
2808                                 (view->lines - pos) * sizeof(*line));
2809
2810                 line = &view->line[pos];
2811                 line->data = text;
2812                 line->type = type;
2813                 view->lines++;
2814                 return TRUE;
2815         }
2816
2817         if (!add_line_text(view, text, type))
2818                 return FALSE;
2819
2820         if (tree_lineno > view->lineno) {
2821                 view->lineno = tree_lineno;
2822                 tree_lineno = 0;
2823         }
2824
2825         return TRUE;
2826 }
2827
2828 static enum request
2829 tree_request(struct view *view, enum request request, struct line *line)
2830 {
2831         enum open_flags flags;
2832
2833         if (request != REQ_ENTER)
2834                 return request;
2835
2836         /* Cleanup the stack if the tree view is at a different tree. */
2837         while (!*opt_path && tree_stack)
2838                 pop_tree_stack_entry();
2839
2840         switch (line->type) {
2841         case LINE_TREE_DIR:
2842                 /* Depending on whether it is a subdir or parent (updir?) link
2843                  * mangle the path buffer. */
2844                 if (line == &view->line[1] && *opt_path) {
2845                         pop_tree_stack_entry();
2846
2847                 } else {
2848                         char *data = line->data;
2849                         char *basename = data + SIZEOF_TREE_ATTR;
2850
2851                         push_tree_stack_entry(basename, view->lineno);
2852                 }
2853
2854                 /* Trees and subtrees share the same ID, so they are not not
2855                  * unique like blobs. */
2856                 flags = OPEN_RELOAD;
2857                 request = REQ_VIEW_TREE;
2858                 break;
2859
2860         case LINE_TREE_FILE:
2861                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2862                 request = REQ_VIEW_BLOB;
2863                 break;
2864
2865         default:
2866                 return TRUE;
2867         }
2868
2869         open_view(view, request, flags);
2870         if (request == REQ_VIEW_TREE) {
2871                 view->lineno = tree_lineno;
2872         }
2873
2874         return REQ_NONE;
2875 }
2876
2877 static void
2878 tree_select(struct view *view, struct line *line)
2879 {
2880         char *text = line->data + STRING_SIZE("100644 blob ");
2881
2882         if (line->type == LINE_TREE_FILE) {
2883                 string_copy_rev(ref_blob, text);
2884
2885         } else if (line->type != LINE_TREE_DIR) {
2886                 return;
2887         }
2888
2889         string_copy_rev(view->ref, text);
2890 }
2891
2892 static struct view_ops tree_ops = {
2893         "file",
2894         NULL,
2895         tree_read,
2896         pager_draw,
2897         tree_request,
2898         pager_grep,
2899         tree_select,
2900 };
2901
2902 static bool
2903 blob_read(struct view *view, char *line)
2904 {
2905         return add_line_text(view, line, LINE_DEFAULT) != NULL;
2906 }
2907
2908 static struct view_ops blob_ops = {
2909         "line",
2910         NULL,
2911         blob_read,
2912         pager_draw,
2913         pager_request,
2914         pager_grep,
2915         pager_select,
2916 };
2917
2918
2919 /*
2920  * Status backend
2921  */
2922
2923 struct status {
2924         char status;
2925         struct {
2926                 mode_t mode;
2927                 char rev[SIZEOF_REV];
2928         } old;
2929         struct {
2930                 mode_t mode;
2931                 char rev[SIZEOF_REV];
2932         } new;
2933         char name[SIZEOF_STR];
2934 };
2935
2936 static struct status stage_status;
2937 static enum line_type stage_line_type;
2938
2939 /* Get fields from the diff line:
2940  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2941  */
2942 static inline bool
2943 status_get_diff(struct status *file, char *buf, size_t bufsize)
2944 {
2945         char *old_mode = buf +  1;
2946         char *new_mode = buf +  8;
2947         char *old_rev  = buf + 15;
2948         char *new_rev  = buf + 56;
2949         char *status   = buf + 97;
2950
2951         if (bufsize != 99 ||
2952             old_mode[-1] != ':' ||
2953             new_mode[-1] != ' ' ||
2954             old_rev[-1]  != ' ' ||
2955             new_rev[-1]  != ' ' ||
2956             status[-1]   != ' ')
2957                 return FALSE;
2958
2959         file->status = *status;
2960
2961         string_copy_rev(file->old.rev, old_rev);
2962         string_copy_rev(file->new.rev, new_rev);
2963
2964         file->old.mode = strtoul(old_mode, NULL, 8);
2965         file->new.mode = strtoul(new_mode, NULL, 8);
2966
2967         file->name[0] = 0;
2968
2969         return TRUE;
2970 }
2971
2972 static bool
2973 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2974 {
2975         struct status *file = NULL;
2976         char buf[SIZEOF_STR * 4];
2977         size_t bufsize = 0;
2978         FILE *pipe;
2979
2980         pipe = popen(cmd, "r");
2981         if (!pipe)
2982                 return FALSE;
2983
2984         add_line_data(view, NULL, type);
2985
2986         while (!feof(pipe) && !ferror(pipe)) {
2987                 char *sep;
2988                 size_t readsize;
2989
2990                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2991                 if (!readsize)
2992                         break;
2993                 bufsize += readsize;
2994
2995                 /* Process while we have NUL chars. */
2996                 while ((sep = memchr(buf, 0, bufsize))) {
2997                         size_t sepsize = sep - buf + 1;
2998
2999                         if (!file) {
3000                                 if (!realloc_lines(view, view->line_size + 1))
3001                                         goto error_out;
3002
3003                                 file = calloc(1, sizeof(*file));
3004                                 if (!file)
3005                                         goto error_out;
3006
3007                                 add_line_data(view, file, type);
3008                         }
3009
3010                         /* Parse diff info part. */
3011                         if (!diff) {
3012                                 file->status = '?';
3013
3014                         } else if (!file->status) {
3015                                 if (!status_get_diff(file, buf, sepsize))
3016                                         goto error_out;
3017
3018                                 bufsize -= sepsize;
3019                                 memmove(buf, sep + 1, bufsize);
3020
3021                                 sep = memchr(buf, 0, bufsize);
3022                                 if (!sep)
3023                                         break;
3024                                 sepsize = sep - buf + 1;
3025                         }
3026
3027                         /* git-ls-files just delivers a NUL separated
3028                          * list of file names similar to the second half
3029                          * of the git-diff-* output. */
3030                         string_ncopy(file->name, buf, sepsize);
3031                         bufsize -= sepsize;
3032                         memmove(buf, sep + 1, bufsize);
3033                         file = NULL;
3034                 }
3035         }
3036
3037         if (ferror(pipe)) {
3038 error_out:
3039                 pclose(pipe);
3040                 return FALSE;
3041         }
3042
3043         if (!view->line[view->lines - 1].data)
3044                 add_line_data(view, NULL, LINE_STAT_NONE);
3045
3046         pclose(pipe);
3047         return TRUE;
3048 }
3049
3050 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3051 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3052 #define STATUS_LIST_OTHER_CMD \
3053         "git ls-files -z --others --exclude-per-directory=.gitignore"
3054
3055 #define STATUS_DIFF_SHOW_CMD \
3056         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3057
3058 /* First parse staged info using git-diff-index(1), then parse unstaged
3059  * info using git-diff-files(1), and finally untracked files using
3060  * git-ls-files(1). */
3061 static bool
3062 status_open(struct view *view)
3063 {
3064         struct stat statbuf;
3065         char exclude[SIZEOF_STR];
3066         char cmd[SIZEOF_STR];
3067         unsigned long prev_lineno = view->lineno;
3068         size_t i;
3069
3070
3071         for (i = 0; i < view->lines; i++)
3072                 free(view->line[i].data);
3073         free(view->line);
3074         view->lines = view->line_size = view->lineno = 0;
3075         view->line = NULL;
3076
3077         if (!realloc_lines(view, view->line_size + 6))
3078                 return FALSE;
3079
3080         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3081                 return FALSE;
3082
3083         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3084
3085         if (stat(exclude, &statbuf) >= 0) {
3086                 size_t cmdsize = strlen(cmd);
3087
3088                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3089                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3090                         return FALSE;
3091         }
3092
3093         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3094             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3095             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3096                 return FALSE;
3097
3098         /* If all went well restore the previous line number to stay in
3099          * the context. */
3100         if (prev_lineno < view->lines)
3101                 view->lineno = prev_lineno;
3102         else
3103                 view->lineno = view->lines - 1;
3104
3105         return TRUE;
3106 }
3107
3108 static bool
3109 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3110 {
3111         struct status *status = line->data;
3112
3113         wmove(view->win, lineno, 0);
3114
3115         if (selected) {
3116                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3117                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3118
3119         } else if (!status && line->type != LINE_STAT_NONE) {
3120                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3121                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3122
3123         } else {
3124                 wattrset(view->win, get_line_attr(line->type));
3125         }
3126
3127         if (!status) {
3128                 char *text;
3129
3130                 switch (line->type) {
3131                 case LINE_STAT_STAGED:
3132                         text = "Changes to be committed:";
3133                         break;
3134
3135                 case LINE_STAT_UNSTAGED:
3136                         text = "Changed but not updated:";
3137                         break;
3138
3139                 case LINE_STAT_UNTRACKED:
3140                         text = "Untracked files:";
3141                         break;
3142
3143                 case LINE_STAT_NONE:
3144                         text = "    (no files)";
3145                         break;
3146
3147                 default:
3148                         return FALSE;
3149                 }
3150
3151                 waddstr(view->win, text);
3152                 return TRUE;
3153         }
3154
3155         waddch(view->win, status->status);
3156         if (!selected)
3157                 wattrset(view->win, A_NORMAL);
3158         wmove(view->win, lineno, 4);
3159         waddstr(view->win, status->name);
3160
3161         return TRUE;
3162 }
3163
3164 static enum request
3165 status_enter(struct view *view, struct line *line)
3166 {
3167         struct status *status = line->data;
3168         char path[SIZEOF_STR] = "";
3169         char *info;
3170         size_t cmdsize = 0;
3171
3172         if (line->type == LINE_STAT_NONE ||
3173             (!status && line[1].type == LINE_STAT_NONE)) {
3174                 report("No file to diff");
3175                 return REQ_NONE;
3176         }
3177
3178         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3179                 return REQ_QUIT;
3180
3181         if (opt_cdup[0] &&
3182             line->type != LINE_STAT_UNTRACKED &&
3183             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3184                 return REQ_QUIT;
3185
3186         switch (line->type) {
3187         case LINE_STAT_STAGED:
3188                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3189                                         "--cached", path))
3190                         return REQ_QUIT;
3191                 if (status)
3192                         info = "Staged changes to %s";
3193                 else
3194                         info = "Staged changes";
3195                 break;
3196
3197         case LINE_STAT_UNSTAGED:
3198                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3199                                         "", path))
3200                         return REQ_QUIT;
3201                 if (status)
3202                         info = "Unstaged changes to %s";
3203                 else
3204                         info = "Unstaged changes";
3205                 break;
3206
3207         case LINE_STAT_UNTRACKED:
3208                 if (opt_pipe)
3209                         return REQ_QUIT;
3210
3211
3212                 if (!status) {
3213                         report("No file to show");
3214                         return REQ_NONE;
3215                 }
3216
3217                 opt_pipe = fopen(status->name, "r");
3218                 info = "Untracked file %s";
3219                 break;
3220
3221         default:
3222                 die("w00t");
3223         }
3224
3225         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3226         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3227                 if (status) {
3228                         stage_status = *status;
3229                 } else {
3230                         memset(&stage_status, 0, sizeof(stage_status));
3231                 }
3232
3233                 stage_line_type = line->type;
3234                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3235         }
3236
3237         return REQ_NONE;
3238 }
3239
3240
3241 static bool
3242 status_update_file(struct view *view, struct status *status, enum line_type type)
3243 {
3244         char cmd[SIZEOF_STR];
3245         char buf[SIZEOF_STR];
3246         size_t cmdsize = 0;
3247         size_t bufsize = 0;
3248         size_t written = 0;
3249         FILE *pipe;
3250
3251         if (opt_cdup[0] &&
3252             type != LINE_STAT_UNTRACKED &&
3253             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3254                 return FALSE;
3255
3256         switch (type) {
3257         case LINE_STAT_STAGED:
3258                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3259                                         status->old.mode,
3260                                         status->old.rev,
3261                                         status->name, 0))
3262                         return FALSE;
3263
3264                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3265                 break;
3266
3267         case LINE_STAT_UNSTAGED:
3268         case LINE_STAT_UNTRACKED:
3269                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3270                         return FALSE;
3271
3272                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3273                 break;
3274
3275         default:
3276                 die("w00t");
3277         }
3278
3279         pipe = popen(cmd, "w");
3280         if (!pipe)
3281                 return FALSE;
3282
3283         while (!ferror(pipe) && written < bufsize) {
3284                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3285         }
3286
3287         pclose(pipe);
3288
3289         if (written != bufsize)
3290                 return FALSE;
3291
3292         return TRUE;
3293 }
3294
3295 static void
3296 status_update(struct view *view)
3297 {
3298         struct line *line = &view->line[view->lineno];
3299
3300         assert(view->lines);
3301
3302         if (!line->data) {
3303                 while (++line < view->line + view->lines && line->data) {
3304                         if (!status_update_file(view, line->data, line->type))
3305                                 report("Failed to update file status");
3306                 }
3307
3308                 if (!line[-1].data) {
3309                         report("Nothing to update");
3310                         return;
3311                 }
3312
3313         } else if (!status_update_file(view, line->data, line->type)) {
3314                 report("Failed to update file status");
3315         }
3316
3317         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3318 }
3319
3320 static enum request
3321 status_request(struct view *view, enum request request, struct line *line)
3322 {
3323         struct status *status = line->data;
3324
3325         switch (request) {
3326         case REQ_STATUS_UPDATE:
3327                 status_update(view);
3328                 break;
3329
3330         case REQ_EDIT:
3331                 if (!status)
3332                         return request;
3333
3334                 open_editor(status->status != '?', status->name);
3335                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3336                 break;
3337
3338         case REQ_ENTER:
3339                 status_enter(view, line);
3340                 break;
3341
3342         case REQ_REFRESH:
3343                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3344                 break;
3345
3346         default:
3347                 return request;
3348         }
3349
3350         return REQ_NONE;
3351 }
3352
3353 static void
3354 status_select(struct view *view, struct line *line)
3355 {
3356         struct status *status = line->data;
3357         char file[SIZEOF_STR] = "all files";
3358         char *text;
3359
3360         if (status && !string_format(file, "'%s'", status->name))
3361                 return;
3362
3363         if (!status && line[1].type == LINE_STAT_NONE)
3364                 line++;
3365
3366         switch (line->type) {
3367         case LINE_STAT_STAGED:
3368                 text = "Press %s to unstage %s for commit";
3369                 break;
3370
3371         case LINE_STAT_UNSTAGED:
3372                 text = "Press %s to stage %s for commit";
3373                 break;
3374
3375         case LINE_STAT_UNTRACKED:
3376                 text = "Press %s to stage %s for addition";
3377                 break;
3378
3379         case LINE_STAT_NONE:
3380                 text = "Nothing to update";
3381                 break;
3382
3383         default:
3384                 die("w00t");
3385         }
3386
3387         string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3388 }
3389
3390 static bool
3391 status_grep(struct view *view, struct line *line)
3392 {
3393         struct status *status = line->data;
3394         enum { S_STATUS, S_NAME, S_END } state;
3395         char buf[2] = "?";
3396         regmatch_t pmatch;
3397
3398         if (!status)
3399                 return FALSE;
3400
3401         for (state = S_STATUS; state < S_END; state++) {
3402                 char *text;
3403
3404                 switch (state) {
3405                 case S_NAME:    text = status->name;    break;
3406                 case S_STATUS:
3407                         buf[0] = status->status;
3408                         text = buf;
3409                         break;
3410
3411                 default:
3412                         return FALSE;
3413                 }
3414
3415                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3416                         return TRUE;
3417         }
3418
3419         return FALSE;
3420 }
3421
3422 static struct view_ops status_ops = {
3423         "file",
3424         status_open,
3425         NULL,
3426         status_draw,
3427         status_request,
3428         status_grep,
3429         status_select,
3430 };
3431
3432
3433 static bool
3434 stage_diff_line(FILE *pipe, struct line *line)
3435 {
3436         char *buf = line->data;
3437         size_t bufsize = strlen(buf);
3438         size_t written = 0;
3439
3440         while (!ferror(pipe) && written < bufsize) {
3441                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3442         }
3443
3444         fputc('\n', pipe);
3445
3446         return written == bufsize;
3447 }
3448
3449 static struct line *
3450 stage_diff_hdr(struct view *view, struct line *line)
3451 {
3452         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3453         struct line *diff_hdr;
3454
3455         if (line->type == LINE_DIFF_CHUNK)
3456                 diff_hdr = line - 1;
3457         else
3458                 diff_hdr = view->line + 1;
3459
3460         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3461                 if (diff_hdr->type == LINE_DIFF_HEADER)
3462                         return diff_hdr;
3463
3464                 diff_hdr += diff_hdr_dir;
3465         }
3466
3467         return NULL;
3468 }
3469
3470 static bool
3471 stage_update_chunk(struct view *view, struct line *line)
3472 {
3473         char cmd[SIZEOF_STR];
3474         size_t cmdsize = 0;
3475         struct line *diff_hdr, *diff_chunk, *diff_end;
3476         FILE *pipe;
3477
3478         diff_hdr = stage_diff_hdr(view, line);
3479         if (!diff_hdr)
3480                 return FALSE;
3481
3482         if (opt_cdup[0] &&
3483             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3484                 return FALSE;
3485
3486         if (!string_format_from(cmd, &cmdsize,
3487                                 "git apply --cached %s - && "
3488                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3489                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3490                 return FALSE;
3491
3492         pipe = popen(cmd, "w");
3493         if (!pipe)
3494                 return FALSE;
3495
3496         diff_end = view->line + view->lines;
3497         if (line->type != LINE_DIFF_CHUNK) {
3498                 diff_chunk = diff_hdr;
3499
3500         } else {
3501                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3502                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3503                             diff_chunk->type == LINE_DIFF_HEADER)
3504                                 diff_end = diff_chunk;
3505
3506                 diff_chunk = line;
3507
3508                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3509                         switch (diff_hdr->type) {
3510                         case LINE_DIFF_HEADER:
3511                         case LINE_DIFF_INDEX:
3512                         case LINE_DIFF_ADD:
3513                         case LINE_DIFF_DEL:
3514                                 break;
3515
3516                         default:
3517                                 diff_hdr++;
3518                                 continue;
3519                         }
3520
3521                         if (!stage_diff_line(pipe, diff_hdr++)) {
3522                                 pclose(pipe);
3523                                 return FALSE;
3524                         }
3525                 }
3526         }
3527
3528         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3529                 diff_chunk++;
3530
3531         pclose(pipe);
3532
3533         if (diff_chunk != diff_end)
3534                 return FALSE;
3535
3536         return TRUE;
3537 }
3538
3539 static void
3540 stage_update(struct view *view, struct line *line)
3541 {
3542         if (stage_line_type != LINE_STAT_UNTRACKED &&
3543             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3544                 if (!stage_update_chunk(view, line)) {
3545                         report("Failed to apply chunk");
3546                         return;
3547                 }
3548
3549         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3550                 report("Failed to update file");
3551                 return;
3552         }
3553
3554         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3555
3556         view = VIEW(REQ_VIEW_STATUS);
3557         if (view_is_displayed(view))
3558                 status_enter(view, &view->line[view->lineno]);
3559 }
3560
3561 static enum request
3562 stage_request(struct view *view, enum request request, struct line *line)
3563 {
3564         switch (request) {
3565         case REQ_STATUS_UPDATE:
3566                 stage_update(view, line);
3567                 break;
3568
3569         case REQ_EDIT:
3570                 if (!stage_status.name[0])
3571                         return request;
3572
3573                 open_editor(stage_status.status != '?', stage_status.name);
3574                 break;
3575
3576         case REQ_ENTER:
3577                 pager_request(view, request, line);
3578                 break;
3579
3580         default:
3581                 return request;
3582         }
3583
3584         return REQ_NONE;
3585 }
3586
3587 static struct view_ops stage_ops = {
3588         "line",
3589         NULL,
3590         pager_read,
3591         pager_draw,
3592         stage_request,
3593         pager_grep,
3594         pager_select,
3595 };
3596
3597
3598 /*
3599  * Revision graph
3600  */
3601
3602 struct commit {
3603         char id[SIZEOF_REV];            /* SHA1 ID. */
3604         char title[128];                /* First line of the commit message. */
3605         char author[75];                /* Author of the commit. */
3606         struct tm time;                 /* Date from the author ident. */
3607         struct ref **refs;              /* Repository references. */
3608         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3609         size_t graph_size;              /* The width of the graph array. */
3610 };
3611
3612 /* Size of rev graph with no  "padding" columns */
3613 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3614
3615 struct rev_graph {
3616         struct rev_graph *prev, *next, *parents;
3617         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3618         size_t size;
3619         struct commit *commit;
3620         size_t pos;
3621 };
3622
3623 /* Parents of the commit being visualized. */
3624 static struct rev_graph graph_parents[4];
3625
3626 /* The current stack of revisions on the graph. */
3627 static struct rev_graph graph_stacks[4] = {
3628         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3629         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3630         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3631         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3632 };
3633
3634 static inline bool
3635 graph_parent_is_merge(struct rev_graph *graph)
3636 {
3637         return graph->parents->size > 1;
3638 }
3639
3640 static inline void
3641 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3642 {
3643         struct commit *commit = graph->commit;
3644
3645         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3646                 commit->graph[commit->graph_size++] = symbol;
3647 }
3648
3649 static void
3650 done_rev_graph(struct rev_graph *graph)
3651 {
3652         if (graph_parent_is_merge(graph) &&
3653             graph->pos < graph->size - 1 &&
3654             graph->next->size == graph->size + graph->parents->size - 1) {
3655                 size_t i = graph->pos + graph->parents->size - 1;
3656
3657                 graph->commit->graph_size = i * 2;
3658                 while (i < graph->next->size - 1) {
3659                         append_to_rev_graph(graph, ' ');
3660                         append_to_rev_graph(graph, '\\');
3661                         i++;
3662                 }
3663         }
3664
3665         graph->size = graph->pos = 0;
3666         graph->commit = NULL;
3667         memset(graph->parents, 0, sizeof(*graph->parents));
3668 }
3669
3670 static void
3671 push_rev_graph(struct rev_graph *graph, char *parent)
3672 {
3673         int i;
3674
3675         /* "Collapse" duplicate parents lines.
3676          *
3677          * FIXME: This needs to also update update the drawn graph but
3678          * for now it just serves as a method for pruning graph lines. */
3679         for (i = 0; i < graph->size; i++)
3680                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3681                         return;
3682
3683         if (graph->size < SIZEOF_REVITEMS) {
3684                 string_copy_rev(graph->rev[graph->size++], parent);
3685         }
3686 }
3687
3688 static chtype
3689 get_rev_graph_symbol(struct rev_graph *graph)
3690 {
3691         chtype symbol;
3692
3693         if (graph->parents->size == 0)
3694                 symbol = REVGRAPH_INIT;
3695         else if (graph_parent_is_merge(graph))
3696                 symbol = REVGRAPH_MERGE;
3697         else if (graph->pos >= graph->size)
3698                 symbol = REVGRAPH_BRANCH;
3699         else
3700                 symbol = REVGRAPH_COMMIT;
3701
3702         return symbol;
3703 }
3704
3705 static void
3706 draw_rev_graph(struct rev_graph *graph)
3707 {
3708         struct rev_filler {
3709                 chtype separator, line;
3710         };
3711         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3712         static struct rev_filler fillers[] = {
3713                 { ' ',  REVGRAPH_LINE },
3714                 { '`',  '.' },
3715                 { '\'', ' ' },
3716                 { '/',  ' ' },
3717         };
3718         chtype symbol = get_rev_graph_symbol(graph);
3719         struct rev_filler *filler;
3720         size_t i;
3721
3722         filler = &fillers[DEFAULT];
3723
3724         for (i = 0; i < graph->pos; i++) {
3725                 append_to_rev_graph(graph, filler->line);
3726                 if (graph_parent_is_merge(graph->prev) &&
3727                     graph->prev->pos == i)
3728                         filler = &fillers[RSHARP];
3729
3730                 append_to_rev_graph(graph, filler->separator);
3731         }
3732
3733         /* Place the symbol for this revision. */
3734         append_to_rev_graph(graph, symbol);
3735
3736         if (graph->prev->size > graph->size)
3737                 filler = &fillers[RDIAG];
3738         else
3739                 filler = &fillers[DEFAULT];
3740
3741         i++;
3742
3743         for (; i < graph->size; i++) {
3744                 append_to_rev_graph(graph, filler->separator);
3745                 append_to_rev_graph(graph, filler->line);
3746                 if (graph_parent_is_merge(graph->prev) &&
3747                     i < graph->prev->pos + graph->parents->size)
3748                         filler = &fillers[RSHARP];
3749                 if (graph->prev->size > graph->size)
3750                         filler = &fillers[LDIAG];
3751         }
3752
3753         if (graph->prev->size > graph->size) {
3754                 append_to_rev_graph(graph, filler->separator);
3755                 if (filler->line != ' ')
3756                         append_to_rev_graph(graph, filler->line);
3757         }
3758 }
3759
3760 /* Prepare the next rev graph */
3761 static void
3762 prepare_rev_graph(struct rev_graph *graph)
3763 {
3764         size_t i;
3765
3766         /* First, traverse all lines of revisions up to the active one. */
3767         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3768                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3769                         break;
3770
3771                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3772         }
3773
3774         /* Interleave the new revision parent(s). */
3775         for (i = 0; i < graph->parents->size; i++)
3776                 push_rev_graph(graph->next, graph->parents->rev[i]);
3777
3778         /* Lastly, put any remaining revisions. */
3779         for (i = graph->pos + 1; i < graph->size; i++)
3780                 push_rev_graph(graph->next, graph->rev[i]);
3781 }
3782
3783 static void
3784 update_rev_graph(struct rev_graph *graph)
3785 {
3786         /* If this is the finalizing update ... */
3787         if (graph->commit)
3788                 prepare_rev_graph(graph);
3789
3790         /* Graph visualization needs a one rev look-ahead,
3791          * so the first update doesn't visualize anything. */
3792         if (!graph->prev->commit)
3793                 return;
3794
3795         draw_rev_graph(graph->prev);
3796         done_rev_graph(graph->prev->prev);
3797 }
3798
3799
3800 /*
3801  * Main view backend
3802  */
3803
3804 static bool
3805 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3806 {
3807         char buf[DATE_COLS + 1];
3808         struct commit *commit = line->data;
3809         enum line_type type;
3810         int col = 0;
3811         size_t timelen;
3812         size_t authorlen;
3813         int trimmed = 1;
3814
3815         if (!*commit->author)
3816                 return FALSE;
3817
3818         wmove(view->win, lineno, col);
3819
3820         if (selected) {
3821                 type = LINE_CURSOR;
3822                 wattrset(view->win, get_line_attr(type));
3823                 wchgat(view->win, -1, 0, type, NULL);
3824
3825         } else {
3826                 type = LINE_MAIN_COMMIT;
3827                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3828         }
3829
3830         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3831         waddnstr(view->win, buf, timelen);
3832         waddstr(view->win, " ");
3833
3834         col += DATE_COLS;
3835         wmove(view->win, lineno, col);
3836         if (type != LINE_CURSOR)
3837                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3838
3839         if (opt_utf8) {
3840                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3841         } else {
3842                 authorlen = strlen(commit->author);
3843                 if (authorlen > AUTHOR_COLS - 2) {
3844                         authorlen = AUTHOR_COLS - 2;
3845                         trimmed = 1;
3846                 }
3847         }
3848
3849         if (trimmed) {
3850                 waddnstr(view->win, commit->author, authorlen);
3851                 if (type != LINE_CURSOR)
3852                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3853                 waddch(view->win, '~');
3854         } else {
3855                 waddstr(view->win, commit->author);
3856         }
3857
3858         col += AUTHOR_COLS;
3859         if (type != LINE_CURSOR)
3860                 wattrset(view->win, A_NORMAL);
3861
3862         if (opt_rev_graph && commit->graph_size) {
3863                 size_t i;
3864
3865                 wmove(view->win, lineno, col);
3866                 /* Using waddch() instead of waddnstr() ensures that
3867                  * they'll be rendered correctly for the cursor line. */
3868                 for (i = 0; i < commit->graph_size; i++)
3869                         waddch(view->win, commit->graph[i]);
3870
3871                 waddch(view->win, ' ');
3872                 col += commit->graph_size + 1;
3873         }
3874
3875         wmove(view->win, lineno, col);
3876
3877         if (commit->refs) {
3878                 size_t i = 0;
3879
3880                 do {
3881                         if (type == LINE_CURSOR)
3882                                 ;
3883                         else if (commit->refs[i]->tag)
3884                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3885                         else if (commit->refs[i]->remote)
3886                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3887                         else
3888                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3889                         waddstr(view->win, "[");
3890                         waddstr(view->win, commit->refs[i]->name);
3891                         waddstr(view->win, "]");
3892                         if (type != LINE_CURSOR)
3893                                 wattrset(view->win, A_NORMAL);
3894                         waddstr(view->win, " ");
3895                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3896                 } while (commit->refs[i++]->next);
3897         }
3898
3899         if (type != LINE_CURSOR)
3900                 wattrset(view->win, get_line_attr(type));
3901
3902         {
3903                 int titlelen = strlen(commit->title);
3904
3905                 if (col + titlelen > view->width)
3906                         titlelen = view->width - col;
3907
3908                 waddnstr(view->win, commit->title, titlelen);
3909         }
3910
3911         return TRUE;
3912 }
3913
3914 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3915 static bool
3916 main_read(struct view *view, char *line)
3917 {
3918         static struct rev_graph *graph = graph_stacks;
3919         enum line_type type;
3920         struct commit *commit;
3921
3922         if (!line) {
3923                 update_rev_graph(graph);
3924                 return TRUE;
3925         }
3926
3927         type = get_line_type(line);
3928         if (type == LINE_COMMIT) {
3929                 commit = calloc(1, sizeof(struct commit));
3930                 if (!commit)
3931                         return FALSE;
3932
3933                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3934                 commit->refs = get_refs(commit->id);
3935                 graph->commit = commit;
3936                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3937                 return TRUE;
3938         }
3939
3940         if (!view->lines)
3941                 return TRUE;
3942         commit = view->line[view->lines - 1].data;
3943
3944         switch (type) {
3945         case LINE_PARENT:
3946                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3947                 break;
3948
3949         case LINE_AUTHOR:
3950         {
3951                 /* Parse author lines where the name may be empty:
3952                  *      author  <email@address.tld> 1138474660 +0100
3953                  */
3954                 char *ident = line + STRING_SIZE("author ");
3955                 char *nameend = strchr(ident, '<');
3956                 char *emailend = strchr(ident, '>');
3957
3958                 if (!nameend || !emailend)
3959                         break;
3960
3961                 update_rev_graph(graph);
3962                 graph = graph->next;
3963
3964                 *nameend = *emailend = 0;
3965                 ident = chomp_string(ident);
3966                 if (!*ident) {
3967                         ident = chomp_string(nameend + 1);
3968                         if (!*ident)
3969                                 ident = "Unknown";
3970                 }
3971
3972                 string_ncopy(commit->author, ident, strlen(ident));
3973
3974                 /* Parse epoch and timezone */
3975                 if (emailend[1] == ' ') {
3976                         char *secs = emailend + 2;
3977                         char *zone = strchr(secs, ' ');
3978                         time_t time = (time_t) atol(secs);
3979
3980                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3981                                 long tz;
3982
3983                                 zone++;
3984                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3985                                 tz += ('0' - zone[2]) * 60 * 60;
3986                                 tz += ('0' - zone[3]) * 60;
3987                                 tz += ('0' - zone[4]) * 60;
3988
3989                                 if (zone[0] == '-')
3990                                         tz = -tz;
3991
3992                                 time -= tz;
3993                         }
3994
3995                         gmtime_r(&time, &commit->time);
3996                 }
3997                 break;
3998         }
3999         default:
4000                 /* Fill in the commit title if it has not already been set. */
4001                 if (commit->title[0])
4002                         break;
4003
4004                 /* Require titles to start with a non-space character at the
4005                  * offset used by git log. */
4006                 if (strncmp(line, "    ", 4))
4007                         break;
4008                 line += 4;
4009                 /* Well, if the title starts with a whitespace character,
4010                  * try to be forgiving.  Otherwise we end up with no title. */
4011                 while (isspace(*line))
4012                         line++;
4013                 if (*line == '\0')
4014                         break;
4015                 /* FIXME: More graceful handling of titles; append "..." to
4016                  * shortened titles, etc. */
4017
4018                 string_ncopy(commit->title, line, strlen(line));
4019         }
4020
4021         return TRUE;
4022 }
4023
4024 static void
4025 cherry_pick_commit(struct commit *commit)
4026 {
4027         char cmd[SIZEOF_STR];
4028         char *cherry_pick = getenv("TIG_CHERRY_PICK");
4029
4030         if (!cherry_pick)
4031                 cherry_pick = "git cherry-pick";
4032
4033         if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4034                 def_prog_mode();           /* save current tty modes */
4035                 endwin();                  /* restore original tty modes */
4036                 system(cmd);
4037                 fprintf(stderr, "Press Enter to continue");
4038                 getc(stdin);
4039                 reset_prog_mode();
4040                 redraw_display();
4041         }
4042 }
4043
4044 static enum request
4045 main_request(struct view *view, enum request request, struct line *line)
4046 {
4047         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4048
4049         if (request == REQ_ENTER)
4050                 open_view(view, REQ_VIEW_DIFF, flags);
4051         else if (request == REQ_CHERRY_PICK)
4052                 cherry_pick_commit(line->data);
4053         else
4054                 return request;
4055
4056         return REQ_NONE;
4057 }
4058
4059 static bool
4060 main_grep(struct view *view, struct line *line)
4061 {
4062         struct commit *commit = line->data;
4063         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4064         char buf[DATE_COLS + 1];
4065         regmatch_t pmatch;
4066
4067         for (state = S_TITLE; state < S_END; state++) {
4068                 char *text;
4069
4070                 switch (state) {
4071                 case S_TITLE:   text = commit->title;   break;
4072                 case S_AUTHOR:  text = commit->author;  break;
4073                 case S_DATE:
4074                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4075                                 continue;
4076                         text = buf;
4077                         break;
4078
4079                 default:
4080                         return FALSE;
4081                 }
4082
4083                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4084                         return TRUE;
4085         }
4086
4087         return FALSE;
4088 }
4089
4090 static void
4091 main_select(struct view *view, struct line *line)
4092 {
4093         struct commit *commit = line->data;
4094
4095         string_copy_rev(view->ref, commit->id);
4096         string_copy_rev(ref_commit, view->ref);
4097 }
4098
4099 static struct view_ops main_ops = {
4100         "commit",
4101         NULL,
4102         main_read,
4103         main_draw,
4104         main_request,
4105         main_grep,
4106         main_select,
4107 };
4108
4109
4110 /*
4111  * Unicode / UTF-8 handling
4112  *
4113  * NOTE: Much of the following code for dealing with unicode is derived from
4114  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4115  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4116  */
4117
4118 /* I've (over)annotated a lot of code snippets because I am not entirely
4119  * confident that the approach taken by this small UTF-8 interface is correct.
4120  * --jonas */
4121
4122 static inline int
4123 unicode_width(unsigned long c)
4124 {
4125         if (c >= 0x1100 &&
4126            (c <= 0x115f                         /* Hangul Jamo */
4127             || c == 0x2329
4128             || c == 0x232a
4129             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4130                                                 /* CJK ... Yi */
4131             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4132             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4133             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4134             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4135             || (c >= 0xffe0  && c <= 0xffe6)
4136             || (c >= 0x20000 && c <= 0x2fffd)
4137             || (c >= 0x30000 && c <= 0x3fffd)))
4138                 return 2;
4139
4140         return 1;
4141 }
4142
4143 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4144  * Illegal bytes are set one. */
4145 static const unsigned char utf8_bytes[256] = {
4146         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,
4147         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,
4148         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,
4149         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,
4150         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,
4151         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,
4152         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,
4153         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,
4154 };
4155
4156 /* Decode UTF-8 multi-byte representation into a unicode character. */
4157 static inline unsigned long
4158 utf8_to_unicode(const char *string, size_t length)
4159 {
4160         unsigned long unicode;
4161
4162         switch (length) {
4163         case 1:
4164                 unicode  =   string[0];
4165                 break;
4166         case 2:
4167                 unicode  =  (string[0] & 0x1f) << 6;
4168                 unicode +=  (string[1] & 0x3f);
4169                 break;
4170         case 3:
4171                 unicode  =  (string[0] & 0x0f) << 12;
4172                 unicode += ((string[1] & 0x3f) << 6);
4173                 unicode +=  (string[2] & 0x3f);
4174                 break;
4175         case 4:
4176                 unicode  =  (string[0] & 0x0f) << 18;
4177                 unicode += ((string[1] & 0x3f) << 12);
4178                 unicode += ((string[2] & 0x3f) << 6);
4179                 unicode +=  (string[3] & 0x3f);
4180                 break;
4181         case 5:
4182                 unicode  =  (string[0] & 0x0f) << 24;
4183                 unicode += ((string[1] & 0x3f) << 18);
4184                 unicode += ((string[2] & 0x3f) << 12);
4185                 unicode += ((string[3] & 0x3f) << 6);
4186                 unicode +=  (string[4] & 0x3f);
4187                 break;
4188         case 6:
4189                 unicode  =  (string[0] & 0x01) << 30;
4190                 unicode += ((string[1] & 0x3f) << 24);
4191                 unicode += ((string[2] & 0x3f) << 18);
4192                 unicode += ((string[3] & 0x3f) << 12);
4193                 unicode += ((string[4] & 0x3f) << 6);
4194                 unicode +=  (string[5] & 0x3f);
4195                 break;
4196         default:
4197                 die("Invalid unicode length");
4198         }
4199
4200         /* Invalid characters could return the special 0xfffd value but NUL
4201          * should be just as good. */
4202         return unicode > 0xffff ? 0 : unicode;
4203 }
4204
4205 /* Calculates how much of string can be shown within the given maximum width
4206  * and sets trimmed parameter to non-zero value if all of string could not be
4207  * shown.
4208  *
4209  * Additionally, adds to coloffset how many many columns to move to align with
4210  * the expected position. Takes into account how multi-byte and double-width
4211  * characters will effect the cursor position.
4212  *
4213  * Returns the number of bytes to output from string to satisfy max_width. */
4214 static size_t
4215 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4216 {
4217         const char *start = string;
4218         const char *end = strchr(string, '\0');
4219         size_t mbwidth = 0;
4220         size_t width = 0;
4221
4222         *trimmed = 0;
4223
4224         while (string < end) {
4225                 int c = *(unsigned char *) string;
4226                 unsigned char bytes = utf8_bytes[c];
4227                 size_t ucwidth;
4228                 unsigned long unicode;
4229
4230                 if (string + bytes > end)
4231                         break;
4232
4233                 /* Change representation to figure out whether
4234                  * it is a single- or double-width character. */
4235
4236                 unicode = utf8_to_unicode(string, bytes);
4237                 /* FIXME: Graceful handling of invalid unicode character. */
4238                 if (!unicode)
4239                         break;
4240
4241                 ucwidth = unicode_width(unicode);
4242                 width  += ucwidth;
4243                 if (width > max_width) {
4244                         *trimmed = 1;
4245                         break;
4246                 }
4247
4248                 /* The column offset collects the differences between the
4249                  * number of bytes encoding a character and the number of
4250                  * columns will be used for rendering said character.
4251                  *
4252                  * So if some character A is encoded in 2 bytes, but will be
4253                  * represented on the screen using only 1 byte this will and up
4254                  * adding 1 to the multi-byte column offset.
4255                  *
4256                  * Assumes that no double-width character can be encoding in
4257                  * less than two bytes. */
4258                 if (bytes > ucwidth)
4259                         mbwidth += bytes - ucwidth;
4260
4261                 string  += bytes;
4262         }
4263
4264         *coloffset += mbwidth;
4265
4266         return string - start;
4267 }
4268
4269
4270 /*
4271  * Status management
4272  */
4273
4274 /* Whether or not the curses interface has been initialized. */
4275 static bool cursed = FALSE;
4276
4277 /* The status window is used for polling keystrokes. */
4278 static WINDOW *status_win;
4279
4280 static bool status_empty = TRUE;
4281
4282 /* Update status and title window. */
4283 static void
4284 report(const char *msg, ...)
4285 {
4286         struct view *view = display[current_view];
4287
4288         if (input_mode)
4289                 return;
4290
4291         if (!status_empty || *msg) {
4292                 va_list args;
4293
4294                 va_start(args, msg);
4295
4296                 wmove(status_win, 0, 0);
4297                 if (*msg) {
4298                         vwprintw(status_win, msg, args);
4299                         status_empty = FALSE;
4300                 } else {
4301                         status_empty = TRUE;
4302                 }
4303                 wclrtoeol(status_win);
4304                 wrefresh(status_win);
4305
4306                 va_end(args);
4307         }
4308
4309         update_view_title(view);
4310         update_display_cursor(view);
4311 }
4312
4313 /* Controls when nodelay should be in effect when polling user input. */
4314 static void
4315 set_nonblocking_input(bool loading)
4316 {
4317         static unsigned int loading_views;
4318
4319         if ((loading == FALSE && loading_views-- == 1) ||
4320             (loading == TRUE  && loading_views++ == 0))
4321                 nodelay(status_win, loading);
4322 }
4323
4324 static void
4325 init_display(void)
4326 {
4327         int x, y;
4328
4329         /* Initialize the curses library */
4330         if (isatty(STDIN_FILENO)) {
4331                 cursed = !!initscr();
4332         } else {
4333                 /* Leave stdin and stdout alone when acting as a pager. */
4334                 FILE *io = fopen("/dev/tty", "r+");
4335
4336                 if (!io)
4337                         die("Failed to open /dev/tty");
4338                 cursed = !!newterm(NULL, io, io);
4339         }
4340
4341         if (!cursed)
4342                 die("Failed to initialize curses");
4343
4344         nonl();         /* Tell curses not to do NL->CR/NL on output */
4345         cbreak();       /* Take input chars one at a time, no wait for \n */
4346         noecho();       /* Don't echo input */
4347         leaveok(stdscr, TRUE);
4348
4349         if (has_colors())
4350                 init_colors();
4351
4352         getmaxyx(stdscr, y, x);
4353         status_win = newwin(1, 0, y - 1, 0);
4354         if (!status_win)
4355                 die("Failed to create status window");
4356
4357         /* Enable keyboard mapping */
4358         keypad(status_win, TRUE);
4359         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4360 }
4361
4362 static char *
4363 read_prompt(const char *prompt)
4364 {
4365         enum { READING, STOP, CANCEL } status = READING;
4366         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4367         int pos = 0;
4368
4369         while (status == READING) {
4370                 struct view *view;
4371                 int i, key;
4372
4373                 input_mode = TRUE;
4374
4375                 foreach_view (view, i)
4376                         update_view(view);
4377
4378                 input_mode = FALSE;
4379
4380                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4381                 wclrtoeol(status_win);
4382
4383                 /* Refresh, accept single keystroke of input */
4384                 key = wgetch(status_win);
4385                 switch (key) {
4386                 case KEY_RETURN:
4387                 case KEY_ENTER:
4388                 case '\n':
4389                         status = pos ? STOP : CANCEL;
4390                         break;
4391
4392                 case KEY_BACKSPACE:
4393                         if (pos > 0)
4394                                 pos--;
4395                         else
4396                                 status = CANCEL;
4397                         break;
4398
4399                 case KEY_ESC:
4400                         status = CANCEL;
4401                         break;
4402
4403                 case ERR:
4404                         break;
4405
4406                 default:
4407                         if (pos >= sizeof(buf)) {
4408                                 report("Input string too long");
4409                                 return NULL;
4410                         }
4411
4412                         if (isprint(key))
4413                                 buf[pos++] = (char) key;
4414                 }
4415         }
4416
4417         /* Clear the status window */
4418         status_empty = FALSE;
4419         report("");
4420
4421         if (status == CANCEL)
4422                 return NULL;
4423
4424         buf[pos++] = 0;
4425
4426         return buf;
4427 }
4428
4429 /*
4430  * Repository references
4431  */
4432
4433 static struct ref *refs;
4434 static size_t refs_size;
4435
4436 /* Id <-> ref store */
4437 static struct ref ***id_refs;
4438 static size_t id_refs_size;
4439
4440 static struct ref **
4441 get_refs(char *id)
4442 {
4443         struct ref ***tmp_id_refs;
4444         struct ref **ref_list = NULL;
4445         size_t ref_list_size = 0;
4446         size_t i;
4447
4448         for (i = 0; i < id_refs_size; i++)
4449                 if (!strcmp(id, id_refs[i][0]->id))
4450                         return id_refs[i];
4451
4452         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4453         if (!tmp_id_refs)
4454                 return NULL;
4455
4456         id_refs = tmp_id_refs;
4457
4458         for (i = 0; i < refs_size; i++) {
4459                 struct ref **tmp;
4460
4461                 if (strcmp(id, refs[i].id))
4462                         continue;
4463
4464                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4465                 if (!tmp) {
4466                         if (ref_list)
4467                                 free(ref_list);
4468                         return NULL;
4469                 }
4470
4471                 ref_list = tmp;
4472                 if (ref_list_size > 0)
4473                         ref_list[ref_list_size - 1]->next = 1;
4474                 ref_list[ref_list_size] = &refs[i];
4475
4476                 /* XXX: The properties of the commit chains ensures that we can
4477                  * safely modify the shared ref. The repo references will
4478                  * always be similar for the same id. */
4479                 ref_list[ref_list_size]->next = 0;
4480                 ref_list_size++;
4481         }
4482
4483         if (ref_list)
4484                 id_refs[id_refs_size++] = ref_list;
4485
4486         return ref_list;
4487 }
4488
4489 static int
4490 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4491 {
4492         struct ref *ref;
4493         bool tag = FALSE;
4494         bool remote = FALSE;
4495
4496         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4497                 /* Commits referenced by tags has "^{}" appended. */
4498                 if (name[namelen - 1] != '}')
4499                         return OK;
4500
4501                 while (namelen > 0 && name[namelen] != '^')
4502                         namelen--;
4503
4504                 tag = TRUE;
4505                 namelen -= STRING_SIZE("refs/tags/");
4506                 name    += STRING_SIZE("refs/tags/");
4507
4508         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4509                 remote = TRUE;
4510                 namelen -= STRING_SIZE("refs/remotes/");
4511                 name    += STRING_SIZE("refs/remotes/");
4512
4513         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4514                 namelen -= STRING_SIZE("refs/heads/");
4515                 name    += STRING_SIZE("refs/heads/");
4516
4517         } else if (!strcmp(name, "HEAD")) {
4518                 return OK;
4519         }
4520
4521         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4522         if (!refs)
4523                 return ERR;
4524
4525         ref = &refs[refs_size++];
4526         ref->name = malloc(namelen + 1);
4527         if (!ref->name)
4528                 return ERR;
4529
4530         strncpy(ref->name, name, namelen);
4531         ref->name[namelen] = 0;
4532         ref->tag = tag;
4533         ref->remote = remote;
4534         string_copy_rev(ref->id, id);
4535
4536         return OK;
4537 }
4538
4539 static int
4540 load_refs(void)
4541 {
4542         const char *cmd_env = getenv("TIG_LS_REMOTE");
4543         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4544
4545         return read_properties(popen(cmd, "r"), "\t", read_ref);
4546 }
4547
4548 static int
4549 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4550 {
4551         if (!strcmp(name, "i18n.commitencoding"))
4552                 string_ncopy(opt_encoding, value, valuelen);
4553
4554         if (!strcmp(name, "core.editor"))
4555                 string_ncopy(opt_editor, value, valuelen);
4556
4557         return OK;
4558 }
4559
4560 static int
4561 load_repo_config(void)
4562 {
4563         return read_properties(popen(GIT_CONFIG " --list", "r"),
4564                                "=", read_repo_config_option);
4565 }
4566
4567 static int
4568 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4569 {
4570         if (!opt_git_dir[0])
4571                 string_ncopy(opt_git_dir, name, namelen);
4572         else
4573                 string_ncopy(opt_cdup, name, namelen);
4574         return OK;
4575 }
4576
4577 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4578  * must be the last one! */
4579 static int
4580 load_repo_info(void)
4581 {
4582         return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4583                                "=", read_repo_info);
4584 }
4585
4586 static int
4587 read_properties(FILE *pipe, const char *separators,
4588                 int (*read_property)(char *, size_t, char *, size_t))
4589 {
4590         char buffer[BUFSIZ];
4591         char *name;
4592         int state = OK;
4593
4594         if (!pipe)
4595                 return ERR;
4596
4597         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4598                 char *value;
4599                 size_t namelen;
4600                 size_t valuelen;
4601
4602                 name = chomp_string(name);
4603                 namelen = strcspn(name, separators);
4604
4605                 if (name[namelen]) {
4606                         name[namelen] = 0;
4607                         value = chomp_string(name + namelen + 1);
4608                         valuelen = strlen(value);
4609
4610                 } else {
4611                         value = "";
4612                         valuelen = 0;
4613                 }
4614
4615                 state = read_property(name, namelen, value, valuelen);
4616         }
4617
4618         if (state != ERR && ferror(pipe))
4619                 state = ERR;
4620
4621         pclose(pipe);
4622
4623         return state;
4624 }
4625
4626
4627 /*
4628  * Main
4629  */
4630
4631 static void __NORETURN
4632 quit(int sig)
4633 {
4634         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4635         if (cursed)
4636                 endwin();
4637         exit(0);
4638 }
4639
4640 static void __NORETURN
4641 die(const char *err, ...)
4642 {
4643         va_list args;
4644
4645         endwin();
4646
4647         va_start(args, err);
4648         fputs("tig: ", stderr);
4649         vfprintf(stderr, err, args);
4650         fputs("\n", stderr);
4651         va_end(args);
4652
4653         exit(1);
4654 }
4655
4656 int
4657 main(int argc, char *argv[])
4658 {
4659         struct view *view;
4660         enum request request;
4661         size_t i;
4662
4663         signal(SIGINT, quit);
4664
4665         if (setlocale(LC_ALL, "")) {
4666                 char *codeset = nl_langinfo(CODESET);
4667
4668                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4669         }
4670
4671         if (load_repo_info() == ERR)
4672                 die("Failed to load repo info.");
4673
4674         if (load_options() == ERR)
4675                 die("Failed to load user config.");
4676
4677         /* Load the repo config file so options can be overwritten from
4678          * the command line. */
4679         if (load_repo_config() == ERR)
4680                 die("Failed to load repo config.");
4681
4682         if (!parse_options(argc, argv))
4683                 return 0;
4684
4685         /* Require a git repository unless when running in pager mode. */
4686         if (!opt_git_dir[0])
4687                 die("Not a git repository");
4688
4689         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4690                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4691                 if (opt_iconv == ICONV_NONE)
4692                         die("Failed to initialize character set conversion");
4693         }
4694
4695         if (load_refs() == ERR)
4696                 die("Failed to load refs.");
4697
4698         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4699                 view->cmd_env = getenv(view->cmd_env);
4700
4701         request = opt_request;
4702
4703         init_display();
4704
4705         while (view_driver(display[current_view], request)) {
4706                 int key;
4707                 int i;
4708
4709                 foreach_view (view, i)
4710                         update_view(view);
4711
4712                 /* Refresh, accept single keystroke of input */
4713                 key = wgetch(status_win);
4714
4715                 /* wgetch() with nodelay() enabled returns ERR when there's no
4716                  * input. */
4717                 if (key == ERR) {
4718                         request = REQ_NONE;
4719                         continue;
4720                 }
4721
4722                 request = get_keybinding(display[current_view]->keymap, key);
4723
4724                 /* Some low-level request handling. This keeps access to
4725                  * status_win restricted. */
4726                 switch (request) {
4727                 case REQ_PROMPT:
4728                 {
4729                         char *cmd = read_prompt(":");
4730
4731                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4732                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4733                                         opt_request = REQ_VIEW_DIFF;
4734                                 } else {
4735                                         opt_request = REQ_VIEW_PAGER;
4736                                 }
4737                                 break;
4738                         }
4739
4740                         request = REQ_NONE;
4741                         break;
4742                 }
4743                 case REQ_SEARCH:
4744                 case REQ_SEARCH_BACK:
4745                 {
4746                         const char *prompt = request == REQ_SEARCH
4747                                            ? "/" : "?";
4748                         char *search = read_prompt(prompt);
4749
4750                         if (search)
4751                                 string_ncopy(opt_search, search, strlen(search));
4752                         else
4753                                 request = REQ_NONE;
4754                         break;
4755                 }
4756                 case REQ_SCREEN_RESIZE:
4757                 {
4758                         int height, width;
4759
4760                         getmaxyx(stdscr, height, width);
4761
4762                         /* Resize the status view and let the view driver take
4763                          * care of resizing the displayed views. */
4764                         wresize(status_win, 1, width);
4765                         mvwin(status_win, height - 1, 0);
4766                         wrefresh(status_win);
4767                         break;
4768                 }
4769                 default:
4770                         break;
4771                 }
4772         }
4773
4774         quit(0);
4775
4776         return 0;
4777 }