Introduce selected flag and use it for refacter wclrtoeol usage
[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                 if (view->lineno == view->offset + view->height) {
1468                         /* Clear the hidden line so it doesn't show if the view
1469                          * is scrolled up. */
1470                         wmove(view->win, view->height, 0);
1471                         wclrtoeol(view->win);
1472                 }
1473                 view->lineno = view->offset + view->height - 1;
1474                 draw_view_line(view, view->lineno - view->offset);
1475         }
1476
1477         assert(view->offset <= view->lineno && view->lineno < view->lines);
1478
1479         if (!redraw)
1480                 return;
1481
1482         redrawwin(view->win);
1483         wrefresh(view->win);
1484         report("");
1485 }
1486
1487 /* Scroll frontend */
1488 static void
1489 scroll_view(struct view *view, enum request request)
1490 {
1491         int lines = 1;
1492
1493         switch (request) {
1494         case REQ_SCROLL_PAGE_DOWN:
1495                 lines = view->height;
1496         case REQ_SCROLL_LINE_DOWN:
1497                 if (view->offset + lines > view->lines)
1498                         lines = view->lines - view->offset;
1499
1500                 if (lines == 0 || view->offset + view->height >= view->lines) {
1501                         report("Cannot scroll beyond the last line");
1502                         return;
1503                 }
1504                 break;
1505
1506         case REQ_SCROLL_PAGE_UP:
1507                 lines = view->height;
1508         case REQ_SCROLL_LINE_UP:
1509                 if (lines > view->offset)
1510                         lines = view->offset;
1511
1512                 if (lines == 0) {
1513                         report("Cannot scroll beyond the first line");
1514                         return;
1515                 }
1516
1517                 lines = -lines;
1518                 break;
1519
1520         default:
1521                 die("request %d not handled in switch", request);
1522         }
1523
1524         do_scroll_view(view, lines, TRUE);
1525 }
1526
1527 /* Cursor moving */
1528 static void
1529 move_view(struct view *view, enum request request, bool redraw)
1530 {
1531         int steps;
1532
1533         switch (request) {
1534         case REQ_MOVE_FIRST_LINE:
1535                 steps = -view->lineno;
1536                 break;
1537
1538         case REQ_MOVE_LAST_LINE:
1539                 steps = view->lines - view->lineno - 1;
1540                 break;
1541
1542         case REQ_MOVE_PAGE_UP:
1543                 steps = view->height > view->lineno
1544                       ? -view->lineno : -view->height;
1545                 break;
1546
1547         case REQ_MOVE_PAGE_DOWN:
1548                 steps = view->lineno + view->height >= view->lines
1549                       ? view->lines - view->lineno - 1 : view->height;
1550                 break;
1551
1552         case REQ_MOVE_UP:
1553                 steps = -1;
1554                 break;
1555
1556         case REQ_MOVE_DOWN:
1557                 steps = 1;
1558                 break;
1559
1560         default:
1561                 die("request %d not handled in switch", request);
1562         }
1563
1564         if (steps <= 0 && view->lineno == 0) {
1565                 report("Cannot move beyond the first line");
1566                 return;
1567
1568         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1569                 report("Cannot move beyond the last line");
1570                 return;
1571         }
1572
1573         /* Move the current line */
1574         view->lineno += steps;
1575         assert(0 <= view->lineno && view->lineno < view->lines);
1576
1577         /* Repaint the old "current" line if we be scrolling */
1578         if (ABS(steps) < view->height)
1579                 draw_view_line(view, view->lineno - steps - view->offset);
1580
1581         /* Check whether the view needs to be scrolled */
1582         if (view->lineno < view->offset ||
1583             view->lineno >= view->offset + view->height) {
1584                 if (steps < 0 && -steps > view->offset) {
1585                         steps = -view->offset;
1586
1587                 } else if (steps > 0) {
1588                         if (view->lineno == view->lines - 1 &&
1589                             view->lines > view->height) {
1590                                 steps = view->lines - view->offset - 1;
1591                                 if (steps >= view->height)
1592                                         steps -= view->height - 1;
1593                         }
1594                 }
1595
1596                 do_scroll_view(view, steps, redraw);
1597                 return;
1598         }
1599
1600         /* Draw the current line */
1601         draw_view_line(view, view->lineno - view->offset);
1602
1603         if (!redraw)
1604                 return;
1605
1606         redrawwin(view->win);
1607         wrefresh(view->win);
1608         report("");
1609 }
1610
1611
1612 /*
1613  * Searching
1614  */
1615
1616 static void search_view(struct view *view, enum request request, const char *search);
1617
1618 static bool
1619 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1620 {
1621         assert(view_is_displayed(view));
1622
1623         if (!view->ops->grep(view, line))
1624                 return FALSE;
1625
1626         if (lineno - view->offset >= view->height) {
1627                 view->offset = lineno;
1628                 view->lineno = lineno;
1629                 redraw_view(view);
1630
1631         } else {
1632                 unsigned long old_lineno = view->lineno - view->offset;
1633
1634                 view->lineno = lineno;
1635                 draw_view_line(view, old_lineno);
1636
1637                 draw_view_line(view, view->lineno - view->offset);
1638                 redrawwin(view->win);
1639                 wrefresh(view->win);
1640         }
1641
1642         report("Line %ld matches '%s'", lineno + 1, view->grep);
1643         return TRUE;
1644 }
1645
1646 static void
1647 find_next(struct view *view, enum request request)
1648 {
1649         unsigned long lineno = view->lineno;
1650         int direction;
1651
1652         if (!*view->grep) {
1653                 if (!*opt_search)
1654                         report("No previous search");
1655                 else
1656                         search_view(view, request, opt_search);
1657                 return;
1658         }
1659
1660         switch (request) {
1661         case REQ_SEARCH:
1662         case REQ_FIND_NEXT:
1663                 direction = 1;
1664                 break;
1665
1666         case REQ_SEARCH_BACK:
1667         case REQ_FIND_PREV:
1668                 direction = -1;
1669                 break;
1670
1671         default:
1672                 return;
1673         }
1674
1675         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1676                 lineno += direction;
1677
1678         /* Note, lineno is unsigned long so will wrap around in which case it
1679          * will become bigger than view->lines. */
1680         for (; lineno < view->lines; lineno += direction) {
1681                 struct line *line = &view->line[lineno];
1682
1683                 if (find_next_line(view, lineno, line))
1684                         return;
1685         }
1686
1687         report("No match found for '%s'", view->grep);
1688 }
1689
1690 static void
1691 search_view(struct view *view, enum request request, const char *search)
1692 {
1693         int regex_err;
1694
1695         if (view->regex) {
1696                 regfree(view->regex);
1697                 *view->grep = 0;
1698         } else {
1699                 view->regex = calloc(1, sizeof(*view->regex));
1700                 if (!view->regex)
1701                         return;
1702         }
1703
1704         regex_err = regcomp(view->regex, search, REG_EXTENDED);
1705         if (regex_err != 0) {
1706                 char buf[SIZEOF_STR] = "unknown error";
1707
1708                 regerror(regex_err, view->regex, buf, sizeof(buf));
1709                 report("Search failed: %s", buf);
1710                 return;
1711         }
1712
1713         string_copy(view->grep, search);
1714
1715         find_next(view, request);
1716 }
1717
1718 /*
1719  * Incremental updating
1720  */
1721
1722 static void
1723 end_update(struct view *view)
1724 {
1725         if (!view->pipe)
1726                 return;
1727         set_nonblocking_input(FALSE);
1728         if (view->pipe == stdin)
1729                 fclose(view->pipe);
1730         else
1731                 pclose(view->pipe);
1732         view->pipe = NULL;
1733 }
1734
1735 static bool
1736 begin_update(struct view *view)
1737 {
1738         const char *id = view->id;
1739
1740         if (view->pipe)
1741                 end_update(view);
1742
1743         if (opt_cmd[0]) {
1744                 string_copy(view->cmd, opt_cmd);
1745                 opt_cmd[0] = 0;
1746                 /* When running random commands, the view ref could have become
1747                  * invalid so clear it. */
1748                 view->ref[0] = 0;
1749
1750         } else if (view == VIEW(REQ_VIEW_TREE)) {
1751                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1752
1753                 if (strcmp(view->vid, view->id))
1754                         opt_path[0] = 0;
1755
1756                 if (!string_format(view->cmd, format, id, opt_path))
1757                         return FALSE;
1758
1759         } else {
1760                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1761
1762                 if (!string_format(view->cmd, format, id, id, id, id, id))
1763                         return FALSE;
1764         }
1765
1766         /* Special case for the pager view. */
1767         if (opt_pipe) {
1768                 view->pipe = opt_pipe;
1769                 opt_pipe = NULL;
1770         } else {
1771                 view->pipe = popen(view->cmd, "r");
1772         }
1773
1774         if (!view->pipe)
1775                 return FALSE;
1776
1777         set_nonblocking_input(TRUE);
1778
1779         view->offset = 0;
1780         view->lines  = 0;
1781         view->lineno = 0;
1782         string_copy(view->vid, id);
1783
1784         if (view->line) {
1785                 int i;
1786
1787                 for (i = 0; i < view->lines; i++)
1788                         if (view->line[i].data)
1789                                 free(view->line[i].data);
1790
1791                 free(view->line);
1792                 view->line = NULL;
1793         }
1794
1795         view->start_time = time(NULL);
1796
1797         return TRUE;
1798 }
1799
1800 static struct line *
1801 realloc_lines(struct view *view, size_t line_size)
1802 {
1803         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1804
1805         if (!tmp)
1806                 return NULL;
1807
1808         view->line = tmp;
1809         view->line_size = line_size;
1810         return view->line;
1811 }
1812
1813 static bool
1814 update_view(struct view *view)
1815 {
1816         char in_buffer[BUFSIZ];
1817         char out_buffer[BUFSIZ * 2];
1818         char *line;
1819         /* The number of lines to read. If too low it will cause too much
1820          * redrawing (and possible flickering), if too high responsiveness
1821          * will suffer. */
1822         unsigned long lines = view->height;
1823         int redraw_from = -1;
1824
1825         if (!view->pipe)
1826                 return TRUE;
1827
1828         /* Only redraw if lines are visible. */
1829         if (view->offset + view->height >= view->lines)
1830                 redraw_from = view->lines - view->offset;
1831
1832         /* FIXME: This is probably not perfect for backgrounded views. */
1833         if (!realloc_lines(view, view->lines + lines))
1834                 goto alloc_error;
1835
1836         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1837                 size_t linelen = strlen(line);
1838
1839                 if (linelen)
1840                         line[linelen - 1] = 0;
1841
1842                 if (opt_iconv != ICONV_NONE) {
1843                         char *inbuf = line;
1844                         size_t inlen = linelen;
1845
1846                         char *outbuf = out_buffer;
1847                         size_t outlen = sizeof(out_buffer);
1848
1849                         size_t ret;
1850
1851                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1852                         if (ret != (size_t) -1) {
1853                                 line = out_buffer;
1854                                 linelen = strlen(out_buffer);
1855                         }
1856                 }
1857
1858                 if (!view->ops->read(view, line))
1859                         goto alloc_error;
1860
1861                 if (lines-- == 1)
1862                         break;
1863         }
1864
1865         {
1866                 int digits;
1867
1868                 lines = view->lines;
1869                 for (digits = 0; lines; digits++)
1870                         lines /= 10;
1871
1872                 /* Keep the displayed view in sync with line number scaling. */
1873                 if (digits != view->digits) {
1874                         view->digits = digits;
1875                         redraw_from = 0;
1876                 }
1877         }
1878
1879         if (!view_is_displayed(view))
1880                 goto check_pipe;
1881
1882         if (view == VIEW(REQ_VIEW_TREE)) {
1883                 /* Clear the view and redraw everything since the tree sorting
1884                  * might have rearranged things. */
1885                 redraw_view(view);
1886
1887         } else if (redraw_from >= 0) {
1888                 /* If this is an incremental update, redraw the previous line
1889                  * since for commits some members could have changed when
1890                  * loading the main view. */
1891                 if (redraw_from > 0)
1892                         redraw_from--;
1893
1894                 /* Incrementally draw avoids flickering. */
1895                 redraw_view_from(view, redraw_from);
1896         }
1897
1898         /* Update the title _after_ the redraw so that if the redraw picks up a
1899          * commit reference in view->ref it'll be available here. */
1900         update_view_title(view);
1901
1902 check_pipe:
1903         if (ferror(view->pipe)) {
1904                 report("Failed to read: %s", strerror(errno));
1905                 goto end;
1906
1907         } else if (feof(view->pipe)) {
1908                 report("");
1909                 goto end;
1910         }
1911
1912         return TRUE;
1913
1914 alloc_error:
1915         report("Allocation failure");
1916
1917 end:
1918         end_update(view);
1919         return FALSE;
1920 }
1921
1922
1923 /*
1924  * View opening
1925  */
1926
1927 static void open_help_view(struct view *view)
1928 {
1929         char buf[BUFSIZ];
1930         int lines = ARRAY_SIZE(req_info) + 2;
1931         int i;
1932
1933         if (view->lines > 0)
1934                 return;
1935
1936         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1937                 if (!req_info[i].request)
1938                         lines++;
1939
1940         view->line = calloc(lines, sizeof(*view->line));
1941         if (!view->line) {
1942                 report("Allocation failure");
1943                 return;
1944         }
1945
1946         view->ops->read(view, "Quick reference for tig keybindings:");
1947
1948         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1949                 char *key;
1950
1951                 if (!req_info[i].request) {
1952                         view->ops->read(view, "");
1953                         view->ops->read(view, req_info[i].help);
1954                         continue;
1955                 }
1956
1957                 key = get_key(req_info[i].request);
1958                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1959                         continue;
1960
1961                 view->ops->read(view, buf);
1962         }
1963 }
1964
1965 enum open_flags {
1966         OPEN_DEFAULT = 0,       /* Use default view switching. */
1967         OPEN_SPLIT = 1,         /* Split current view. */
1968         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1969         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1970 };
1971
1972 static void
1973 open_view(struct view *prev, enum request request, enum open_flags flags)
1974 {
1975         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1976         bool split = !!(flags & OPEN_SPLIT);
1977         bool reload = !!(flags & OPEN_RELOAD);
1978         struct view *view = VIEW(request);
1979         int nviews = displayed_views();
1980         struct view *base_view = display[0];
1981
1982         if (view == prev && nviews == 1 && !reload) {
1983                 report("Already in %s view", view->name);
1984                 return;
1985         }
1986
1987         if (view == VIEW(REQ_VIEW_HELP)) {
1988                 open_help_view(view);
1989
1990         } else if ((reload || strcmp(view->vid, view->id)) &&
1991                    !begin_update(view)) {
1992                 report("Failed to load %s view", view->name);
1993                 return;
1994         }
1995
1996         if (split) {
1997                 display[1] = view;
1998                 if (!backgrounded)
1999                         current_view = 1;
2000         } else {
2001                 /* Maximize the current view. */
2002                 memset(display, 0, sizeof(display));
2003                 current_view = 0;
2004                 display[current_view] = view;
2005         }
2006
2007         /* Resize the view when switching between split- and full-screen,
2008          * or when switching between two different full-screen views. */
2009         if (nviews != displayed_views() ||
2010             (nviews == 1 && base_view != display[0]))
2011                 resize_display();
2012
2013         if (split && prev->lineno - prev->offset >= prev->height) {
2014                 /* Take the title line into account. */
2015                 int lines = prev->lineno - prev->offset - prev->height + 1;
2016
2017                 /* Scroll the view that was split if the current line is
2018                  * outside the new limited view. */
2019                 do_scroll_view(prev, lines, TRUE);
2020         }
2021
2022         if (prev && view != prev) {
2023                 if (split && !backgrounded) {
2024                         /* "Blur" the previous view. */
2025                         update_view_title(prev);
2026                 }
2027
2028                 view->parent = prev;
2029         }
2030
2031         if (view->pipe && view->lines == 0) {
2032                 /* Clear the old view and let the incremental updating refill
2033                  * the screen. */
2034                 wclear(view->win);
2035                 report("");
2036         } else {
2037                 redraw_view(view);
2038                 report("");
2039         }
2040
2041         /* If the view is backgrounded the above calls to report()
2042          * won't redraw the view title. */
2043         if (backgrounded)
2044                 update_view_title(view);
2045 }
2046
2047
2048 /*
2049  * User request switch noodle
2050  */
2051
2052 static int
2053 view_driver(struct view *view, enum request request)
2054 {
2055         int i;
2056
2057         switch (request) {
2058         case REQ_MOVE_UP:
2059         case REQ_MOVE_DOWN:
2060         case REQ_MOVE_PAGE_UP:
2061         case REQ_MOVE_PAGE_DOWN:
2062         case REQ_MOVE_FIRST_LINE:
2063         case REQ_MOVE_LAST_LINE:
2064                 move_view(view, request, TRUE);
2065                 break;
2066
2067         case REQ_SCROLL_LINE_DOWN:
2068         case REQ_SCROLL_LINE_UP:
2069         case REQ_SCROLL_PAGE_DOWN:
2070         case REQ_SCROLL_PAGE_UP:
2071                 scroll_view(view, request);
2072                 break;
2073
2074         case REQ_VIEW_BLOB:
2075                 if (!ref_blob[0]) {
2076                         report("No file chosen, press 't' to open tree view");
2077                         break;
2078                 }
2079                 /* Fall-through */
2080         case REQ_VIEW_MAIN:
2081         case REQ_VIEW_DIFF:
2082         case REQ_VIEW_LOG:
2083         case REQ_VIEW_TREE:
2084         case REQ_VIEW_HELP:
2085         case REQ_VIEW_PAGER:
2086                 open_view(view, request, OPEN_DEFAULT);
2087                 break;
2088
2089         case REQ_NEXT:
2090         case REQ_PREVIOUS:
2091                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2092
2093                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2094                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2095                    (view == VIEW(REQ_VIEW_BLOB) &&
2096                      view->parent == VIEW(REQ_VIEW_TREE))) {
2097                         bool redraw = display[1] == view;
2098
2099                         view = view->parent;
2100                         move_view(view, request, redraw);
2101                         if (redraw)
2102                                 update_view_title(view);
2103                 } else {
2104                         move_view(view, request, TRUE);
2105                         break;
2106                 }
2107                 /* Fall-through */
2108
2109         case REQ_ENTER:
2110                 if (!view->lines) {
2111                         report("Nothing to enter");
2112                         break;
2113                 }
2114                 return view->ops->enter(view, &view->line[view->lineno]);
2115
2116         case REQ_VIEW_NEXT:
2117         {
2118                 int nviews = displayed_views();
2119                 int next_view = (current_view + 1) % nviews;
2120
2121                 if (next_view == current_view) {
2122                         report("Only one view is displayed");
2123                         break;
2124                 }
2125
2126                 current_view = next_view;
2127                 /* Blur out the title of the previous view. */
2128                 update_view_title(view);
2129                 report("");
2130                 break;
2131         }
2132         case REQ_TOGGLE_LINENO:
2133                 opt_line_number = !opt_line_number;
2134                 redraw_display();
2135                 break;
2136
2137         case REQ_TOGGLE_REV_GRAPH:
2138                 opt_rev_graph = !opt_rev_graph;
2139                 redraw_display();
2140                 break;
2141
2142         case REQ_PROMPT:
2143                 /* Always reload^Wrerun commands from the prompt. */
2144                 open_view(view, opt_request, OPEN_RELOAD);
2145                 break;
2146
2147         case REQ_SEARCH:
2148         case REQ_SEARCH_BACK:
2149                 search_view(view, request, opt_search);
2150                 break;
2151
2152         case REQ_FIND_NEXT:
2153         case REQ_FIND_PREV:
2154                 find_next(view, request);
2155                 break;
2156
2157         case REQ_STOP_LOADING:
2158                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2159                         view = &views[i];
2160                         if (view->pipe)
2161                                 report("Stopped loading the %s view", view->name),
2162                         end_update(view);
2163                 }
2164                 break;
2165
2166         case REQ_SHOW_VERSION:
2167                 report("%s (built %s)", VERSION, __DATE__);
2168                 return TRUE;
2169
2170         case REQ_SCREEN_RESIZE:
2171                 resize_display();
2172                 /* Fall-through */
2173         case REQ_SCREEN_REDRAW:
2174                 redraw_display();
2175                 break;
2176
2177         case REQ_NONE:
2178                 doupdate();
2179                 return TRUE;
2180
2181         case REQ_VIEW_CLOSE:
2182                 /* XXX: Mark closed views by letting view->parent point to the
2183                  * view itself. Parents to closed view should never be
2184                  * followed. */
2185                 if (view->parent &&
2186                     view->parent->parent != view->parent) {
2187                         memset(display, 0, sizeof(display));
2188                         current_view = 0;
2189                         display[current_view] = view->parent;
2190                         view->parent = view;
2191                         resize_display();
2192                         redraw_display();
2193                         break;
2194                 }
2195                 /* Fall-through */
2196         case REQ_QUIT:
2197                 return FALSE;
2198
2199         default:
2200                 /* An unknown key will show most commonly used commands. */
2201                 report("Unknown key, press 'h' for help");
2202                 return TRUE;
2203         }
2204
2205         return TRUE;
2206 }
2207
2208
2209 /*
2210  * Pager backend
2211  */
2212
2213 static bool
2214 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2215 {
2216         char *text = line->data;
2217         enum line_type type = line->type;
2218         int textlen = strlen(text);
2219         int attr;
2220
2221         wmove(view->win, lineno, 0);
2222
2223         if (selected) {
2224                 type = LINE_CURSOR;
2225                 wchgat(view->win, -1, 0, type, NULL);
2226         }
2227
2228         attr = get_line_attr(type);
2229         wattrset(view->win, attr);
2230
2231         if (opt_line_number || opt_tab_size < TABSIZE) {
2232                 static char spaces[] = "                    ";
2233                 int col_offset = 0, col = 0;
2234
2235                 if (opt_line_number) {
2236                         unsigned long real_lineno = view->offset + lineno + 1;
2237
2238                         if (real_lineno == 1 ||
2239                             (real_lineno % opt_num_interval) == 0) {
2240                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2241
2242                         } else {
2243                                 waddnstr(view->win, spaces,
2244                                          MIN(view->digits, STRING_SIZE(spaces)));
2245                         }
2246                         waddstr(view->win, ": ");
2247                         col_offset = view->digits + 2;
2248                 }
2249
2250                 while (text && col_offset + col < view->width) {
2251                         int cols_max = view->width - col_offset - col;
2252                         char *pos = text;
2253                         int cols;
2254
2255                         if (*text == '\t') {
2256                                 text++;
2257                                 assert(sizeof(spaces) > TABSIZE);
2258                                 pos = spaces;
2259                                 cols = opt_tab_size - (col % opt_tab_size);
2260
2261                         } else {
2262                                 text = strchr(text, '\t');
2263                                 cols = line ? text - pos : strlen(pos);
2264                         }
2265
2266                         waddnstr(view->win, pos, MIN(cols, cols_max));
2267                         col += cols;
2268                 }
2269
2270         } else {
2271                 int col = 0, pos = 0;
2272
2273                 for (; pos < textlen && col < view->width; pos++, col++)
2274                         if (text[pos] == '\t')
2275                                 col += TABSIZE - (col % TABSIZE) - 1;
2276
2277                 waddnstr(view->win, text, pos);
2278         }
2279
2280         return TRUE;
2281 }
2282
2283 static bool
2284 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2285 {
2286         char refbuf[SIZEOF_STR];
2287         char *ref = NULL;
2288         FILE *pipe;
2289
2290         if (!string_format(refbuf, "git describe %s", commit_id))
2291                 return TRUE;
2292
2293         pipe = popen(refbuf, "r");
2294         if (!pipe)
2295                 return TRUE;
2296
2297         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2298                 ref = chomp_string(ref);
2299         pclose(pipe);
2300
2301         if (!ref || !*ref)
2302                 return TRUE;
2303
2304         /* This is the only fatal call, since it can "corrupt" the buffer. */
2305         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2306                 return FALSE;
2307
2308         return TRUE;
2309 }
2310
2311 static void
2312 add_pager_refs(struct view *view, struct line *line)
2313 {
2314         char buf[SIZEOF_STR];
2315         char *commit_id = line->data + STRING_SIZE("commit ");
2316         struct ref **refs;
2317         size_t bufpos = 0, refpos = 0;
2318         const char *sep = "Refs: ";
2319         bool is_tag = FALSE;
2320
2321         assert(line->type == LINE_COMMIT);
2322
2323         refs = get_refs(commit_id);
2324         if (!refs) {
2325                 if (view == VIEW(REQ_VIEW_DIFF))
2326                         goto try_add_describe_ref;
2327                 return;
2328         }
2329
2330         do {
2331                 struct ref *ref = refs[refpos];
2332                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2333
2334                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2335                         return;
2336                 sep = ", ";
2337                 if (ref->tag)
2338                         is_tag = TRUE;
2339         } while (refs[refpos++]->next);
2340
2341         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2342 try_add_describe_ref:
2343                 /* Add <tag>-g<commit_id> "fake" reference. */
2344                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2345                         return;
2346         }
2347
2348         if (bufpos == 0)
2349                 return;
2350
2351         if (!realloc_lines(view, view->line_size + 1))
2352                 return;
2353
2354         line = &view->line[view->lines];
2355         line->data = strdup(buf);
2356         if (!line->data)
2357                 return;
2358
2359         line->type = LINE_PP_REFS;
2360         view->lines++;
2361 }
2362
2363 static bool
2364 pager_read(struct view *view, char *data)
2365 {
2366         struct line *line = &view->line[view->lines];
2367
2368         line->data = strdup(data);
2369         if (!line->data)
2370                 return FALSE;
2371
2372         line->type = get_line_type(line->data);
2373         view->lines++;
2374
2375         if (line->type == LINE_COMMIT &&
2376             (view == VIEW(REQ_VIEW_DIFF) ||
2377              view == VIEW(REQ_VIEW_LOG)))
2378                 add_pager_refs(view, line);
2379
2380         return TRUE;
2381 }
2382
2383 static bool
2384 pager_enter(struct view *view, struct line *line)
2385 {
2386         int split = 0;
2387
2388         if (line->type == LINE_COMMIT &&
2389            (view == VIEW(REQ_VIEW_LOG) ||
2390             view == VIEW(REQ_VIEW_PAGER))) {
2391                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2392                 split = 1;
2393         }
2394
2395         /* Always scroll the view even if it was split. That way
2396          * you can use Enter to scroll through the log view and
2397          * split open each commit diff. */
2398         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2399
2400         /* FIXME: A minor workaround. Scrolling the view will call report("")
2401          * but if we are scrolling a non-current view this won't properly
2402          * update the view title. */
2403         if (split)
2404                 update_view_title(view);
2405
2406         return TRUE;
2407 }
2408
2409 static bool
2410 pager_grep(struct view *view, struct line *line)
2411 {
2412         regmatch_t pmatch;
2413         char *text = line->data;
2414
2415         if (!*text)
2416                 return FALSE;
2417
2418         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2419                 return FALSE;
2420
2421         return TRUE;
2422 }
2423
2424 static void
2425 pager_select(struct view *view, struct line *line)
2426 {
2427         if (line->type == LINE_COMMIT) {
2428                 char *text = line->data;
2429
2430                 string_copy(view->ref, text + STRING_SIZE("commit "));
2431                 string_copy(ref_commit, view->ref);
2432         }
2433 }
2434
2435 static struct view_ops pager_ops = {
2436         "line",
2437         pager_draw,
2438         pager_read,
2439         pager_enter,
2440         pager_grep,
2441         pager_select,
2442 };
2443
2444
2445 /*
2446  * Tree backend
2447  */
2448
2449 /* Parse output from git ls-tree:
2450  *
2451  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2452  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2453  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2454  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2455  */
2456
2457 #define SIZEOF_TREE_ATTR \
2458         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2459
2460 #define TREE_UP_FORMAT "040000 tree %s\t.."
2461
2462 static int
2463 tree_compare_entry(enum line_type type1, char *name1,
2464                    enum line_type type2, char *name2)
2465 {
2466         if (type1 != type2) {
2467                 if (type1 == LINE_TREE_DIR)
2468                         return -1;
2469                 return 1;
2470         }
2471
2472         return strcmp(name1, name2);
2473 }
2474
2475 static bool
2476 tree_read(struct view *view, char *text)
2477 {
2478         size_t textlen = strlen(text);
2479         char buf[SIZEOF_STR];
2480         unsigned long pos;
2481         enum line_type type;
2482         bool first_read = view->lines == 0;
2483
2484         if (textlen <= SIZEOF_TREE_ATTR)
2485                 return FALSE;
2486
2487         type = text[STRING_SIZE("100644 ")] == 't'
2488              ? LINE_TREE_DIR : LINE_TREE_FILE;
2489
2490         if (first_read) {
2491                 /* Add path info line */
2492                 if (string_format(buf, "Directory path /%s", opt_path) &&
2493                     realloc_lines(view, view->line_size + 1) &&
2494                     pager_read(view, buf))
2495                         view->line[view->lines - 1].type = LINE_DEFAULT;
2496                 else
2497                         return FALSE;
2498
2499                 /* Insert "link" to parent directory. */
2500                 if (*opt_path &&
2501                     string_format(buf, TREE_UP_FORMAT, view->ref) &&
2502                     realloc_lines(view, view->line_size + 1) &&
2503                     pager_read(view, buf))
2504                         view->line[view->lines - 1].type = LINE_TREE_DIR;
2505                 else if (*opt_path)
2506                         return FALSE;
2507         }
2508
2509         /* Strip the path part ... */
2510         if (*opt_path) {
2511                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2512                 size_t striplen = strlen(opt_path);
2513                 char *path = text + SIZEOF_TREE_ATTR;
2514
2515                 if (pathlen > striplen)
2516                         memmove(path, path + striplen,
2517                                 pathlen - striplen + 1);
2518         }
2519
2520         /* Skip "Directory ..." and ".." line. */
2521         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2522                 struct line *line = &view->line[pos];
2523                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2524                 char *path2 = text + SIZEOF_TREE_ATTR;
2525                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2526
2527                 if (cmp <= 0)
2528                         continue;
2529
2530                 text = strdup(text);
2531                 if (!text)
2532                         return FALSE;
2533
2534                 if (view->lines > pos)
2535                         memmove(&view->line[pos + 1], &view->line[pos],
2536                                 (view->lines - pos) * sizeof(*line));
2537
2538                 line = &view->line[pos];
2539                 line->data = text;
2540                 line->type = type;
2541                 view->lines++;
2542                 return TRUE;
2543         }
2544
2545         if (!pager_read(view, text))
2546                 return FALSE;
2547
2548         /* Move the current line to the first tree entry. */
2549         if (first_read)
2550                 view->lineno++;
2551
2552         view->line[view->lines - 1].type = type;
2553         return TRUE;
2554 }
2555
2556 static bool
2557 tree_enter(struct view *view, struct line *line)
2558 {
2559         enum open_flags flags = OPEN_DEFAULT;
2560         char *data = line->data;
2561         enum request request;
2562
2563         switch (line->type) {
2564         case LINE_TREE_DIR:
2565                 /* Depending on whether it is a subdir or parent (updir?) link
2566                  * mangle the path buffer. */
2567                 if (line == &view->line[1] && *opt_path) {
2568                         size_t path_len = strlen(opt_path);
2569                         char *dirsep = opt_path + path_len - 1;
2570
2571                         while (dirsep > opt_path && dirsep[-1] != '/')
2572                                 dirsep--;
2573
2574                         dirsep[0] = 0;
2575
2576                 } else {
2577                         size_t pathlen = strlen(opt_path);
2578                         size_t origlen = pathlen;
2579                         char *basename = data + SIZEOF_TREE_ATTR;
2580
2581                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2582                                 opt_path[origlen] = 0;
2583                                 return TRUE;
2584                         }
2585                 }
2586
2587                 /* Trees and subtrees share the same ID, so they are not not
2588                  * unique like blobs. */
2589                 flags |= OPEN_RELOAD;
2590                 request = REQ_VIEW_TREE;
2591                 break;
2592
2593         case LINE_TREE_FILE:
2594                 /* This causes the blob view to become split, and not having it
2595                  * in the tree dir case will make the blob view automatically
2596                  * disappear when moving to a different directory. */
2597                 flags |= OPEN_SPLIT;
2598                 request = REQ_VIEW_BLOB;
2599                 break;
2600
2601         default:
2602                 return TRUE;
2603         }
2604
2605         open_view(view, request, flags);
2606
2607         if (!VIEW(request)->pipe)
2608                 return TRUE;
2609
2610         /* For tree views insert the path to the parent as the first line. */
2611         if (request == REQ_VIEW_BLOB) {
2612                 /* Mirror what is showed in the title bar. */
2613                 string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40);
2614                 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2615                 return TRUE;
2616         }
2617
2618         return TRUE;
2619 }
2620
2621 static void
2622 tree_select(struct view *view, struct line *line)
2623 {
2624         if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) {
2625                 char *text = line->data;
2626
2627                 string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
2628                 string_copy(ref_blob, view->ref);
2629         }
2630 }
2631
2632 static struct view_ops tree_ops = {
2633         "file",
2634         pager_draw,
2635         tree_read,
2636         tree_enter,
2637         pager_grep,
2638         tree_select,
2639 };
2640
2641 static bool
2642 blob_read(struct view *view, char *line)
2643 {
2644         bool state = pager_read(view, line);
2645
2646         if (state == TRUE)
2647                 view->line[view->lines - 1].type = LINE_DEFAULT;
2648
2649         return state;
2650 }
2651
2652 static struct view_ops blob_ops = {
2653         "line",
2654         pager_draw,
2655         blob_read,
2656         pager_enter,
2657         pager_grep,
2658         pager_select,
2659 };
2660
2661
2662 /*
2663  * Main view backend
2664  */
2665
2666 struct commit {
2667         char id[41];                    /* SHA1 ID. */
2668         char title[75];                 /* First line of the commit message. */
2669         char author[75];                /* Author of the commit. */
2670         struct tm time;                 /* Date from the author ident. */
2671         struct ref **refs;              /* Repository references. */
2672         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2673         size_t graph_size;              /* The width of the graph array. */
2674 };
2675
2676 static bool
2677 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2678 {
2679         char buf[DATE_COLS + 1];
2680         struct commit *commit = line->data;
2681         enum line_type type;
2682         int col = 0;
2683         size_t timelen;
2684         size_t authorlen;
2685         int trimmed = 1;
2686
2687         if (!*commit->author)
2688                 return FALSE;
2689
2690         wmove(view->win, lineno, col);
2691
2692         if (selected) {
2693                 type = LINE_CURSOR;
2694                 wattrset(view->win, get_line_attr(type));
2695                 wchgat(view->win, -1, 0, type, NULL);
2696
2697         } else {
2698                 type = LINE_MAIN_COMMIT;
2699                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2700         }
2701
2702         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2703         waddnstr(view->win, buf, timelen);
2704         waddstr(view->win, " ");
2705
2706         col += DATE_COLS;
2707         wmove(view->win, lineno, col);
2708         if (type != LINE_CURSOR)
2709                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2710
2711         if (opt_utf8) {
2712                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2713         } else {
2714                 authorlen = strlen(commit->author);
2715                 if (authorlen > AUTHOR_COLS - 2) {
2716                         authorlen = AUTHOR_COLS - 2;
2717                         trimmed = 1;
2718                 }
2719         }
2720
2721         if (trimmed) {
2722                 waddnstr(view->win, commit->author, authorlen);
2723                 if (type != LINE_CURSOR)
2724                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2725                 waddch(view->win, '~');
2726         } else {
2727                 waddstr(view->win, commit->author);
2728         }
2729
2730         col += AUTHOR_COLS;
2731         if (type != LINE_CURSOR)
2732                 wattrset(view->win, A_NORMAL);
2733
2734         if (opt_rev_graph && commit->graph_size) {
2735                 size_t i;
2736
2737                 wmove(view->win, lineno, col);
2738                 /* Using waddch() instead of waddnstr() ensures that
2739                  * they'll be rendered correctly for the cursor line. */
2740                 for (i = 0; i < commit->graph_size; i++)
2741                         waddch(view->win, commit->graph[i]);
2742
2743                 col += commit->graph_size + 1;
2744         }
2745
2746         wmove(view->win, lineno, col);
2747
2748         if (commit->refs) {
2749                 size_t i = 0;
2750
2751                 do {
2752                         if (type == LINE_CURSOR)
2753                                 ;
2754                         else if (commit->refs[i]->tag)
2755                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2756                         else
2757                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2758                         waddstr(view->win, "[");
2759                         waddstr(view->win, commit->refs[i]->name);
2760                         waddstr(view->win, "]");
2761                         if (type != LINE_CURSOR)
2762                                 wattrset(view->win, A_NORMAL);
2763                         waddstr(view->win, " ");
2764                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2765                 } while (commit->refs[i++]->next);
2766         }
2767
2768         if (type != LINE_CURSOR)
2769                 wattrset(view->win, get_line_attr(type));
2770
2771         {
2772                 int titlelen = strlen(commit->title);
2773
2774                 if (col + titlelen > view->width)
2775                         titlelen = view->width - col;
2776
2777                 waddnstr(view->win, commit->title, titlelen);
2778         }
2779
2780         return TRUE;
2781 }
2782
2783 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2784 static bool
2785 main_read(struct view *view, char *line)
2786 {
2787         enum line_type type = get_line_type(line);
2788         struct commit *commit = view->lines
2789                               ? view->line[view->lines - 1].data : NULL;
2790
2791         switch (type) {
2792         case LINE_COMMIT:
2793                 commit = calloc(1, sizeof(struct commit));
2794                 if (!commit)
2795                         return FALSE;
2796
2797                 line += STRING_SIZE("commit ");
2798
2799                 view->line[view->lines++].data = commit;
2800                 string_copy(commit->id, line);
2801                 commit->refs = get_refs(commit->id);
2802                 commit->graph[commit->graph_size++] = ACS_LTEE;
2803                 break;
2804
2805         case LINE_AUTHOR:
2806         {
2807                 char *ident = line + STRING_SIZE("author ");
2808                 char *end = strchr(ident, '<');
2809
2810                 if (!commit)
2811                         break;
2812
2813                 if (end) {
2814                         char *email = end + 1;
2815
2816                         for (; end > ident && isspace(end[-1]); end--) ;
2817
2818                         if (end == ident && *email) {
2819                                 ident = email;
2820                                 end = strchr(ident, '>');
2821                                 for (; end > ident && isspace(end[-1]); end--) ;
2822                         }
2823                         *end = 0;
2824                 }
2825
2826                 /* End is NULL or ident meaning there's no author. */
2827                 if (end <= ident)
2828                         ident = "Unknown";
2829
2830                 string_copy(commit->author, ident);
2831
2832                 /* Parse epoch and timezone */
2833                 if (end) {
2834                         char *secs = strchr(end + 1, '>');
2835                         char *zone;
2836                         time_t time;
2837
2838                         if (!secs || secs[1] != ' ')
2839                                 break;
2840
2841                         secs += 2;
2842                         time = (time_t) atol(secs);
2843                         zone = strchr(secs, ' ');
2844                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2845                                 long tz;
2846
2847                                 zone++;
2848                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2849                                 tz += ('0' - zone[2]) * 60 * 60;
2850                                 tz += ('0' - zone[3]) * 60;
2851                                 tz += ('0' - zone[4]) * 60;
2852
2853                                 if (zone[0] == '-')
2854                                         tz = -tz;
2855
2856                                 time -= tz;
2857                         }
2858                         gmtime_r(&time, &commit->time);
2859                 }
2860                 break;
2861         }
2862         default:
2863                 if (!commit)
2864                         break;
2865
2866                 /* Fill in the commit title if it has not already been set. */
2867                 if (commit->title[0])
2868                         break;
2869
2870                 /* Require titles to start with a non-space character at the
2871                  * offset used by git log. */
2872                 /* FIXME: More gracefull handling of titles; append "..." to
2873                  * shortened titles, etc. */
2874                 if (strncmp(line, "    ", 4) ||
2875                     isspace(line[4]))
2876                         break;
2877
2878                 string_copy(commit->title, line + 4);
2879         }
2880
2881         return TRUE;
2882 }
2883
2884 static bool
2885 main_enter(struct view *view, struct line *line)
2886 {
2887         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2888
2889         open_view(view, REQ_VIEW_DIFF, flags);
2890         return TRUE;
2891 }
2892
2893 static bool
2894 main_grep(struct view *view, struct line *line)
2895 {
2896         struct commit *commit = line->data;
2897         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
2898         char buf[DATE_COLS + 1];
2899         regmatch_t pmatch;
2900
2901         for (state = S_TITLE; state < S_END; state++) {
2902                 char *text;
2903
2904                 switch (state) {
2905                 case S_TITLE:   text = commit->title;   break;
2906                 case S_AUTHOR:  text = commit->author;  break;
2907                 case S_DATE:
2908                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
2909                                 continue;
2910                         text = buf;
2911                         break;
2912
2913                 default:
2914                         return FALSE;
2915                 }
2916
2917                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
2918                         return TRUE;
2919         }
2920
2921         return FALSE;
2922 }
2923
2924 static void
2925 main_select(struct view *view, struct line *line)
2926 {
2927         struct commit *commit = line->data;
2928
2929         string_copy(view->ref, commit->id);
2930         string_copy(ref_commit, view->ref);
2931 }
2932
2933 static struct view_ops main_ops = {
2934         "commit",
2935         main_draw,
2936         main_read,
2937         main_enter,
2938         main_grep,
2939         main_select,
2940 };
2941
2942
2943 /*
2944  * Unicode / UTF-8 handling
2945  *
2946  * NOTE: Much of the following code for dealing with unicode is derived from
2947  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2948  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2949  */
2950
2951 /* I've (over)annotated a lot of code snippets because I am not entirely
2952  * confident that the approach taken by this small UTF-8 interface is correct.
2953  * --jonas */
2954
2955 static inline int
2956 unicode_width(unsigned long c)
2957 {
2958         if (c >= 0x1100 &&
2959            (c <= 0x115f                         /* Hangul Jamo */
2960             || c == 0x2329
2961             || c == 0x232a
2962             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2963                                                 /* CJK ... Yi */
2964             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2965             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2966             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2967             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2968             || (c >= 0xffe0  && c <= 0xffe6)
2969             || (c >= 0x20000 && c <= 0x2fffd)
2970             || (c >= 0x30000 && c <= 0x3fffd)))
2971                 return 2;
2972
2973         return 1;
2974 }
2975
2976 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2977  * Illegal bytes are set one. */
2978 static const unsigned char utf8_bytes[256] = {
2979         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,
2980         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,
2981         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,
2982         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,
2983         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,
2984         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,
2985         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,
2986         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,
2987 };
2988
2989 /* Decode UTF-8 multi-byte representation into a unicode character. */
2990 static inline unsigned long
2991 utf8_to_unicode(const char *string, size_t length)
2992 {
2993         unsigned long unicode;
2994
2995         switch (length) {
2996         case 1:
2997                 unicode  =   string[0];
2998                 break;
2999         case 2:
3000                 unicode  =  (string[0] & 0x1f) << 6;
3001                 unicode +=  (string[1] & 0x3f);
3002                 break;
3003         case 3:
3004                 unicode  =  (string[0] & 0x0f) << 12;
3005                 unicode += ((string[1] & 0x3f) << 6);
3006                 unicode +=  (string[2] & 0x3f);
3007                 break;
3008         case 4:
3009                 unicode  =  (string[0] & 0x0f) << 18;
3010                 unicode += ((string[1] & 0x3f) << 12);
3011                 unicode += ((string[2] & 0x3f) << 6);
3012                 unicode +=  (string[3] & 0x3f);
3013                 break;
3014         case 5:
3015                 unicode  =  (string[0] & 0x0f) << 24;
3016                 unicode += ((string[1] & 0x3f) << 18);
3017                 unicode += ((string[2] & 0x3f) << 12);
3018                 unicode += ((string[3] & 0x3f) << 6);
3019                 unicode +=  (string[4] & 0x3f);
3020                 break;
3021         case 6:
3022                 unicode  =  (string[0] & 0x01) << 30;
3023                 unicode += ((string[1] & 0x3f) << 24);
3024                 unicode += ((string[2] & 0x3f) << 18);
3025                 unicode += ((string[3] & 0x3f) << 12);
3026                 unicode += ((string[4] & 0x3f) << 6);
3027                 unicode +=  (string[5] & 0x3f);
3028                 break;
3029         default:
3030                 die("Invalid unicode length");
3031         }
3032
3033         /* Invalid characters could return the special 0xfffd value but NUL
3034          * should be just as good. */
3035         return unicode > 0xffff ? 0 : unicode;
3036 }
3037
3038 /* Calculates how much of string can be shown within the given maximum width
3039  * and sets trimmed parameter to non-zero value if all of string could not be
3040  * shown.
3041  *
3042  * Additionally, adds to coloffset how many many columns to move to align with
3043  * the expected position. Takes into account how multi-byte and double-width
3044  * characters will effect the cursor position.
3045  *
3046  * Returns the number of bytes to output from string to satisfy max_width. */
3047 static size_t
3048 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3049 {
3050         const char *start = string;
3051         const char *end = strchr(string, '\0');
3052         size_t mbwidth = 0;
3053         size_t width = 0;
3054
3055         *trimmed = 0;
3056
3057         while (string < end) {
3058                 int c = *(unsigned char *) string;
3059                 unsigned char bytes = utf8_bytes[c];
3060                 size_t ucwidth;
3061                 unsigned long unicode;
3062
3063                 if (string + bytes > end)
3064                         break;
3065
3066                 /* Change representation to figure out whether
3067                  * it is a single- or double-width character. */
3068
3069                 unicode = utf8_to_unicode(string, bytes);
3070                 /* FIXME: Graceful handling of invalid unicode character. */
3071                 if (!unicode)
3072                         break;
3073
3074                 ucwidth = unicode_width(unicode);
3075                 width  += ucwidth;
3076                 if (width > max_width) {
3077                         *trimmed = 1;
3078                         break;
3079                 }
3080
3081                 /* The column offset collects the differences between the
3082                  * number of bytes encoding a character and the number of
3083                  * columns will be used for rendering said character.
3084                  *
3085                  * So if some character A is encoded in 2 bytes, but will be
3086                  * represented on the screen using only 1 byte this will and up
3087                  * adding 1 to the multi-byte column offset.
3088                  *
3089                  * Assumes that no double-width character can be encoding in
3090                  * less than two bytes. */
3091                 if (bytes > ucwidth)
3092                         mbwidth += bytes - ucwidth;
3093
3094                 string  += bytes;
3095         }
3096
3097         *coloffset += mbwidth;
3098
3099         return string - start;
3100 }
3101
3102
3103 /*
3104  * Status management
3105  */
3106
3107 /* Whether or not the curses interface has been initialized. */
3108 static bool cursed = FALSE;
3109
3110 /* The status window is used for polling keystrokes. */
3111 static WINDOW *status_win;
3112
3113 /* Update status and title window. */
3114 static void
3115 report(const char *msg, ...)
3116 {
3117         static bool empty = TRUE;
3118         struct view *view = display[current_view];
3119
3120         if (!empty || *msg) {
3121                 va_list args;
3122
3123                 va_start(args, msg);
3124
3125                 werase(status_win);
3126                 wmove(status_win, 0, 0);
3127                 if (*msg) {
3128                         vwprintw(status_win, msg, args);
3129                         empty = FALSE;
3130                 } else {
3131                         empty = TRUE;
3132                 }
3133                 wrefresh(status_win);
3134
3135                 va_end(args);
3136         }
3137
3138         update_view_title(view);
3139         update_display_cursor();
3140 }
3141
3142 /* Controls when nodelay should be in effect when polling user input. */
3143 static void
3144 set_nonblocking_input(bool loading)
3145 {
3146         static unsigned int loading_views;
3147
3148         if ((loading == FALSE && loading_views-- == 1) ||
3149             (loading == TRUE  && loading_views++ == 0))
3150                 nodelay(status_win, loading);
3151 }
3152
3153 static void
3154 init_display(void)
3155 {
3156         int x, y;
3157
3158         /* Initialize the curses library */
3159         if (isatty(STDIN_FILENO)) {
3160                 cursed = !!initscr();
3161         } else {
3162                 /* Leave stdin and stdout alone when acting as a pager. */
3163                 FILE *io = fopen("/dev/tty", "r+");
3164
3165                 if (!io)
3166                         die("Failed to open /dev/tty");
3167                 cursed = !!newterm(NULL, io, io);
3168         }
3169
3170         if (!cursed)
3171                 die("Failed to initialize curses");
3172
3173         nonl();         /* Tell curses not to do NL->CR/NL on output */
3174         cbreak();       /* Take input chars one at a time, no wait for \n */
3175         noecho();       /* Don't echo input */
3176         leaveok(stdscr, TRUE);
3177
3178         if (has_colors())
3179                 init_colors();
3180
3181         getmaxyx(stdscr, y, x);
3182         status_win = newwin(1, 0, y - 1, 0);
3183         if (!status_win)
3184                 die("Failed to create status window");
3185
3186         /* Enable keyboard mapping */
3187         keypad(status_win, TRUE);
3188         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3189 }
3190
3191 static char *
3192 read_prompt(const char *prompt)
3193 {
3194         enum { READING, STOP, CANCEL } status = READING;
3195         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3196         int pos = 0;
3197
3198         while (status == READING) {
3199                 struct view *view;
3200                 int i, key;
3201
3202                 foreach_view (view, i)
3203                         update_view(view);
3204
3205                 report("%s%.*s", prompt, pos, buf);
3206                 /* Refresh, accept single keystroke of input */
3207                 key = wgetch(status_win);
3208                 switch (key) {
3209                 case KEY_RETURN:
3210                 case KEY_ENTER:
3211                 case '\n':
3212                         status = pos ? STOP : CANCEL;
3213                         break;
3214
3215                 case KEY_BACKSPACE:
3216                         if (pos > 0)
3217                                 pos--;
3218                         else
3219                                 status = CANCEL;
3220                         break;
3221
3222                 case KEY_ESC:
3223                         status = CANCEL;
3224                         break;
3225
3226                 case ERR:
3227                         break;
3228
3229                 default:
3230                         if (pos >= sizeof(buf)) {
3231                                 report("Input string too long");
3232                                 return NULL;
3233                         }
3234
3235                         if (isprint(key))
3236                                 buf[pos++] = (char) key;
3237                 }
3238         }
3239
3240         if (status == CANCEL) {
3241                 /* Clear the status window */
3242                 report("");
3243                 return NULL;
3244         }
3245
3246         buf[pos++] = 0;
3247
3248         return buf;
3249 }
3250
3251 /*
3252  * Repository references
3253  */
3254
3255 static struct ref *refs;
3256 static size_t refs_size;
3257
3258 /* Id <-> ref store */
3259 static struct ref ***id_refs;
3260 static size_t id_refs_size;
3261
3262 static struct ref **
3263 get_refs(char *id)
3264 {
3265         struct ref ***tmp_id_refs;
3266         struct ref **ref_list = NULL;
3267         size_t ref_list_size = 0;
3268         size_t i;
3269
3270         for (i = 0; i < id_refs_size; i++)
3271                 if (!strcmp(id, id_refs[i][0]->id))
3272                         return id_refs[i];
3273
3274         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3275         if (!tmp_id_refs)
3276                 return NULL;
3277
3278         id_refs = tmp_id_refs;
3279
3280         for (i = 0; i < refs_size; i++) {
3281                 struct ref **tmp;
3282
3283                 if (strcmp(id, refs[i].id))
3284                         continue;
3285
3286                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3287                 if (!tmp) {
3288                         if (ref_list)
3289                                 free(ref_list);
3290                         return NULL;
3291                 }
3292
3293                 ref_list = tmp;
3294                 if (ref_list_size > 0)
3295                         ref_list[ref_list_size - 1]->next = 1;
3296                 ref_list[ref_list_size] = &refs[i];
3297
3298                 /* XXX: The properties of the commit chains ensures that we can
3299                  * safely modify the shared ref. The repo references will
3300                  * always be similar for the same id. */
3301                 ref_list[ref_list_size]->next = 0;
3302                 ref_list_size++;
3303         }
3304
3305         if (ref_list)
3306                 id_refs[id_refs_size++] = ref_list;
3307
3308         return ref_list;
3309 }
3310
3311 static int
3312 read_ref(char *id, int idlen, char *name, int namelen)
3313 {
3314         struct ref *ref;
3315         bool tag = FALSE;
3316
3317         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3318                 /* Commits referenced by tags has "^{}" appended. */
3319                 if (name[namelen - 1] != '}')
3320                         return OK;
3321
3322                 while (namelen > 0 && name[namelen] != '^')
3323                         namelen--;
3324
3325                 tag = TRUE;
3326                 namelen -= STRING_SIZE("refs/tags/");
3327                 name    += STRING_SIZE("refs/tags/");
3328
3329         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3330                 namelen -= STRING_SIZE("refs/heads/");
3331                 name    += STRING_SIZE("refs/heads/");
3332
3333         } else if (!strcmp(name, "HEAD")) {
3334                 return OK;
3335         }
3336
3337         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3338         if (!refs)
3339                 return ERR;
3340
3341         ref = &refs[refs_size++];
3342         ref->name = malloc(namelen + 1);
3343         if (!ref->name)
3344                 return ERR;
3345
3346         strncpy(ref->name, name, namelen);
3347         ref->name[namelen] = 0;
3348         ref->tag = tag;
3349         string_copy(ref->id, id);
3350
3351         return OK;
3352 }
3353
3354 static int
3355 load_refs(void)
3356 {
3357         const char *cmd_env = getenv("TIG_LS_REMOTE");
3358         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3359
3360         return read_properties(popen(cmd, "r"), "\t", read_ref);
3361 }
3362
3363 static int
3364 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3365 {
3366         if (!strcmp(name, "i18n.commitencoding"))
3367                 string_copy(opt_encoding, value);
3368
3369         return OK;
3370 }
3371
3372 static int
3373 load_repo_config(void)
3374 {
3375         return read_properties(popen("git repo-config --list", "r"),
3376                                "=", read_repo_config_option);
3377 }
3378
3379 static int
3380 read_properties(FILE *pipe, const char *separators,
3381                 int (*read_property)(char *, int, char *, int))
3382 {
3383         char buffer[BUFSIZ];
3384         char *name;
3385         int state = OK;
3386
3387         if (!pipe)
3388                 return ERR;
3389
3390         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3391                 char *value;
3392                 size_t namelen;
3393                 size_t valuelen;
3394
3395                 name = chomp_string(name);
3396                 namelen = strcspn(name, separators);
3397
3398                 if (name[namelen]) {
3399                         name[namelen] = 0;
3400                         value = chomp_string(name + namelen + 1);
3401                         valuelen = strlen(value);
3402
3403                 } else {
3404                         value = "";
3405                         valuelen = 0;
3406                 }
3407
3408                 state = read_property(name, namelen, value, valuelen);
3409         }
3410
3411         if (state != ERR && ferror(pipe))
3412                 state = ERR;
3413
3414         pclose(pipe);
3415
3416         return state;
3417 }
3418
3419
3420 /*
3421  * Main
3422  */
3423
3424 static void __NORETURN
3425 quit(int sig)
3426 {
3427         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3428         if (cursed)
3429                 endwin();
3430         exit(0);
3431 }
3432
3433 static void __NORETURN
3434 die(const char *err, ...)
3435 {
3436         va_list args;
3437
3438         endwin();
3439
3440         va_start(args, err);
3441         fputs("tig: ", stderr);
3442         vfprintf(stderr, err, args);
3443         fputs("\n", stderr);
3444         va_end(args);
3445
3446         exit(1);
3447 }
3448
3449 int
3450 main(int argc, char *argv[])
3451 {
3452         struct view *view;
3453         enum request request;
3454         size_t i;
3455
3456         signal(SIGINT, quit);
3457
3458         if (setlocale(LC_ALL, "")) {
3459                 string_copy(opt_codeset, nl_langinfo(CODESET));
3460         }
3461
3462         if (load_options() == ERR)
3463                 die("Failed to load user config.");
3464
3465         /* Load the repo config file so options can be overwritten from
3466          * the command line.  */
3467         if (load_repo_config() == ERR)
3468                 die("Failed to load repo config.");
3469
3470         if (!parse_options(argc, argv))
3471                 return 0;
3472
3473         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3474                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3475                 if (opt_iconv == ICONV_NONE)
3476                         die("Failed to initialize character set conversion");
3477         }
3478
3479         if (load_refs() == ERR)
3480                 die("Failed to load refs.");
3481
3482         /* Require a git repository unless when running in pager mode. */
3483         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3484                 die("Not a git repository");
3485
3486         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3487                 view->cmd_env = getenv(view->cmd_env);
3488
3489         request = opt_request;
3490
3491         init_display();
3492
3493         while (view_driver(display[current_view], request)) {
3494                 int key;
3495                 int i;
3496
3497                 foreach_view (view, i)
3498                         update_view(view);
3499
3500                 /* Refresh, accept single keystroke of input */
3501                 key = wgetch(status_win);
3502
3503                 request = get_keybinding(display[current_view]->keymap, key);
3504
3505                 /* Some low-level request handling. This keeps access to
3506                  * status_win restricted. */
3507                 switch (request) {
3508                 case REQ_PROMPT:
3509                 {
3510                         char *cmd = read_prompt(":");
3511
3512                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3513                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3514                                         opt_request = REQ_VIEW_DIFF;
3515                                 } else {
3516                                         opt_request = REQ_VIEW_PAGER;
3517                                 }
3518                                 break;
3519                         }
3520
3521                         request = REQ_NONE;
3522                         break;
3523                 }
3524                 case REQ_SEARCH:
3525                 case REQ_SEARCH_BACK:
3526                 {
3527                         const char *prompt = request == REQ_SEARCH
3528                                            ? "/" : "?";
3529                         char *search = read_prompt(prompt);
3530
3531                         if (search)
3532                                 string_copy(opt_search, search);
3533                         else
3534                                 request = REQ_NONE;
3535                         break;
3536                 }
3537                 case REQ_SCREEN_RESIZE:
3538                 {
3539                         int height, width;
3540
3541                         getmaxyx(stdscr, height, width);
3542
3543                         /* Resize the status view and let the view driver take
3544                          * care of resizing the displayed views. */
3545                         wresize(status_win, 1, width);
3546                         mvwin(status_win, height - 1, 0);
3547                         wrefresh(status_win);
3548                         break;
3549                 }
3550                 default:
3551                         break;
3552                 }
3553         }
3554
3555         quit(0);
3556
3557         return 0;
3558 }