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