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