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