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