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