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