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