Add support for showing tags and other repo refs in the diff and log view
[tig] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13 /**
14  * TIG(1)
15  * ======
16  *
17  * NAME
18  * ----
19  * tig - text-mode interface for git
20  *
21  * SYNOPSIS
22  * --------
23  * [verse]
24  * tig [options]
25  * tig [options] [--] [git log options]
26  * tig [options] log  [git log options]
27  * tig [options] diff [git diff options]
28  * tig [options] show [git show options]
29  * tig [options] <    [git command output]
30  *
31  * DESCRIPTION
32  * -----------
33  * Browse changes in a git repository. Additionally, tig(1) can also act
34  * as a pager for output of various git commands.
35  *
36  * When browsing repositories, tig(1) uses the underlying git commands
37  * to present the user with various views, such as summarized commit log
38  * and showing the commit with the log message, diffstat, and the diff.
39  *
40  * Using tig(1) as a pager, it will display input from stdin and try
41  * to colorize it.
42  **/
43
44 #ifndef VERSION
45 #define VERSION "tig-0.3"
46 #endif
47
48 #ifndef DEBUG
49 #define NDEBUG
50 #endif
51
52 #include <assert.h>
53 #include <errno.h>
54 #include <ctype.h>
55 #include <signal.h>
56 #include <stdarg.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61 #include <time.h>
62
63 #include <curses.h>
64
65 static void die(const char *err, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
70 static void load_help_page(void);
71
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74
75 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x)  (sizeof(x) - 1)
77
78 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
79 #define SIZEOF_CMD      1024    /* Size of command buffer. */
80
81 /* This color name can be used to refer to the default term colors. */
82 #define COLOR_DEFAULT   (-1)
83
84 /* The format and size of the date column in the main view. */
85 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
86 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
87
88 #define AUTHOR_COLS     20
89
90 /* The default interval between line numbers. */
91 #define NUMBER_INTERVAL 1
92
93 #define TABSIZE         8
94
95 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
96
97 /* Some ascii-shorthands fitted into the ncurses namespace. */
98 #define KEY_TAB         '\t'
99 #define KEY_RETURN      '\r'
100 #define KEY_ESC         27
101
102
103 struct ref {
104         char *name;             /* Ref name; tag or head names are shortened. */
105         char id[41];            /* Commit SHA1 ID */
106         unsigned int tag:1;     /* Is it a tag? */
107         unsigned int next:1;    /* For ref lists: are there more refs? */
108 };
109
110 static struct ref **get_refs(char *id);
111
112 struct int_map {
113         const char *name;
114         int namelen;
115         int value;
116 };
117
118 static int
119 set_from_int_map(struct int_map *map, size_t map_size,
120                  int *value, const char *name, int namelen)
121 {
122
123         int i;
124
125         for (i = 0; i < map_size; i++)
126                 if (namelen == map[i].namelen &&
127                     !strncasecmp(name, map[i].name, namelen)) {
128                         *value = map[i].value;
129                         return OK;
130                 }
131
132         return ERR;
133 }
134
135
136 /*
137  * String helpers
138  */
139
140 static inline void
141 string_ncopy(char *dst, const char *src, int dstlen)
142 {
143         strncpy(dst, src, dstlen - 1);
144         dst[dstlen - 1] = 0;
145
146 }
147
148 /* Shorthand for safely copying into a fixed buffer. */
149 #define string_copy(dst, src) \
150         string_ncopy(dst, src, sizeof(dst))
151
152 static char *
153 chomp_string(char *name)
154 {
155         int namelen;
156
157         while (isspace(*name))
158                 name++;
159
160         namelen = strlen(name) - 1;
161         while (namelen > 0 && isspace(name[namelen]))
162                 name[namelen--] = 0;
163
164         return name;
165 }
166
167
168 /* Shell quoting
169  *
170  * NOTE: The following is a slightly modified copy of the git project's shell
171  * quoting routines found in the quote.c file.
172  *
173  * Help to copy the thing properly quoted for the shell safety.  any single
174  * quote is replaced with '\'', any exclamation point is replaced with '\!',
175  * and the whole thing is enclosed in a
176  *
177  * E.g.
178  *  original     sq_quote     result
179  *  name     ==> name      ==> 'name'
180  *  a b      ==> a b       ==> 'a b'
181  *  a'b      ==> a'\''b    ==> 'a'\''b'
182  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
183  */
184
185 static size_t
186 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
187 {
188         char c;
189
190 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
191
192         BUFPUT('\'');
193         while ((c = *src++)) {
194                 if (c == '\'' || c == '!') {
195                         BUFPUT('\'');
196                         BUFPUT('\\');
197                         BUFPUT(c);
198                         BUFPUT('\'');
199                 } else {
200                         BUFPUT(c);
201                 }
202         }
203         BUFPUT('\'');
204
205         return bufsize;
206 }
207
208
209 /*
210  * User requests
211  */
212
213 #define REQ_INFO \
214         /* XXX: Keep the view request first and in sync with views[]. */ \
215         REQ_GROUP("View switching") \
216         REQ_(VIEW_MAIN,         "Show main view"), \
217         REQ_(VIEW_DIFF,         "Show diff view"), \
218         REQ_(VIEW_LOG,          "Show log view"), \
219         REQ_(VIEW_HELP,         "Show help page"), \
220         REQ_(VIEW_PAGER,        "Show pager view"), \
221         \
222         REQ_GROUP("View manipulation") \
223         REQ_(ENTER,             "Enter current line and scroll"), \
224         REQ_(NEXT,              "Move to next"), \
225         REQ_(PREVIOUS,          "Move to previous"), \
226         REQ_(VIEW_NEXT,         "Move focus to next view"), \
227         REQ_(VIEW_CLOSE,        "Close the current view"), \
228         REQ_(QUIT,              "Close all views and quit"), \
229         \
230         REQ_GROUP("Cursor navigation") \
231         REQ_(MOVE_UP,           "Move cursor one line up"), \
232         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
233         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
234         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
235         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
236         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
237         \
238         REQ_GROUP("Scrolling") \
239         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
240         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
241         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
242         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
243         \
244         REQ_GROUP("Misc") \
245         REQ_(PROMPT,            "Bring up the prompt"), \
246         REQ_(SCREEN_UPDATE,     "Update the screen"), \
247         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
248         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
249         REQ_(SHOW_VERSION,      "Show version information"), \
250         REQ_(STOP_LOADING,      "Stop all loading views"), \
251         REQ_(TOGGLE_LINENO,     "Toggle line numbers"),
252
253
254 /* User action requests. */
255 enum request {
256 #define REQ_GROUP(help)
257 #define REQ_(req, help) REQ_##req
258
259         /* Offset all requests to avoid conflicts with ncurses getch values. */
260         REQ_OFFSET = KEY_MAX + 1,
261         REQ_INFO
262
263 #undef  REQ_GROUP
264 #undef  REQ_
265 };
266
267 struct request_info {
268         enum request request;
269         char *help;
270 };
271
272 static struct request_info req_info[] = {
273 #define REQ_GROUP(help) { 0, (help) },
274 #define REQ_(req, help) { REQ_##req, (help) }
275         REQ_INFO
276 #undef  REQ_GROUP
277 #undef  REQ_
278 };
279
280 /**
281  * OPTIONS
282  * -------
283  **/
284
285 static const char usage[] =
286 VERSION " (" __DATE__ ")\n"
287 "\n"
288 "Usage: tig [options]\n"
289 "   or: tig [options] [--] [git log options]\n"
290 "   or: tig [options] log  [git log options]\n"
291 "   or: tig [options] diff [git diff options]\n"
292 "   or: tig [options] show [git show options]\n"
293 "   or: tig [options] <    [git command output]\n"
294 "\n"
295 "Options:\n"
296 "  -l                          Start up in log view\n"
297 "  -d                          Start up in diff view\n"
298 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
299 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
300 "  --                          Mark end of tig options\n"
301 "  -v, --version               Show version and exit\n"
302 "  -h, --help                  Show help message and exit\n";
303
304 /* Option and state variables. */
305 static bool opt_line_number     = FALSE;
306 static int opt_num_interval     = NUMBER_INTERVAL;
307 static int opt_tab_size         = TABSIZE;
308 static enum request opt_request = REQ_VIEW_MAIN;
309 static char opt_cmd[SIZEOF_CMD] = "";
310 static char opt_encoding[20]    = "";
311 static bool opt_utf8            = TRUE;
312 static FILE *opt_pipe           = NULL;
313
314 /* Returns the index of log or diff command or -1 to exit. */
315 static bool
316 parse_options(int argc, char *argv[])
317 {
318         int i;
319
320         for (i = 1; i < argc; i++) {
321                 char *opt = argv[i];
322
323                 /**
324                  * -l::
325                  *      Start up in log view using the internal log command.
326                  **/
327                 if (!strcmp(opt, "-l")) {
328                         opt_request = REQ_VIEW_LOG;
329                         continue;
330                 }
331
332                 /**
333                  * -d::
334                  *      Start up in diff view using the internal diff command.
335                  **/
336                 if (!strcmp(opt, "-d")) {
337                         opt_request = REQ_VIEW_DIFF;
338                         continue;
339                 }
340
341                 /**
342                  * -n[INTERVAL], --line-number[=INTERVAL]::
343                  *      Prefix line numbers in log and diff view.
344                  *      Optionally, with interval different than each line.
345                  **/
346                 if (!strncmp(opt, "-n", 2) ||
347                     !strncmp(opt, "--line-number", 13)) {
348                         char *num = opt;
349
350                         if (opt[1] == 'n') {
351                                 num = opt + 2;
352
353                         } else if (opt[STRING_SIZE("--line-number")] == '=') {
354                                 num = opt + STRING_SIZE("--line-number=");
355                         }
356
357                         if (isdigit(*num))
358                                 opt_num_interval = atoi(num);
359
360                         opt_line_number = TRUE;
361                         continue;
362                 }
363
364                 /**
365                  * -b[NSPACES], --tab-size[=NSPACES]::
366                  *      Set the number of spaces tabs should be expanded to.
367                  **/
368                 if (!strncmp(opt, "-b", 2) ||
369                     !strncmp(opt, "--tab-size", 10)) {
370                         char *num = opt;
371
372                         if (opt[1] == 'b') {
373                                 num = opt + 2;
374
375                         } else if (opt[STRING_SIZE("--tab-size")] == '=') {
376                                 num = opt + STRING_SIZE("--tab-size=");
377                         }
378
379                         if (isdigit(*num))
380                                 opt_tab_size = MIN(atoi(num), TABSIZE);
381                         continue;
382                 }
383
384                 /**
385                  * -v, --version::
386                  *      Show version and exit.
387                  **/
388                 if (!strcmp(opt, "-v") ||
389                     !strcmp(opt, "--version")) {
390                         printf("tig version %s\n", VERSION);
391                         return FALSE;
392                 }
393
394                 /**
395                  * -h, --help::
396                  *      Show help message and exit.
397                  **/
398                 if (!strcmp(opt, "-h") ||
399                     !strcmp(opt, "--help")) {
400                         printf(usage);
401                         return FALSE;
402                 }
403
404                 /**
405                  * \--::
406                  *      End of tig(1) options. Useful when specifying command
407                  *      options for the main view. Example:
408                  *
409                  *              $ tig -- --since=1.month
410                  **/
411                 if (!strcmp(opt, "--")) {
412                         i++;
413                         break;
414                 }
415
416                 /**
417                  * log [git log options]::
418                  *      Open log view using the given git log options.
419                  *
420                  * diff [git diff options]::
421                  *      Open diff view using the given git diff options.
422                  *
423                  * show [git show options]::
424                  *      Open diff view using the given git show options.
425                  **/
426                 if (!strcmp(opt, "log") ||
427                     !strcmp(opt, "diff") ||
428                     !strcmp(opt, "show")) {
429                         opt_request = opt[0] == 'l'
430                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
431                         break;
432                 }
433
434                 /**
435                  * [git log options]::
436                  *      tig(1) will stop the option parsing when the first
437                  *      command line parameter not starting with "-" is
438                  *      encountered. All options including this one will be
439                  *      passed to git log when loading the main view.
440                  *      This makes it possible to say:
441                  *
442                  *      $ tig tag-1.0..HEAD
443                  **/
444                 if (opt[0] && opt[0] != '-')
445                         break;
446
447                 die("unknown command '%s'", opt);
448         }
449
450         if (!isatty(STDIN_FILENO)) {
451                 opt_request = REQ_VIEW_PAGER;
452                 opt_pipe = stdin;
453
454         } else if (i < argc) {
455                 size_t buf_size;
456
457                 if (opt_request == REQ_VIEW_MAIN)
458                         /* XXX: This is vulnerable to the user overriding
459                          * options required for the main view parser. */
460                         string_copy(opt_cmd, "git log --stat --pretty=raw");
461                 else
462                         string_copy(opt_cmd, "git");
463                 buf_size = strlen(opt_cmd);
464
465                 while (buf_size < sizeof(opt_cmd) && i < argc) {
466                         opt_cmd[buf_size++] = ' ';
467                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
468                 }
469
470                 if (buf_size >= sizeof(opt_cmd))
471                         die("command too long");
472
473                 opt_cmd[buf_size] = 0;
474
475         }
476
477         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
478                 opt_utf8 = FALSE;
479
480         return TRUE;
481 }
482
483
484 /**
485  * ENVIRONMENT VARIABLES
486  * ---------------------
487  * TIG_LS_REMOTE::
488  *      Set command for retrieving all repository references. The command
489  *      should output data in the same format as git-ls-remote(1).
490  **/
491
492 #define TIG_LS_REMOTE \
493         "git ls-remote . 2>/dev/null"
494
495 /**
496  * TIG_DIFF_CMD::
497  *      The command used for the diff view. By default, git show is used
498  *      as a backend.
499  *
500  * TIG_LOG_CMD::
501  *      The command used for the log view. If you prefer to have both
502  *      author and committer shown in the log view be sure to pass
503  *      `--pretty=fuller` to git log.
504  *
505  * TIG_MAIN_CMD::
506  *      The command used for the main view. Note, you must always specify
507  *      the option: `--pretty=raw` since the main view parser expects to
508  *      read that format.
509  **/
510
511 #define TIG_DIFF_CMD \
512         "git show --patch-with-stat --find-copies-harder -B -C %s"
513
514 #define TIG_LOG_CMD     \
515         "git log --cc --stat -n100 %s"
516
517 #define TIG_MAIN_CMD \
518         "git log --topo-order --stat --pretty=raw %s"
519
520 /* ... silently ignore that the following are also exported. */
521
522 #define TIG_HELP_CMD \
523         ""
524
525 #define TIG_PAGER_CMD \
526         ""
527
528
529 /**
530  * FILES
531  * -----
532  * '~/.tigrc'::
533  *      User configuration file. See tigrc(5) for examples.
534  *
535  * '.git/config'::
536  *      Repository config file. Read on startup with the help of
537  *      git-repo-config(1).
538  **/
539
540 static struct int_map color_map[] = {
541 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
542         COLOR_MAP(DEFAULT),
543         COLOR_MAP(BLACK),
544         COLOR_MAP(BLUE),
545         COLOR_MAP(CYAN),
546         COLOR_MAP(GREEN),
547         COLOR_MAP(MAGENTA),
548         COLOR_MAP(RED),
549         COLOR_MAP(WHITE),
550         COLOR_MAP(YELLOW),
551 };
552
553 static struct int_map attr_map[] = {
554 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
555         ATTR_MAP(NORMAL),
556         ATTR_MAP(BLINK),
557         ATTR_MAP(BOLD),
558         ATTR_MAP(DIM),
559         ATTR_MAP(REVERSE),
560         ATTR_MAP(STANDOUT),
561         ATTR_MAP(UNDERLINE),
562 };
563
564 #define LINE_INFO \
565 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
566 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
568 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
569 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
570 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
571 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
572 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
573 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
574 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
575 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
579 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
580 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
581 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
582 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
583 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
586 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
587 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
588 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
593 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
594 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
595 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
596 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
597 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
598 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
599 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
600 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
601 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
602 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
603
604
605 /*
606  * Line-oriented content detection.
607  */
608
609 enum line_type {
610 #define LINE(type, line, fg, bg, attr) \
611         LINE_##type
612         LINE_INFO
613 #undef  LINE
614 };
615
616 struct line_info {
617         const char *name;       /* Option name. */
618         int namelen;            /* Size of option name. */
619         const char *line;       /* The start of line to match. */
620         int linelen;            /* Size of string to match. */
621         int fg, bg, attr;       /* Color and text attributes for the lines. */
622 };
623
624 static struct line_info line_info[] = {
625 #define LINE(type, line, fg, bg, attr) \
626         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
627         LINE_INFO
628 #undef  LINE
629 };
630
631 static enum line_type
632 get_line_type(char *line)
633 {
634         int linelen = strlen(line);
635         enum line_type type;
636
637         for (type = 0; type < ARRAY_SIZE(line_info); type++)
638                 /* Case insensitive search matches Signed-off-by lines better. */
639                 if (linelen >= line_info[type].linelen &&
640                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
641                         return type;
642
643         return LINE_DEFAULT;
644 }
645
646 static inline int
647 get_line_attr(enum line_type type)
648 {
649         assert(type < ARRAY_SIZE(line_info));
650         return COLOR_PAIR(type) | line_info[type].attr;
651 }
652
653 static struct line_info *
654 get_line_info(char *name, int namelen)
655 {
656         enum line_type type;
657         int i;
658
659         /* Diff-Header -> DIFF_HEADER */
660         for (i = 0; i < namelen; i++) {
661                 if (name[i] == '-')
662                         name[i] = '_';
663                 else if (name[i] == '.')
664                         name[i] = '_';
665         }
666
667         for (type = 0; type < ARRAY_SIZE(line_info); type++)
668                 if (namelen == line_info[type].namelen &&
669                     !strncasecmp(line_info[type].name, name, namelen))
670                         return &line_info[type];
671
672         return NULL;
673 }
674
675 static void
676 init_colors(void)
677 {
678         int default_bg = COLOR_BLACK;
679         int default_fg = COLOR_WHITE;
680         enum line_type type;
681
682         start_color();
683
684         if (use_default_colors() != ERR) {
685                 default_bg = -1;
686                 default_fg = -1;
687         }
688
689         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
690                 struct line_info *info = &line_info[type];
691                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
692                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
693
694                 init_pair(type, fg, bg);
695         }
696 }
697
698 struct line {
699         enum line_type type;
700         void *data;             /* User data */
701 };
702
703
704 /*
705  * User config file handling.
706  */
707
708 #define set_color(color, name, namelen) \
709         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
710
711 #define set_attribute(attr, name, namelen) \
712         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
713
714 static int   config_lineno;
715 static bool  config_errors;
716 static char *config_msg;
717
718 static int
719 set_option(char *opt, int optlen, char *value, int valuelen)
720 {
721         /* Reads: "color" object fgcolor bgcolor [attr] */
722         if (!strcmp(opt, "color")) {
723                 struct line_info *info;
724
725                 value = chomp_string(value);
726                 valuelen = strcspn(value, " \t");
727                 info = get_line_info(value, valuelen);
728                 if (!info) {
729                         config_msg = "Unknown color name";
730                         return ERR;
731                 }
732
733                 value = chomp_string(value + valuelen);
734                 valuelen = strcspn(value, " \t");
735                 if (set_color(&info->fg, value, valuelen) == ERR) {
736                         config_msg = "Unknown color";
737                         return ERR;
738                 }
739
740                 value = chomp_string(value + valuelen);
741                 valuelen = strcspn(value, " \t");
742                 if (set_color(&info->bg, value, valuelen) == ERR) {
743                         config_msg = "Unknown color";
744                         return ERR;
745                 }
746
747                 value = chomp_string(value + valuelen);
748                 if (*value &&
749                     set_attribute(&info->attr, value, strlen(value)) == ERR) {
750                         config_msg = "Unknown attribute";
751                         return ERR;
752                 }
753
754                 return OK;
755         }
756
757         return ERR;
758 }
759
760 static int
761 read_option(char *opt, int optlen, char *value, int valuelen)
762 {
763         config_lineno++;
764         config_msg = "Internal error";
765
766         optlen = strcspn(opt, "#;");
767         if (optlen == 0) {
768                 /* The whole line is a commend or empty. */
769                 return OK;
770
771         } else if (opt[optlen] != 0) {
772                 /* Part of the option name is a comment, so the value part
773                  * should be ignored. */
774                 valuelen = 0;
775                 opt[optlen] = value[valuelen] = 0;
776         } else {
777                 /* Else look for comment endings in the value. */
778                 valuelen = strcspn(value, "#;");
779                 value[valuelen] = 0;
780         }
781
782         if (set_option(opt, optlen, value, valuelen) == ERR) {
783                 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
784                         config_lineno, optlen, opt, config_msg);
785                 config_errors = TRUE;
786         }
787
788         /* Always keep going if errors are encountered. */
789         return OK;
790 }
791
792 static int
793 load_options(void)
794 {
795         char *home = getenv("HOME");
796         char buf[1024];
797         FILE *file;
798
799         config_lineno = 0;
800         config_errors = FALSE;
801
802         if (!home ||
803             snprintf(buf, sizeof(buf), "%s/.tigrc", home) >= sizeof(buf))
804                 return ERR;
805
806         /* It's ok that the file doesn't exist. */
807         file = fopen(buf, "r");
808         if (!file)
809                 return OK;
810
811         if (read_properties(file, " \t", read_option) == ERR ||
812             config_errors == TRUE)
813                 fprintf(stderr, "Errors while loading %s.\n", buf);
814
815         return OK;
816 }
817
818
819 /*
820  * The viewer
821  */
822
823 struct view;
824 struct view_ops;
825
826 /* The display array of active views and the index of the current view. */
827 static struct view *display[2];
828 static unsigned int current_view;
829
830 #define foreach_view(view, i) \
831         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
832
833 #define displayed_views()       (display[1] != NULL ? 2 : 1)
834
835 /* Current head and commit ID */
836 static char ref_commit[SIZEOF_REF]      = "HEAD";
837 static char ref_head[SIZEOF_REF]        = "HEAD";
838
839 struct view {
840         const char *name;       /* View name */
841         const char *cmd_fmt;    /* Default command line format */
842         const char *cmd_env;    /* Command line set via environment */
843         const char *id;         /* Points to either of ref_{head,commit} */
844
845         struct view_ops *ops;   /* View operations */
846
847         char cmd[SIZEOF_CMD];   /* Command buffer */
848         char ref[SIZEOF_REF];   /* Hovered commit reference */
849         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
850
851         int height, width;      /* The width and height of the main window */
852         WINDOW *win;            /* The main window */
853         WINDOW *title;          /* The title window living below the main window */
854
855         /* Navigation */
856         unsigned long offset;   /* Offset of the window top */
857         unsigned long lineno;   /* Current line number */
858
859         /* If non-NULL, points to the view that opened this view. If this view
860          * is closed tig will switch back to the parent view. */
861         struct view *parent;
862
863         /* Buffering */
864         unsigned long lines;    /* Total number of lines */
865         struct line *line;      /* Line index */
866         unsigned long line_size;/* Total number of allocated lines */
867         unsigned int digits;    /* Number of digits in the lines member. */
868
869         /* Loading */
870         FILE *pipe;
871         time_t start_time;
872 };
873
874 struct view_ops {
875         /* What type of content being displayed. Used in the title bar. */
876         const char *type;
877         /* Draw one line; @lineno must be < view->height. */
878         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
879         /* Read one line; updates view->line. */
880         bool (*read)(struct view *view, struct line *prev, char *data);
881         /* Depending on view, change display based on current line. */
882         bool (*enter)(struct view *view, struct line *line);
883 };
884
885 static struct view_ops pager_ops;
886 static struct view_ops main_ops;
887
888 #define VIEW_STR(name, cmd, env, ref, ops) \
889         { name, cmd, #env, ref, ops }
890
891 #define VIEW_(id, name, ops, ref) \
892         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
893
894
895 static struct view views[] = {
896         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
897         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
898         VIEW_(LOG,   "log",   &pager_ops, ref_head),
899         VIEW_(HELP,  "help",  &pager_ops, "static"),
900         VIEW_(PAGER, "pager", &pager_ops, "static"),
901 };
902
903 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
904
905
906 static bool
907 draw_view_line(struct view *view, unsigned int lineno)
908 {
909         if (view->offset + lineno >= view->lines)
910                 return FALSE;
911
912         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
913 }
914
915 static void
916 redraw_view_from(struct view *view, int lineno)
917 {
918         assert(0 <= lineno && lineno < view->height);
919
920         for (; lineno < view->height; lineno++) {
921                 if (!draw_view_line(view, lineno))
922                         break;
923         }
924
925         redrawwin(view->win);
926         wrefresh(view->win);
927 }
928
929 static void
930 redraw_view(struct view *view)
931 {
932         wclear(view->win);
933         redraw_view_from(view, 0);
934 }
935
936
937 static void
938 update_view_title(struct view *view)
939 {
940         if (view == display[current_view])
941                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
942         else
943                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
944
945         werase(view->title);
946         wmove(view->title, 0, 0);
947
948         if (*view->ref)
949                 wprintw(view->title, "[%s] %s", view->name, view->ref);
950         else
951                 wprintw(view->title, "[%s]", view->name);
952
953         if (view->lines || view->pipe) {
954                 unsigned int lines = view->lines
955                                    ? (view->lineno + 1) * 100 / view->lines
956                                    : 0;
957
958                 wprintw(view->title, " - %s %d of %d (%d%%)",
959                         view->ops->type,
960                         view->lineno + 1,
961                         view->lines,
962                         lines);
963         }
964
965         if (view->pipe) {
966                 time_t secs = time(NULL) - view->start_time;
967
968                 /* Three git seconds are a long time ... */
969                 if (secs > 2)
970                         wprintw(view->title, " %lds", secs);
971         }
972
973         wmove(view->title, 0, view->width - 1);
974         wrefresh(view->title);
975 }
976
977 static void
978 resize_display(void)
979 {
980         int offset, i;
981         struct view *base = display[0];
982         struct view *view = display[1] ? display[1] : display[0];
983
984         /* Setup window dimensions */
985
986         getmaxyx(stdscr, base->height, base->width);
987
988         /* Make room for the status window. */
989         base->height -= 1;
990
991         if (view != base) {
992                 /* Horizontal split. */
993                 view->width   = base->width;
994                 view->height  = SCALE_SPLIT_VIEW(base->height);
995                 base->height -= view->height;
996
997                 /* Make room for the title bar. */
998                 view->height -= 1;
999         }
1000
1001         /* Make room for the title bar. */
1002         base->height -= 1;
1003
1004         offset = 0;
1005
1006         foreach_view (view, i) {
1007                 if (!view->win) {
1008                         view->win = newwin(view->height, 0, offset, 0);
1009                         if (!view->win)
1010                                 die("Failed to create %s view", view->name);
1011
1012                         scrollok(view->win, TRUE);
1013
1014                         view->title = newwin(1, 0, offset + view->height, 0);
1015                         if (!view->title)
1016                                 die("Failed to create title window");
1017
1018                 } else {
1019                         wresize(view->win, view->height, view->width);
1020                         mvwin(view->win,   offset, 0);
1021                         mvwin(view->title, offset + view->height, 0);
1022                 }
1023
1024                 offset += view->height + 1;
1025         }
1026 }
1027
1028 static void
1029 redraw_display(void)
1030 {
1031         struct view *view;
1032         int i;
1033
1034         foreach_view (view, i) {
1035                 redraw_view(view);
1036                 update_view_title(view);
1037         }
1038 }
1039
1040 static void
1041 update_display_cursor(void)
1042 {
1043         struct view *view = display[current_view];
1044
1045         /* Move the cursor to the right-most column of the cursor line.
1046          *
1047          * XXX: This could turn out to be a bit expensive, but it ensures that
1048          * the cursor does not jump around. */
1049         if (view->lines) {
1050                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1051                 wrefresh(view->win);
1052         }
1053 }
1054
1055 /*
1056  * Navigation
1057  */
1058
1059 /* Scrolling backend */
1060 static void
1061 do_scroll_view(struct view *view, int lines, bool redraw)
1062 {
1063         /* The rendering expects the new offset. */
1064         view->offset += lines;
1065
1066         assert(0 <= view->offset && view->offset < view->lines);
1067         assert(lines);
1068
1069         /* Redraw the whole screen if scrolling is pointless. */
1070         if (view->height < ABS(lines)) {
1071                 redraw_view(view);
1072
1073         } else {
1074                 int line = lines > 0 ? view->height - lines : 0;
1075                 int end = line + ABS(lines);
1076
1077                 wscrl(view->win, lines);
1078
1079                 for (; line < end; line++) {
1080                         if (!draw_view_line(view, line))
1081                                 break;
1082                 }
1083         }
1084
1085         /* Move current line into the view. */
1086         if (view->lineno < view->offset) {
1087                 view->lineno = view->offset;
1088                 draw_view_line(view, 0);
1089
1090         } else if (view->lineno >= view->offset + view->height) {
1091                 if (view->lineno == view->offset + view->height) {
1092                         /* Clear the hidden line so it doesn't show if the view
1093                          * is scrolled up. */
1094                         wmove(view->win, view->height, 0);
1095                         wclrtoeol(view->win);
1096                 }
1097                 view->lineno = view->offset + view->height - 1;
1098                 draw_view_line(view, view->lineno - view->offset);
1099         }
1100
1101         assert(view->offset <= view->lineno && view->lineno < view->lines);
1102
1103         if (!redraw)
1104                 return;
1105
1106         redrawwin(view->win);
1107         wrefresh(view->win);
1108         report("");
1109 }
1110
1111 /* Scroll frontend */
1112 static void
1113 scroll_view(struct view *view, enum request request)
1114 {
1115         int lines = 1;
1116
1117         switch (request) {
1118         case REQ_SCROLL_PAGE_DOWN:
1119                 lines = view->height;
1120         case REQ_SCROLL_LINE_DOWN:
1121                 if (view->offset + lines > view->lines)
1122                         lines = view->lines - view->offset;
1123
1124                 if (lines == 0 || view->offset + view->height >= view->lines) {
1125                         report("Cannot scroll beyond the last line");
1126                         return;
1127                 }
1128                 break;
1129
1130         case REQ_SCROLL_PAGE_UP:
1131                 lines = view->height;
1132         case REQ_SCROLL_LINE_UP:
1133                 if (lines > view->offset)
1134                         lines = view->offset;
1135
1136                 if (lines == 0) {
1137                         report("Cannot scroll beyond the first line");
1138                         return;
1139                 }
1140
1141                 lines = -lines;
1142                 break;
1143
1144         default:
1145                 die("request %d not handled in switch", request);
1146         }
1147
1148         do_scroll_view(view, lines, TRUE);
1149 }
1150
1151 /* Cursor moving */
1152 static void
1153 move_view(struct view *view, enum request request, bool redraw)
1154 {
1155         int steps;
1156
1157         switch (request) {
1158         case REQ_MOVE_FIRST_LINE:
1159                 steps = -view->lineno;
1160                 break;
1161
1162         case REQ_MOVE_LAST_LINE:
1163                 steps = view->lines - view->lineno - 1;
1164                 break;
1165
1166         case REQ_MOVE_PAGE_UP:
1167                 steps = view->height > view->lineno
1168                       ? -view->lineno : -view->height;
1169                 break;
1170
1171         case REQ_MOVE_PAGE_DOWN:
1172                 steps = view->lineno + view->height >= view->lines
1173                       ? view->lines - view->lineno - 1 : view->height;
1174                 break;
1175
1176         case REQ_MOVE_UP:
1177                 steps = -1;
1178                 break;
1179
1180         case REQ_MOVE_DOWN:
1181                 steps = 1;
1182                 break;
1183
1184         default:
1185                 die("request %d not handled in switch", request);
1186         }
1187
1188         if (steps <= 0 && view->lineno == 0) {
1189                 report("Cannot move beyond the first line");
1190                 return;
1191
1192         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1193                 report("Cannot move beyond the last line");
1194                 return;
1195         }
1196
1197         /* Move the current line */
1198         view->lineno += steps;
1199         assert(0 <= view->lineno && view->lineno < view->lines);
1200
1201         /* Repaint the old "current" line if we be scrolling */
1202         if (ABS(steps) < view->height) {
1203                 int prev_lineno = view->lineno - steps - view->offset;
1204
1205                 wmove(view->win, prev_lineno, 0);
1206                 wclrtoeol(view->win);
1207                 draw_view_line(view,  prev_lineno);
1208         }
1209
1210         /* Check whether the view needs to be scrolled */
1211         if (view->lineno < view->offset ||
1212             view->lineno >= view->offset + view->height) {
1213                 if (steps < 0 && -steps > view->offset) {
1214                         steps = -view->offset;
1215
1216                 } else if (steps > 0) {
1217                         if (view->lineno == view->lines - 1 &&
1218                             view->lines > view->height) {
1219                                 steps = view->lines - view->offset - 1;
1220                                 if (steps >= view->height)
1221                                         steps -= view->height - 1;
1222                         }
1223                 }
1224
1225                 do_scroll_view(view, steps, redraw);
1226                 return;
1227         }
1228
1229         /* Draw the current line */
1230         draw_view_line(view, view->lineno - view->offset);
1231
1232         if (!redraw)
1233                 return;
1234
1235         redrawwin(view->win);
1236         wrefresh(view->win);
1237         report("");
1238 }
1239
1240
1241 /*
1242  * Incremental updating
1243  */
1244
1245 static void
1246 end_update(struct view *view)
1247 {
1248         if (!view->pipe)
1249                 return;
1250         set_nonblocking_input(FALSE);
1251         if (view->pipe == stdin)
1252                 fclose(view->pipe);
1253         else
1254                 pclose(view->pipe);
1255         view->pipe = NULL;
1256 }
1257
1258 static bool
1259 begin_update(struct view *view)
1260 {
1261         const char *id = view->id;
1262
1263         if (view->pipe)
1264                 end_update(view);
1265
1266         if (opt_cmd[0]) {
1267                 string_copy(view->cmd, opt_cmd);
1268                 opt_cmd[0] = 0;
1269                 /* When running random commands, the view ref could have become
1270                  * invalid so clear it. */
1271                 view->ref[0] = 0;
1272         } else {
1273                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1274
1275                 if (snprintf(view->cmd, sizeof(view->cmd), format,
1276                              id, id, id, id, id) >= sizeof(view->cmd))
1277                         return FALSE;
1278         }
1279
1280         /* Special case for the pager view. */
1281         if (opt_pipe) {
1282                 view->pipe = opt_pipe;
1283                 opt_pipe = NULL;
1284         } else {
1285                 view->pipe = popen(view->cmd, "r");
1286         }
1287
1288         if (!view->pipe)
1289                 return FALSE;
1290
1291         set_nonblocking_input(TRUE);
1292
1293         view->offset = 0;
1294         view->lines  = 0;
1295         view->lineno = 0;
1296         string_copy(view->vid, id);
1297
1298         if (view->line) {
1299                 int i;
1300
1301                 for (i = 0; i < view->lines; i++)
1302                         if (view->line[i].data)
1303                                 free(view->line[i].data);
1304
1305                 free(view->line);
1306                 view->line = NULL;
1307         }
1308
1309         view->start_time = time(NULL);
1310
1311         return TRUE;
1312 }
1313
1314 static struct line *
1315 realloc_lines(struct view *view, size_t line_size)
1316 {
1317         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1318
1319         if (!tmp)
1320                 return NULL;
1321
1322         view->line = tmp;
1323         view->line_size = line_size;
1324         return view->line;
1325 }
1326
1327 static bool
1328 update_view(struct view *view)
1329 {
1330         char buffer[BUFSIZ];
1331         char *line;
1332         /* The number of lines to read. If too low it will cause too much
1333          * redrawing (and possible flickering), if too high responsiveness
1334          * will suffer. */
1335         unsigned long lines = view->height;
1336         int redraw_from = -1;
1337
1338         if (!view->pipe)
1339                 return TRUE;
1340
1341         /* Only redraw if lines are visible. */
1342         if (view->offset + view->height >= view->lines)
1343                 redraw_from = view->lines - view->offset;
1344
1345         if (!realloc_lines(view, view->lines + lines))
1346                 goto alloc_error;
1347
1348         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1349                 int linelen = strlen(line);
1350
1351                 struct line *prev = view->lines
1352                                   ? &view->line[view->lines - 1]
1353                                   : NULL;
1354
1355                 if (linelen)
1356                         line[linelen - 1] = 0;
1357
1358                 if (!view->ops->read(view, prev, line))
1359                         goto alloc_error;
1360
1361                 if (lines-- == 1)
1362                         break;
1363         }
1364
1365         {
1366                 int digits;
1367
1368                 lines = view->lines;
1369                 for (digits = 0; lines; digits++)
1370                         lines /= 10;
1371
1372                 /* Keep the displayed view in sync with line number scaling. */
1373                 if (digits != view->digits) {
1374                         view->digits = digits;
1375                         redraw_from = 0;
1376                 }
1377         }
1378
1379         if (redraw_from >= 0) {
1380                 /* If this is an incremental update, redraw the previous line
1381                  * since for commits some members could have changed when
1382                  * loading the main view. */
1383                 if (redraw_from > 0)
1384                         redraw_from--;
1385
1386                 /* Incrementally draw avoids flickering. */
1387                 redraw_view_from(view, redraw_from);
1388         }
1389
1390         /* Update the title _after_ the redraw so that if the redraw picks up a
1391          * commit reference in view->ref it'll be available here. */
1392         update_view_title(view);
1393
1394         if (ferror(view->pipe)) {
1395                 report("Failed to read: %s", strerror(errno));
1396                 goto end;
1397
1398         } else if (feof(view->pipe)) {
1399                 report("");
1400                 goto end;
1401         }
1402
1403         return TRUE;
1404
1405 alloc_error:
1406         report("Allocation failure");
1407
1408 end:
1409         end_update(view);
1410         return FALSE;
1411 }
1412
1413 enum open_flags {
1414         OPEN_DEFAULT = 0,       /* Use default view switching. */
1415         OPEN_SPLIT = 1,         /* Split current view. */
1416         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1417         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1418 };
1419
1420 static void
1421 open_view(struct view *prev, enum request request, enum open_flags flags)
1422 {
1423         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1424         bool split = !!(flags & OPEN_SPLIT);
1425         bool reload = !!(flags & OPEN_RELOAD);
1426         struct view *view = VIEW(request);
1427         int nviews = displayed_views();
1428         struct view *base_view = display[0];
1429
1430         if (view == prev && nviews == 1 && !reload) {
1431                 report("Already in %s view", view->name);
1432                 return;
1433         }
1434
1435         if ((reload || strcmp(view->vid, view->id)) &&
1436             !begin_update(view)) {
1437                 report("Failed to load %s view", view->name);
1438                 return;
1439         }
1440
1441         if (split) {
1442                 display[1] = view;
1443                 if (!backgrounded)
1444                         current_view = 1;
1445         } else {
1446                 /* Maximize the current view. */
1447                 memset(display, 0, sizeof(display));
1448                 current_view = 0;
1449                 display[current_view] = view;
1450         }
1451
1452         /* Resize the view when switching between split- and full-screen,
1453          * or when switching between two different full-screen views. */
1454         if (nviews != displayed_views() ||
1455             (nviews == 1 && base_view != display[0]))
1456                 resize_display();
1457
1458         if (split && prev->lineno - prev->offset >= prev->height) {
1459                 /* Take the title line into account. */
1460                 int lines = prev->lineno - prev->offset - prev->height + 1;
1461
1462                 /* Scroll the view that was split if the current line is
1463                  * outside the new limited view. */
1464                 do_scroll_view(prev, lines, TRUE);
1465         }
1466
1467         if (prev && view != prev) {
1468                 if (split && !backgrounded) {
1469                         /* "Blur" the previous view. */
1470                         update_view_title(prev);
1471                 }
1472
1473                 view->parent = prev;
1474         }
1475
1476         if (view == VIEW(REQ_VIEW_HELP))
1477                 load_help_page();
1478
1479         if (view->pipe && view->lines == 0) {
1480                 /* Clear the old view and let the incremental updating refill
1481                  * the screen. */
1482                 wclear(view->win);
1483                 report("");
1484         } else {
1485                 redraw_view(view);
1486                 report("");
1487         }
1488
1489         /* If the view is backgrounded the above calls to report()
1490          * won't redraw the view title. */
1491         if (backgrounded)
1492                 update_view_title(view);
1493 }
1494
1495
1496 /*
1497  * User request switch noodle
1498  */
1499
1500 static int
1501 view_driver(struct view *view, enum request request)
1502 {
1503         int i;
1504
1505         switch (request) {
1506         case REQ_MOVE_UP:
1507         case REQ_MOVE_DOWN:
1508         case REQ_MOVE_PAGE_UP:
1509         case REQ_MOVE_PAGE_DOWN:
1510         case REQ_MOVE_FIRST_LINE:
1511         case REQ_MOVE_LAST_LINE:
1512                 move_view(view, request, TRUE);
1513                 break;
1514
1515         case REQ_SCROLL_LINE_DOWN:
1516         case REQ_SCROLL_LINE_UP:
1517         case REQ_SCROLL_PAGE_DOWN:
1518         case REQ_SCROLL_PAGE_UP:
1519                 scroll_view(view, request);
1520                 break;
1521
1522         case REQ_VIEW_MAIN:
1523         case REQ_VIEW_DIFF:
1524         case REQ_VIEW_LOG:
1525         case REQ_VIEW_HELP:
1526         case REQ_VIEW_PAGER:
1527                 open_view(view, request, OPEN_DEFAULT);
1528                 break;
1529
1530         case REQ_NEXT:
1531         case REQ_PREVIOUS:
1532                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1533
1534                 if (view == VIEW(REQ_VIEW_DIFF) &&
1535                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1536                         bool redraw = display[1] == view;
1537
1538                         view = view->parent;
1539                         move_view(view, request, redraw);
1540                         if (redraw)
1541                                 update_view_title(view);
1542                 } else {
1543                         move_view(view, request, TRUE);
1544                         break;
1545                 }
1546                 /* Fall-through */
1547
1548         case REQ_ENTER:
1549                 if (!view->lines) {
1550                         report("Nothing to enter");
1551                         break;
1552                 }
1553                 return view->ops->enter(view, &view->line[view->lineno]);
1554
1555         case REQ_VIEW_NEXT:
1556         {
1557                 int nviews = displayed_views();
1558                 int next_view = (current_view + 1) % nviews;
1559
1560                 if (next_view == current_view) {
1561                         report("Only one view is displayed");
1562                         break;
1563                 }
1564
1565                 current_view = next_view;
1566                 /* Blur out the title of the previous view. */
1567                 update_view_title(view);
1568                 report("");
1569                 break;
1570         }
1571         case REQ_TOGGLE_LINENO:
1572                 opt_line_number = !opt_line_number;
1573                 redraw_display();
1574                 break;
1575
1576         case REQ_PROMPT:
1577                 /* Always reload^Wrerun commands from the prompt. */
1578                 open_view(view, opt_request, OPEN_RELOAD);
1579                 break;
1580
1581         case REQ_STOP_LOADING:
1582                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1583                         view = &views[i];
1584                         if (view->pipe)
1585                                 report("Stopped loading the %s view", view->name),
1586                         end_update(view);
1587                 }
1588                 break;
1589
1590         case REQ_SHOW_VERSION:
1591                 report("%s (built %s)", VERSION, __DATE__);
1592                 return TRUE;
1593
1594         case REQ_SCREEN_RESIZE:
1595                 resize_display();
1596                 /* Fall-through */
1597         case REQ_SCREEN_REDRAW:
1598                 redraw_display();
1599                 break;
1600
1601         case REQ_SCREEN_UPDATE:
1602                 doupdate();
1603                 return TRUE;
1604
1605         case REQ_VIEW_CLOSE:
1606                 /* XXX: Mark closed views by letting view->parent point to the
1607                  * view itself. Parents to closed view should never be
1608                  * followed. */
1609                 if (view->parent &&
1610                     view->parent->parent != view->parent) {
1611                         memset(display, 0, sizeof(display));
1612                         current_view = 0;
1613                         display[current_view] = view->parent;
1614                         view->parent = view;
1615                         resize_display();
1616                         redraw_display();
1617                         break;
1618                 }
1619                 /* Fall-through */
1620         case REQ_QUIT:
1621                 return FALSE;
1622
1623         default:
1624                 /* An unknown key will show most commonly used commands. */
1625                 report("Unknown key, press 'h' for help");
1626                 return TRUE;
1627         }
1628
1629         return TRUE;
1630 }
1631
1632
1633 /*
1634  * Pager backend
1635  */
1636
1637 static bool
1638 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1639 {
1640         char *text = line->data;
1641         enum line_type type = line->type;
1642         int textlen = strlen(text);
1643         int attr;
1644
1645         wmove(view->win, lineno, 0);
1646
1647         if (view->offset + lineno == view->lineno) {
1648                 if (type == LINE_COMMIT) {
1649                         string_copy(view->ref, text + 7);
1650                         string_copy(ref_commit, view->ref);
1651                 }
1652
1653                 type = LINE_CURSOR;
1654                 wchgat(view->win, -1, 0, type, NULL);
1655         }
1656
1657         attr = get_line_attr(type);
1658         wattrset(view->win, attr);
1659
1660         if (opt_line_number || opt_tab_size < TABSIZE) {
1661                 static char spaces[] = "                    ";
1662                 int col_offset = 0, col = 0;
1663
1664                 if (opt_line_number) {
1665                         unsigned long real_lineno = view->offset + lineno + 1;
1666
1667                         if (real_lineno == 1 ||
1668                             (real_lineno % opt_num_interval) == 0) {
1669                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1670
1671                         } else {
1672                                 waddnstr(view->win, spaces,
1673                                          MIN(view->digits, STRING_SIZE(spaces)));
1674                         }
1675                         waddstr(view->win, ": ");
1676                         col_offset = view->digits + 2;
1677                 }
1678
1679                 while (text && col_offset + col < view->width) {
1680                         int cols_max = view->width - col_offset - col;
1681                         char *pos = text;
1682                         int cols;
1683
1684                         if (*text == '\t') {
1685                                 text++;
1686                                 assert(sizeof(spaces) > TABSIZE);
1687                                 pos = spaces;
1688                                 cols = opt_tab_size - (col % opt_tab_size);
1689
1690                         } else {
1691                                 text = strchr(text, '\t');
1692                                 cols = line ? text - pos : strlen(pos);
1693                         }
1694
1695                         waddnstr(view->win, pos, MIN(cols, cols_max));
1696                         col += cols;
1697                 }
1698
1699         } else {
1700                 int col = 0, pos = 0;
1701
1702                 for (; pos < textlen && col < view->width; pos++, col++)
1703                         if (text[pos] == '\t')
1704                                 col += TABSIZE - (col % TABSIZE) - 1;
1705
1706                 waddnstr(view->win, text, pos);
1707         }
1708
1709         return TRUE;
1710 }
1711
1712 static void
1713 add_pager_refs(struct view *view, struct line *line)
1714 {
1715         char buf[1024];
1716         char *data = line->data;
1717         struct ref **refs;
1718         int bufpos = 0, refpos = 0;
1719         const char *sep = "Refs: ";
1720
1721         assert(line->type == LINE_COMMIT);
1722
1723         refs = get_refs(data + STRING_SIZE("commit "));
1724         if (!refs)
1725                 return;
1726
1727         do {
1728                 char *begin = "", *end = "";
1729
1730                 if (refs[refpos]->tag) {
1731                         begin = "[";
1732                         end = "]";
1733                 }
1734
1735                 bufpos += snprintf(buf + bufpos, sizeof(buf) - bufpos,
1736                                    "%s%s%s%s", sep, begin, refs[refpos]->name,
1737                                    end);
1738                 if (bufpos >= sizeof(buf))
1739                         break;
1740                 sep = ", ";
1741         } while (refs[refpos++]->next);
1742
1743         if (!bufpos ||
1744             bufpos >= sizeof(buf) ||
1745             !realloc_lines(view, view->line_size + 1))
1746                 return;
1747
1748         line = &view->line[view->lines];
1749         line->data = strdup(buf);
1750         if (!line->data)
1751                 return;
1752
1753         line->type = LINE_PP_REFS;
1754         view->lines++;
1755 }
1756
1757 static bool
1758 pager_read(struct view *view, struct line *prev, char *data)
1759 {
1760         struct line *line = &view->line[view->lines];
1761
1762         line->data = strdup(data);
1763         if (!line->data)
1764                 return FALSE;
1765
1766         line->type = get_line_type(line->data);
1767         view->lines++;
1768
1769         if (line->type == LINE_COMMIT &&
1770             (view == VIEW(REQ_VIEW_DIFF) ||
1771              view == VIEW(REQ_VIEW_LOG)))
1772                 add_pager_refs(view, line);
1773
1774         return TRUE;
1775 }
1776
1777 static bool
1778 pager_enter(struct view *view, struct line *line)
1779 {
1780         int split = 0;
1781
1782         if (line->type == LINE_COMMIT &&
1783            (view == VIEW(REQ_VIEW_LOG) ||
1784             view == VIEW(REQ_VIEW_PAGER))) {
1785                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1786                 split = 1;
1787         }
1788
1789         /* Always scroll the view even if it was split. That way
1790          * you can use Enter to scroll through the log view and
1791          * split open each commit diff. */
1792         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1793
1794         /* FIXME: A minor workaround. Scrolling the view will call report("")
1795          * but if we are scrolling a non-current view this won't properly
1796          * update the view title. */
1797         if (split)
1798                 update_view_title(view);
1799
1800         return TRUE;
1801 }
1802
1803 static struct view_ops pager_ops = {
1804         "line",
1805         pager_draw,
1806         pager_read,
1807         pager_enter,
1808 };
1809
1810
1811 /*
1812  * Main view backend
1813  */
1814
1815 struct commit {
1816         char id[41];            /* SHA1 ID. */
1817         char title[75];         /* The first line of the commit message. */
1818         char author[75];        /* The author of the commit. */
1819         struct tm time;         /* Date from the author ident. */
1820         struct ref **refs;      /* Repository references; tags & branch heads. */
1821 };
1822
1823 static bool
1824 main_draw(struct view *view, struct line *line, unsigned int lineno)
1825 {
1826         char buf[DATE_COLS + 1];
1827         struct commit *commit = line->data;
1828         enum line_type type;
1829         int col = 0;
1830         size_t timelen;
1831         size_t authorlen;
1832         int trimmed = 1;
1833
1834         if (!*commit->author)
1835                 return FALSE;
1836
1837         wmove(view->win, lineno, col);
1838
1839         if (view->offset + lineno == view->lineno) {
1840                 string_copy(view->ref, commit->id);
1841                 string_copy(ref_commit, view->ref);
1842                 type = LINE_CURSOR;
1843                 wattrset(view->win, get_line_attr(type));
1844                 wchgat(view->win, -1, 0, type, NULL);
1845
1846         } else {
1847                 type = LINE_MAIN_COMMIT;
1848                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1849         }
1850
1851         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1852         waddnstr(view->win, buf, timelen);
1853         waddstr(view->win, " ");
1854
1855         col += DATE_COLS;
1856         wmove(view->win, lineno, col);
1857         if (type != LINE_CURSOR)
1858                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1859
1860         if (opt_utf8) {
1861                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1862         } else {
1863                 authorlen = strlen(commit->author);
1864                 if (authorlen > AUTHOR_COLS - 2) {
1865                         authorlen = AUTHOR_COLS - 2;
1866                         trimmed = 1;
1867                 }
1868         }
1869
1870         if (trimmed) {
1871                 waddnstr(view->win, commit->author, authorlen);
1872                 if (type != LINE_CURSOR)
1873                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1874                 waddch(view->win, '~');
1875         } else {
1876                 waddstr(view->win, commit->author);
1877         }
1878
1879         col += AUTHOR_COLS;
1880         if (type != LINE_CURSOR)
1881                 wattrset(view->win, A_NORMAL);
1882
1883         mvwaddch(view->win, lineno, col, ACS_LTEE);
1884         wmove(view->win, lineno, col + 2);
1885         col += 2;
1886
1887         if (commit->refs) {
1888                 size_t i = 0;
1889
1890                 do {
1891                         if (type == LINE_CURSOR)
1892                                 ;
1893                         else if (commit->refs[i]->tag)
1894                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1895                         else
1896                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1897                         waddstr(view->win, "[");
1898                         waddstr(view->win, commit->refs[i]->name);
1899                         waddstr(view->win, "]");
1900                         if (type != LINE_CURSOR)
1901                                 wattrset(view->win, A_NORMAL);
1902                         waddstr(view->win, " ");
1903                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1904                 } while (commit->refs[i++]->next);
1905         }
1906
1907         if (type != LINE_CURSOR)
1908                 wattrset(view->win, get_line_attr(type));
1909
1910         {
1911                 int titlelen = strlen(commit->title);
1912
1913                 if (col + titlelen > view->width)
1914                         titlelen = view->width - col;
1915
1916                 waddnstr(view->win, commit->title, titlelen);
1917         }
1918
1919         return TRUE;
1920 }
1921
1922 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1923 static bool
1924 main_read(struct view *view, struct line *prev, char *line)
1925 {
1926         enum line_type type = get_line_type(line);
1927         struct commit *commit;
1928
1929         switch (type) {
1930         case LINE_COMMIT:
1931                 commit = calloc(1, sizeof(struct commit));
1932                 if (!commit)
1933                         return FALSE;
1934
1935                 line += STRING_SIZE("commit ");
1936
1937                 view->line[view->lines++].data = commit;
1938                 string_copy(commit->id, line);
1939                 commit->refs = get_refs(commit->id);
1940                 break;
1941
1942         case LINE_AUTHOR:
1943         {
1944                 char *ident = line + STRING_SIZE("author ");
1945                 char *end = strchr(ident, '<');
1946
1947                 if (!prev)
1948                         break;
1949
1950                 commit = prev->data;
1951
1952                 if (end) {
1953                         for (; end > ident && isspace(end[-1]); end--) ;
1954                         *end = 0;
1955                 }
1956
1957                 string_copy(commit->author, ident);
1958
1959                 /* Parse epoch and timezone */
1960                 if (end) {
1961                         char *secs = strchr(end + 1, '>');
1962                         char *zone;
1963                         time_t time;
1964
1965                         if (!secs || secs[1] != ' ')
1966                                 break;
1967
1968                         secs += 2;
1969                         time = (time_t) atol(secs);
1970                         zone = strchr(secs, ' ');
1971                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1972                                 long tz;
1973
1974                                 zone++;
1975                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1976                                 tz += ('0' - zone[2]) * 60 * 60;
1977                                 tz += ('0' - zone[3]) * 60;
1978                                 tz += ('0' - zone[4]) * 60;
1979
1980                                 if (zone[0] == '-')
1981                                         tz = -tz;
1982
1983                                 time -= tz;
1984                         }
1985                         gmtime_r(&time, &commit->time);
1986                 }
1987                 break;
1988         }
1989         default:
1990                 if (!prev)
1991                         break;
1992
1993                 commit = prev->data;
1994
1995                 /* Fill in the commit title if it has not already been set. */
1996                 if (commit->title[0])
1997                         break;
1998
1999                 /* Require titles to start with a non-space character at the
2000                  * offset used by git log. */
2001                 /* FIXME: More gracefull handling of titles; append "..." to
2002                  * shortened titles, etc. */
2003                 if (strncmp(line, "    ", 4) ||
2004                     isspace(line[4]))
2005                         break;
2006
2007                 string_copy(commit->title, line + 4);
2008         }
2009
2010         return TRUE;
2011 }
2012
2013 static bool
2014 main_enter(struct view *view, struct line *line)
2015 {
2016         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2017
2018         open_view(view, REQ_VIEW_DIFF, flags);
2019         return TRUE;
2020 }
2021
2022 static struct view_ops main_ops = {
2023         "commit",
2024         main_draw,
2025         main_read,
2026         main_enter,
2027 };
2028
2029
2030 /*
2031  * Keys
2032  */
2033
2034 struct keymap {
2035         int alias;
2036         int request;
2037 };
2038
2039 static struct keymap keymap[] = {
2040         /* View switching */
2041         { 'm',          REQ_VIEW_MAIN },
2042         { 'd',          REQ_VIEW_DIFF },
2043         { 'l',          REQ_VIEW_LOG },
2044         { 'p',          REQ_VIEW_PAGER },
2045         { 'h',          REQ_VIEW_HELP },
2046         { '?',          REQ_VIEW_HELP },
2047
2048         /* View manipulation */
2049         { 'q',          REQ_VIEW_CLOSE },
2050         { KEY_TAB,      REQ_VIEW_NEXT },
2051         { KEY_RETURN,   REQ_ENTER },
2052         { KEY_UP,       REQ_PREVIOUS },
2053         { KEY_DOWN,     REQ_NEXT },
2054
2055         /* Cursor navigation */
2056         { 'k',          REQ_MOVE_UP },
2057         { 'j',          REQ_MOVE_DOWN },
2058         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
2059         { KEY_END,      REQ_MOVE_LAST_LINE },
2060         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
2061         { ' ',          REQ_MOVE_PAGE_DOWN },
2062         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
2063         { 'b',          REQ_MOVE_PAGE_UP },
2064         { '-',          REQ_MOVE_PAGE_UP },
2065
2066         /* Scrolling */
2067         { KEY_IC,       REQ_SCROLL_LINE_UP },
2068         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
2069         { 'w',          REQ_SCROLL_PAGE_UP },
2070         { 's',          REQ_SCROLL_PAGE_DOWN },
2071
2072         /* Misc */
2073         { 'Q',          REQ_QUIT },
2074         { 'z',          REQ_STOP_LOADING },
2075         { 'v',          REQ_SHOW_VERSION },
2076         { 'r',          REQ_SCREEN_REDRAW },
2077         { 'n',          REQ_TOGGLE_LINENO },
2078         { ':',          REQ_PROMPT },
2079
2080         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2081         { ERR,          REQ_SCREEN_UPDATE },
2082
2083         /* Use the ncurses SIGWINCH handler. */
2084         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
2085 };
2086
2087 static enum request
2088 get_request(int key)
2089 {
2090         int i;
2091
2092         for (i = 0; i < ARRAY_SIZE(keymap); i++)
2093                 if (keymap[i].alias == key)
2094                         return keymap[i].request;
2095
2096         return (enum request) key;
2097 }
2098
2099 struct key {
2100         char *name;
2101         int value;
2102 };
2103
2104 static struct key key_table[] = {
2105         { "Enter",      KEY_RETURN },
2106         { "Space",      ' ' },
2107         { "Backspace",  KEY_BACKSPACE },
2108         { "Tab",        KEY_TAB },
2109         { "Escape",     KEY_ESC },
2110         { "Left",       KEY_LEFT },
2111         { "Right",      KEY_RIGHT },
2112         { "Up",         KEY_UP },
2113         { "Down",       KEY_DOWN },
2114         { "Insert",     KEY_IC },
2115         { "Delete",     KEY_DC },
2116         { "Home",       KEY_HOME },
2117         { "End",        KEY_END },
2118         { "PageUp",     KEY_PPAGE },
2119         { "PageDown",   KEY_NPAGE },
2120         { "F1",         KEY_F(1) },
2121         { "F2",         KEY_F(2) },
2122         { "F3",         KEY_F(3) },
2123         { "F4",         KEY_F(4) },
2124         { "F5",         KEY_F(5) },
2125         { "F6",         KEY_F(6) },
2126         { "F7",         KEY_F(7) },
2127         { "F8",         KEY_F(8) },
2128         { "F9",         KEY_F(9) },
2129         { "F10",        KEY_F(10) },
2130         { "F11",        KEY_F(11) },
2131         { "F12",        KEY_F(12) },
2132 };
2133
2134 static char *
2135 get_key(enum request request)
2136 {
2137         static char buf[BUFSIZ];
2138         static char key_char[] = "'X'";
2139         int pos = 0;
2140         char *sep = "    ";
2141         int i;
2142
2143         buf[pos] = 0;
2144
2145         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2146                 char *seq = NULL;
2147                 int key;
2148
2149                 if (keymap[i].request != request)
2150                         continue;
2151
2152                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2153                         if (key_table[key].value == keymap[i].alias)
2154                                 seq = key_table[key].name;
2155
2156                 if (seq == NULL &&
2157                     keymap[i].alias < 127 &&
2158                     isprint(keymap[i].alias)) {
2159                         key_char[1] = (char) keymap[i].alias;
2160                         seq = key_char;
2161                 }
2162
2163                 if (!seq)
2164                         seq = "'?'";
2165
2166                 pos += snprintf(buf + pos, sizeof(buf) - pos, "%s%s", sep, seq);
2167                 if (pos >= sizeof(buf))
2168                         return "Too many keybindings!";
2169                 sep = ", ";
2170         }
2171
2172         return buf;
2173 }
2174
2175 static void load_help_page(void)
2176 {
2177         char buf[BUFSIZ];
2178         struct view *view = VIEW(REQ_VIEW_HELP);
2179         int lines = ARRAY_SIZE(req_info) + 2;
2180         int i;
2181
2182         if (view->lines > 0)
2183                 return;
2184
2185         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2186                 if (!req_info[i].request)
2187                         lines++;
2188
2189         view->line = calloc(lines, sizeof(*view->line));
2190         if (!view->line) {
2191                 report("Allocation failure");
2192                 return;
2193         }
2194
2195         pager_read(view, NULL, "Quick reference for tig keybindings:");
2196
2197         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2198                 char *key;
2199
2200                 if (!req_info[i].request) {
2201                         pager_read(view, NULL, "");
2202                         pager_read(view, NULL, req_info[i].help);
2203                         continue;
2204                 }
2205
2206                 key = get_key(req_info[i].request);
2207                 if (snprintf(buf, sizeof(buf), "%-25s %s", key, req_info[i].help)
2208                     >= sizeof(buf))
2209                         continue;
2210
2211                 pager_read(view, NULL, buf);
2212         }
2213 }
2214
2215
2216 /*
2217  * Unicode / UTF-8 handling
2218  *
2219  * NOTE: Much of the following code for dealing with unicode is derived from
2220  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2221  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2222  */
2223
2224 /* I've (over)annotated a lot of code snippets because I am not entirely
2225  * confident that the approach taken by this small UTF-8 interface is correct.
2226  * --jonas */
2227
2228 static inline int
2229 unicode_width(unsigned long c)
2230 {
2231         if (c >= 0x1100 &&
2232            (c <= 0x115f                         /* Hangul Jamo */
2233             || c == 0x2329
2234             || c == 0x232a
2235             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2236                                                 /* CJK ... Yi */
2237             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2238             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2239             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2240             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2241             || (c >= 0xffe0  && c <= 0xffe6)
2242             || (c >= 0x20000 && c <= 0x2fffd)
2243             || (c >= 0x30000 && c <= 0x3fffd)))
2244                 return 2;
2245
2246         return 1;
2247 }
2248
2249 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2250  * Illegal bytes are set one. */
2251 static const unsigned char utf8_bytes[256] = {
2252         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,
2253         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,
2254         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,
2255         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,
2256         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,
2257         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,
2258         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,
2259         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,
2260 };
2261
2262 /* Decode UTF-8 multi-byte representation into a unicode character. */
2263 static inline unsigned long
2264 utf8_to_unicode(const char *string, size_t length)
2265 {
2266         unsigned long unicode;
2267
2268         switch (length) {
2269         case 1:
2270                 unicode  =   string[0];
2271                 break;
2272         case 2:
2273                 unicode  =  (string[0] & 0x1f) << 6;
2274                 unicode +=  (string[1] & 0x3f);
2275                 break;
2276         case 3:
2277                 unicode  =  (string[0] & 0x0f) << 12;
2278                 unicode += ((string[1] & 0x3f) << 6);
2279                 unicode +=  (string[2] & 0x3f);
2280                 break;
2281         case 4:
2282                 unicode  =  (string[0] & 0x0f) << 18;
2283                 unicode += ((string[1] & 0x3f) << 12);
2284                 unicode += ((string[2] & 0x3f) << 6);
2285                 unicode +=  (string[3] & 0x3f);
2286                 break;
2287         case 5:
2288                 unicode  =  (string[0] & 0x0f) << 24;
2289                 unicode += ((string[1] & 0x3f) << 18);
2290                 unicode += ((string[2] & 0x3f) << 12);
2291                 unicode += ((string[3] & 0x3f) << 6);
2292                 unicode +=  (string[4] & 0x3f);
2293                 break;
2294         case 6:
2295                 unicode  =  (string[0] & 0x01) << 30;
2296                 unicode += ((string[1] & 0x3f) << 24);
2297                 unicode += ((string[2] & 0x3f) << 18);
2298                 unicode += ((string[3] & 0x3f) << 12);
2299                 unicode += ((string[4] & 0x3f) << 6);
2300                 unicode +=  (string[5] & 0x3f);
2301                 break;
2302         default:
2303                 die("Invalid unicode length");
2304         }
2305
2306         /* Invalid characters could return the special 0xfffd value but NUL
2307          * should be just as good. */
2308         return unicode > 0xffff ? 0 : unicode;
2309 }
2310
2311 /* Calculates how much of string can be shown within the given maximum width
2312  * and sets trimmed parameter to non-zero value if all of string could not be
2313  * shown.
2314  *
2315  * Additionally, adds to coloffset how many many columns to move to align with
2316  * the expected position. Takes into account how multi-byte and double-width
2317  * characters will effect the cursor position.
2318  *
2319  * Returns the number of bytes to output from string to satisfy max_width. */
2320 static size_t
2321 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2322 {
2323         const char *start = string;
2324         const char *end = strchr(string, '\0');
2325         size_t mbwidth = 0;
2326         size_t width = 0;
2327
2328         *trimmed = 0;
2329
2330         while (string < end) {
2331                 int c = *(unsigned char *) string;
2332                 unsigned char bytes = utf8_bytes[c];
2333                 size_t ucwidth;
2334                 unsigned long unicode;
2335
2336                 if (string + bytes > end)
2337                         break;
2338
2339                 /* Change representation to figure out whether
2340                  * it is a single- or double-width character. */
2341
2342                 unicode = utf8_to_unicode(string, bytes);
2343                 /* FIXME: Graceful handling of invalid unicode character. */
2344                 if (!unicode)
2345                         break;
2346
2347                 ucwidth = unicode_width(unicode);
2348                 width  += ucwidth;
2349                 if (width > max_width) {
2350                         *trimmed = 1;
2351                         break;
2352                 }
2353
2354                 /* The column offset collects the differences between the
2355                  * number of bytes encoding a character and the number of
2356                  * columns will be used for rendering said character.
2357                  *
2358                  * So if some character A is encoded in 2 bytes, but will be
2359                  * represented on the screen using only 1 byte this will and up
2360                  * adding 1 to the multi-byte column offset.
2361                  *
2362                  * Assumes that no double-width character can be encoding in
2363                  * less than two bytes. */
2364                 if (bytes > ucwidth)
2365                         mbwidth += bytes - ucwidth;
2366
2367                 string  += bytes;
2368         }
2369
2370         *coloffset += mbwidth;
2371
2372         return string - start;
2373 }
2374
2375
2376 /*
2377  * Status management
2378  */
2379
2380 /* Whether or not the curses interface has been initialized. */
2381 static bool cursed = FALSE;
2382
2383 /* The status window is used for polling keystrokes. */
2384 static WINDOW *status_win;
2385
2386 /* Update status and title window. */
2387 static void
2388 report(const char *msg, ...)
2389 {
2390         static bool empty = TRUE;
2391         struct view *view = display[current_view];
2392
2393         if (!empty || *msg) {
2394                 va_list args;
2395
2396                 va_start(args, msg);
2397
2398                 werase(status_win);
2399                 wmove(status_win, 0, 0);
2400                 if (*msg) {
2401                         vwprintw(status_win, msg, args);
2402                         empty = FALSE;
2403                 } else {
2404                         empty = TRUE;
2405                 }
2406                 wrefresh(status_win);
2407
2408                 va_end(args);
2409         }
2410
2411         update_view_title(view);
2412         update_display_cursor();
2413 }
2414
2415 /* Controls when nodelay should be in effect when polling user input. */
2416 static void
2417 set_nonblocking_input(bool loading)
2418 {
2419         static unsigned int loading_views;
2420
2421         if ((loading == FALSE && loading_views-- == 1) ||
2422             (loading == TRUE  && loading_views++ == 0))
2423                 nodelay(status_win, loading);
2424 }
2425
2426 static void
2427 init_display(void)
2428 {
2429         int x, y;
2430
2431         /* Initialize the curses library */
2432         if (isatty(STDIN_FILENO)) {
2433                 cursed = !!initscr();
2434         } else {
2435                 /* Leave stdin and stdout alone when acting as a pager. */
2436                 FILE *io = fopen("/dev/tty", "r+");
2437
2438                 cursed = !!newterm(NULL, io, io);
2439         }
2440
2441         if (!cursed)
2442                 die("Failed to initialize curses");
2443
2444         nonl();         /* Tell curses not to do NL->CR/NL on output */
2445         cbreak();       /* Take input chars one at a time, no wait for \n */
2446         noecho();       /* Don't echo input */
2447         leaveok(stdscr, TRUE);
2448
2449         if (has_colors())
2450                 init_colors();
2451
2452         getmaxyx(stdscr, y, x);
2453         status_win = newwin(1, 0, y - 1, 0);
2454         if (!status_win)
2455                 die("Failed to create status window");
2456
2457         /* Enable keyboard mapping */
2458         keypad(status_win, TRUE);
2459         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2460 }
2461
2462
2463 /*
2464  * Repository references
2465  */
2466
2467 static struct ref *refs;
2468 static size_t refs_size;
2469
2470 /* Id <-> ref store */
2471 static struct ref ***id_refs;
2472 static size_t id_refs_size;
2473
2474 static struct ref **
2475 get_refs(char *id)
2476 {
2477         struct ref ***tmp_id_refs;
2478         struct ref **ref_list = NULL;
2479         size_t ref_list_size = 0;
2480         size_t i;
2481
2482         for (i = 0; i < id_refs_size; i++)
2483                 if (!strcmp(id, id_refs[i][0]->id))
2484                         return id_refs[i];
2485
2486         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2487         if (!tmp_id_refs)
2488                 return NULL;
2489
2490         id_refs = tmp_id_refs;
2491
2492         for (i = 0; i < refs_size; i++) {
2493                 struct ref **tmp;
2494
2495                 if (strcmp(id, refs[i].id))
2496                         continue;
2497
2498                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2499                 if (!tmp) {
2500                         if (ref_list)
2501                                 free(ref_list);
2502                         return NULL;
2503                 }
2504
2505                 ref_list = tmp;
2506                 if (ref_list_size > 0)
2507                         ref_list[ref_list_size - 1]->next = 1;
2508                 ref_list[ref_list_size] = &refs[i];
2509
2510                 /* XXX: The properties of the commit chains ensures that we can
2511                  * safely modify the shared ref. The repo references will
2512                  * always be similar for the same id. */
2513                 ref_list[ref_list_size]->next = 0;
2514                 ref_list_size++;
2515         }
2516
2517         if (ref_list)
2518                 id_refs[id_refs_size++] = ref_list;
2519
2520         return ref_list;
2521 }
2522
2523 static int
2524 read_ref(char *id, int idlen, char *name, int namelen)
2525 {
2526         struct ref *ref;
2527         bool tag = FALSE;
2528         bool tag_commit = FALSE;
2529
2530         /* Commits referenced by tags has "^{}" appended. */
2531         if (name[namelen - 1] == '}') {
2532                 while (namelen > 0 && name[namelen] != '^')
2533                         namelen--;
2534                 if (namelen > 0)
2535                         tag_commit = TRUE;
2536                 name[namelen] = 0;
2537         }
2538
2539         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2540                 if (!tag_commit)
2541                         return OK;
2542                 name += STRING_SIZE("refs/tags/");
2543                 tag = TRUE;
2544
2545         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2546                 name += STRING_SIZE("refs/heads/");
2547
2548         } else if (!strcmp(name, "HEAD")) {
2549                 return OK;
2550         }
2551
2552         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2553         if (!refs)
2554                 return ERR;
2555
2556         ref = &refs[refs_size++];
2557         ref->name = strdup(name);
2558         if (!ref->name)
2559                 return ERR;
2560
2561         ref->tag = tag;
2562         string_copy(ref->id, id);
2563
2564         return OK;
2565 }
2566
2567 static int
2568 load_refs(void)
2569 {
2570         const char *cmd_env = getenv("TIG_LS_REMOTE");
2571         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2572
2573         return read_properties(popen(cmd, "r"), "\t", read_ref);
2574 }
2575
2576 static int
2577 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2578 {
2579         if (!strcmp(name, "i18n.commitencoding")) {
2580                 string_copy(opt_encoding, value);
2581         }
2582
2583         return OK;
2584 }
2585
2586 static int
2587 load_repo_config(void)
2588 {
2589         return read_properties(popen("git repo-config --list", "r"),
2590                                "=", read_repo_config_option);
2591 }
2592
2593 static int
2594 read_properties(FILE *pipe, const char *separators,
2595                 int (*read_property)(char *, int, char *, int))
2596 {
2597         char buffer[BUFSIZ];
2598         char *name;
2599         int state = OK;
2600
2601         if (!pipe)
2602                 return ERR;
2603
2604         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2605                 char *value;
2606                 size_t namelen;
2607                 size_t valuelen;
2608
2609                 name = chomp_string(name);
2610                 namelen = strcspn(name, separators);
2611
2612                 if (name[namelen]) {
2613                         name[namelen] = 0;
2614                         value = chomp_string(name + namelen + 1);
2615                         valuelen = strlen(value);
2616
2617                 } else {
2618                         value = "";
2619                         valuelen = 0;
2620                 }
2621
2622                 state = read_property(name, namelen, value, valuelen);
2623         }
2624
2625         if (state != ERR && ferror(pipe))
2626                 state = ERR;
2627
2628         pclose(pipe);
2629
2630         return state;
2631 }
2632
2633
2634 /*
2635  * Main
2636  */
2637
2638 #if __GNUC__ >= 3
2639 #define __NORETURN __attribute__((__noreturn__))
2640 #else
2641 #define __NORETURN
2642 #endif
2643
2644 static void __NORETURN
2645 quit(int sig)
2646 {
2647         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2648         if (cursed)
2649                 endwin();
2650         exit(0);
2651 }
2652
2653 static void __NORETURN
2654 die(const char *err, ...)
2655 {
2656         va_list args;
2657
2658         endwin();
2659
2660         va_start(args, err);
2661         fputs("tig: ", stderr);
2662         vfprintf(stderr, err, args);
2663         fputs("\n", stderr);
2664         va_end(args);
2665
2666         exit(1);
2667 }
2668
2669 int
2670 main(int argc, char *argv[])
2671 {
2672         struct view *view;
2673         enum request request;
2674         size_t i;
2675
2676         signal(SIGINT, quit);
2677
2678         if (load_options() == ERR)
2679                 die("Failed to load user config.");
2680
2681         /* Load the repo config file so options can be overwritten from
2682          * the command line.  */
2683         if (load_repo_config() == ERR)
2684                 die("Failed to load repo config.");
2685
2686         if (!parse_options(argc, argv))
2687                 return 0;
2688
2689         if (load_refs() == ERR)
2690                 die("Failed to load refs.");
2691
2692         /* Require a git repository unless when running in pager mode. */
2693         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2694                 die("Not a git repository");
2695
2696         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2697                 view->cmd_env = getenv(view->cmd_env);
2698
2699         request = opt_request;
2700
2701         init_display();
2702
2703         while (view_driver(display[current_view], request)) {
2704                 int key;
2705                 int i;
2706
2707                 foreach_view (view, i)
2708                         update_view(view);
2709
2710                 /* Refresh, accept single keystroke of input */
2711                 key = wgetch(status_win);
2712                 request = get_request(key);
2713
2714                 /* Some low-level request handling. This keeps access to
2715                  * status_win restricted. */
2716                 switch (request) {
2717                 case REQ_PROMPT:
2718                         report(":");
2719                         /* Temporarily switch to line-oriented and echoed
2720                          * input. */
2721                         nocbreak();
2722                         echo();
2723
2724                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2725                                 memcpy(opt_cmd, "git ", 4);
2726                                 opt_request = REQ_VIEW_PAGER;
2727                         } else {
2728                                 report("Prompt interrupted by loading view, "
2729                                        "press 'z' to stop loading views");
2730                                 request = REQ_SCREEN_UPDATE;
2731                         }
2732
2733                         noecho();
2734                         cbreak();
2735                         break;
2736
2737                 case REQ_SCREEN_RESIZE:
2738                 {
2739                         int height, width;
2740
2741                         getmaxyx(stdscr, height, width);
2742
2743                         /* Resize the status view and let the view driver take
2744                          * care of resizing the displayed views. */
2745                         wresize(status_win, 1, width);
2746                         mvwin(status_win, height - 1, 0);
2747                         wrefresh(status_win);
2748                         break;
2749                 }
2750                 default:
2751                         break;
2752                 }
2753         }
2754
2755         quit(0);
2756
2757         return 0;
2758 }
2759
2760 /**
2761  * include::BUGS[]
2762  *
2763  * COPYRIGHT
2764  * ---------
2765  * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2766  *
2767  * This program is free software; you can redistribute it and/or modify
2768  * it under the terms of the GNU General Public License as published by
2769  * the Free Software Foundation; either version 2 of the License, or
2770  * (at your option) any later version.
2771  *
2772  * SEE ALSO
2773  * --------
2774  * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2775  * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2776  *
2777  * Other git repository browsers:
2778  *
2779  *  - gitk(1)
2780  *  - qgit(1)
2781  *  - gitview(1)
2782  *
2783  * Sites:
2784  *
2785  * include::SITES[]
2786  **/