Wrap all snprintf usage to simplify error handling
[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, struct line *prev, 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                 struct line *prev = view->lines
1371                                   ? &view->line[view->lines - 1]
1372                                   : NULL;
1373
1374                 if (linelen)
1375                         line[linelen - 1] = 0;
1376
1377                 if (!view->ops->read(view, prev, line))
1378                         goto alloc_error;
1379
1380                 if (lines-- == 1)
1381                         break;
1382         }
1383
1384         {
1385                 int digits;
1386
1387                 lines = view->lines;
1388                 for (digits = 0; lines; digits++)
1389                         lines /= 10;
1390
1391                 /* Keep the displayed view in sync with line number scaling. */
1392                 if (digits != view->digits) {
1393                         view->digits = digits;
1394                         redraw_from = 0;
1395                 }
1396         }
1397
1398         if (redraw_from >= 0) {
1399                 /* If this is an incremental update, redraw the previous line
1400                  * since for commits some members could have changed when
1401                  * loading the main view. */
1402                 if (redraw_from > 0)
1403                         redraw_from--;
1404
1405                 /* Incrementally draw avoids flickering. */
1406                 redraw_view_from(view, redraw_from);
1407         }
1408
1409         /* Update the title _after_ the redraw so that if the redraw picks up a
1410          * commit reference in view->ref it'll be available here. */
1411         update_view_title(view);
1412
1413         if (ferror(view->pipe)) {
1414                 report("Failed to read: %s", strerror(errno));
1415                 goto end;
1416
1417         } else if (feof(view->pipe)) {
1418                 report("");
1419                 goto end;
1420         }
1421
1422         return TRUE;
1423
1424 alloc_error:
1425         report("Allocation failure");
1426
1427 end:
1428         end_update(view);
1429         return FALSE;
1430 }
1431
1432 enum open_flags {
1433         OPEN_DEFAULT = 0,       /* Use default view switching. */
1434         OPEN_SPLIT = 1,         /* Split current view. */
1435         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1436         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1437 };
1438
1439 static void
1440 open_view(struct view *prev, enum request request, enum open_flags flags)
1441 {
1442         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1443         bool split = !!(flags & OPEN_SPLIT);
1444         bool reload = !!(flags & OPEN_RELOAD);
1445         struct view *view = VIEW(request);
1446         int nviews = displayed_views();
1447         struct view *base_view = display[0];
1448
1449         if (view == prev && nviews == 1 && !reload) {
1450                 report("Already in %s view", view->name);
1451                 return;
1452         }
1453
1454         if ((reload || strcmp(view->vid, view->id)) &&
1455             !begin_update(view)) {
1456                 report("Failed to load %s view", view->name);
1457                 return;
1458         }
1459
1460         if (split) {
1461                 display[1] = view;
1462                 if (!backgrounded)
1463                         current_view = 1;
1464         } else {
1465                 /* Maximize the current view. */
1466                 memset(display, 0, sizeof(display));
1467                 current_view = 0;
1468                 display[current_view] = view;
1469         }
1470
1471         /* Resize the view when switching between split- and full-screen,
1472          * or when switching between two different full-screen views. */
1473         if (nviews != displayed_views() ||
1474             (nviews == 1 && base_view != display[0]))
1475                 resize_display();
1476
1477         if (split && prev->lineno - prev->offset >= prev->height) {
1478                 /* Take the title line into account. */
1479                 int lines = prev->lineno - prev->offset - prev->height + 1;
1480
1481                 /* Scroll the view that was split if the current line is
1482                  * outside the new limited view. */
1483                 do_scroll_view(prev, lines, TRUE);
1484         }
1485
1486         if (prev && view != prev) {
1487                 if (split && !backgrounded) {
1488                         /* "Blur" the previous view. */
1489                         update_view_title(prev);
1490                 }
1491
1492                 view->parent = prev;
1493         }
1494
1495         if (view == VIEW(REQ_VIEW_HELP))
1496                 load_help_page();
1497
1498         if (view->pipe && view->lines == 0) {
1499                 /* Clear the old view and let the incremental updating refill
1500                  * the screen. */
1501                 wclear(view->win);
1502                 report("");
1503         } else {
1504                 redraw_view(view);
1505                 report("");
1506         }
1507
1508         /* If the view is backgrounded the above calls to report()
1509          * won't redraw the view title. */
1510         if (backgrounded)
1511                 update_view_title(view);
1512 }
1513
1514
1515 /*
1516  * User request switch noodle
1517  */
1518
1519 static int
1520 view_driver(struct view *view, enum request request)
1521 {
1522         int i;
1523
1524         switch (request) {
1525         case REQ_MOVE_UP:
1526         case REQ_MOVE_DOWN:
1527         case REQ_MOVE_PAGE_UP:
1528         case REQ_MOVE_PAGE_DOWN:
1529         case REQ_MOVE_FIRST_LINE:
1530         case REQ_MOVE_LAST_LINE:
1531                 move_view(view, request, TRUE);
1532                 break;
1533
1534         case REQ_SCROLL_LINE_DOWN:
1535         case REQ_SCROLL_LINE_UP:
1536         case REQ_SCROLL_PAGE_DOWN:
1537         case REQ_SCROLL_PAGE_UP:
1538                 scroll_view(view, request);
1539                 break;
1540
1541         case REQ_VIEW_MAIN:
1542         case REQ_VIEW_DIFF:
1543         case REQ_VIEW_LOG:
1544         case REQ_VIEW_HELP:
1545         case REQ_VIEW_PAGER:
1546                 open_view(view, request, OPEN_DEFAULT);
1547                 break;
1548
1549         case REQ_NEXT:
1550         case REQ_PREVIOUS:
1551                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1552
1553                 if (view == VIEW(REQ_VIEW_DIFF) &&
1554                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1555                         bool redraw = display[1] == view;
1556
1557                         view = view->parent;
1558                         move_view(view, request, redraw);
1559                         if (redraw)
1560                                 update_view_title(view);
1561                 } else {
1562                         move_view(view, request, TRUE);
1563                         break;
1564                 }
1565                 /* Fall-through */
1566
1567         case REQ_ENTER:
1568                 if (!view->lines) {
1569                         report("Nothing to enter");
1570                         break;
1571                 }
1572                 return view->ops->enter(view, &view->line[view->lineno]);
1573
1574         case REQ_VIEW_NEXT:
1575         {
1576                 int nviews = displayed_views();
1577                 int next_view = (current_view + 1) % nviews;
1578
1579                 if (next_view == current_view) {
1580                         report("Only one view is displayed");
1581                         break;
1582                 }
1583
1584                 current_view = next_view;
1585                 /* Blur out the title of the previous view. */
1586                 update_view_title(view);
1587                 report("");
1588                 break;
1589         }
1590         case REQ_TOGGLE_LINENO:
1591                 opt_line_number = !opt_line_number;
1592                 redraw_display();
1593                 break;
1594
1595         case REQ_PROMPT:
1596                 /* Always reload^Wrerun commands from the prompt. */
1597                 open_view(view, opt_request, OPEN_RELOAD);
1598                 break;
1599
1600         case REQ_STOP_LOADING:
1601                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1602                         view = &views[i];
1603                         if (view->pipe)
1604                                 report("Stopped loading the %s view", view->name),
1605                         end_update(view);
1606                 }
1607                 break;
1608
1609         case REQ_SHOW_VERSION:
1610                 report("%s (built %s)", VERSION, __DATE__);
1611                 return TRUE;
1612
1613         case REQ_SCREEN_RESIZE:
1614                 resize_display();
1615                 /* Fall-through */
1616         case REQ_SCREEN_REDRAW:
1617                 redraw_display();
1618                 break;
1619
1620         case REQ_SCREEN_UPDATE:
1621                 doupdate();
1622                 return TRUE;
1623
1624         case REQ_VIEW_CLOSE:
1625                 /* XXX: Mark closed views by letting view->parent point to the
1626                  * view itself. Parents to closed view should never be
1627                  * followed. */
1628                 if (view->parent &&
1629                     view->parent->parent != view->parent) {
1630                         memset(display, 0, sizeof(display));
1631                         current_view = 0;
1632                         display[current_view] = view->parent;
1633                         view->parent = view;
1634                         resize_display();
1635                         redraw_display();
1636                         break;
1637                 }
1638                 /* Fall-through */
1639         case REQ_QUIT:
1640                 return FALSE;
1641
1642         default:
1643                 /* An unknown key will show most commonly used commands. */
1644                 report("Unknown key, press 'h' for help");
1645                 return TRUE;
1646         }
1647
1648         return TRUE;
1649 }
1650
1651
1652 /*
1653  * Pager backend
1654  */
1655
1656 static bool
1657 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1658 {
1659         char *text = line->data;
1660         enum line_type type = line->type;
1661         int textlen = strlen(text);
1662         int attr;
1663
1664         wmove(view->win, lineno, 0);
1665
1666         if (view->offset + lineno == view->lineno) {
1667                 if (type == LINE_COMMIT) {
1668                         string_copy(view->ref, text + 7);
1669                         string_copy(ref_commit, view->ref);
1670                 }
1671
1672                 type = LINE_CURSOR;
1673                 wchgat(view->win, -1, 0, type, NULL);
1674         }
1675
1676         attr = get_line_attr(type);
1677         wattrset(view->win, attr);
1678
1679         if (opt_line_number || opt_tab_size < TABSIZE) {
1680                 static char spaces[] = "                    ";
1681                 int col_offset = 0, col = 0;
1682
1683                 if (opt_line_number) {
1684                         unsigned long real_lineno = view->offset + lineno + 1;
1685
1686                         if (real_lineno == 1 ||
1687                             (real_lineno % opt_num_interval) == 0) {
1688                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1689
1690                         } else {
1691                                 waddnstr(view->win, spaces,
1692                                          MIN(view->digits, STRING_SIZE(spaces)));
1693                         }
1694                         waddstr(view->win, ": ");
1695                         col_offset = view->digits + 2;
1696                 }
1697
1698                 while (text && col_offset + col < view->width) {
1699                         int cols_max = view->width - col_offset - col;
1700                         char *pos = text;
1701                         int cols;
1702
1703                         if (*text == '\t') {
1704                                 text++;
1705                                 assert(sizeof(spaces) > TABSIZE);
1706                                 pos = spaces;
1707                                 cols = opt_tab_size - (col % opt_tab_size);
1708
1709                         } else {
1710                                 text = strchr(text, '\t');
1711                                 cols = line ? text - pos : strlen(pos);
1712                         }
1713
1714                         waddnstr(view->win, pos, MIN(cols, cols_max));
1715                         col += cols;
1716                 }
1717
1718         } else {
1719                 int col = 0, pos = 0;
1720
1721                 for (; pos < textlen && col < view->width; pos++, col++)
1722                         if (text[pos] == '\t')
1723                                 col += TABSIZE - (col % TABSIZE) - 1;
1724
1725                 waddnstr(view->win, text, pos);
1726         }
1727
1728         return TRUE;
1729 }
1730
1731 static void
1732 add_pager_refs(struct view *view, struct line *line)
1733 {
1734         char buf[1024];
1735         char *data = line->data;
1736         struct ref **refs;
1737         int bufpos = 0, refpos = 0;
1738         const char *sep = "Refs: ";
1739
1740         assert(line->type == LINE_COMMIT);
1741
1742         refs = get_refs(data + STRING_SIZE("commit "));
1743         if (!refs)
1744                 return;
1745
1746         do {
1747                 struct ref *ref = refs[refpos];
1748                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1749
1750                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1751                         return;
1752                 sep = ", ";
1753         } while (refs[refpos++]->next);
1754
1755         if (!realloc_lines(view, view->line_size + 1))
1756                 return;
1757
1758         line = &view->line[view->lines];
1759         line->data = strdup(buf);
1760         if (!line->data)
1761                 return;
1762
1763         line->type = LINE_PP_REFS;
1764         view->lines++;
1765 }
1766
1767 static bool
1768 pager_read(struct view *view, struct line *prev, char *data)
1769 {
1770         struct line *line = &view->line[view->lines];
1771
1772         line->data = strdup(data);
1773         if (!line->data)
1774                 return FALSE;
1775
1776         line->type = get_line_type(line->data);
1777         view->lines++;
1778
1779         if (line->type == LINE_COMMIT &&
1780             (view == VIEW(REQ_VIEW_DIFF) ||
1781              view == VIEW(REQ_VIEW_LOG)))
1782                 add_pager_refs(view, line);
1783
1784         return TRUE;
1785 }
1786
1787 static bool
1788 pager_enter(struct view *view, struct line *line)
1789 {
1790         int split = 0;
1791
1792         if (line->type == LINE_COMMIT &&
1793            (view == VIEW(REQ_VIEW_LOG) ||
1794             view == VIEW(REQ_VIEW_PAGER))) {
1795                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1796                 split = 1;
1797         }
1798
1799         /* Always scroll the view even if it was split. That way
1800          * you can use Enter to scroll through the log view and
1801          * split open each commit diff. */
1802         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1803
1804         /* FIXME: A minor workaround. Scrolling the view will call report("")
1805          * but if we are scrolling a non-current view this won't properly
1806          * update the view title. */
1807         if (split)
1808                 update_view_title(view);
1809
1810         return TRUE;
1811 }
1812
1813 static struct view_ops pager_ops = {
1814         "line",
1815         pager_draw,
1816         pager_read,
1817         pager_enter,
1818 };
1819
1820
1821 /*
1822  * Main view backend
1823  */
1824
1825 struct commit {
1826         char id[41];            /* SHA1 ID. */
1827         char title[75];         /* The first line of the commit message. */
1828         char author[75];        /* The author of the commit. */
1829         struct tm time;         /* Date from the author ident. */
1830         struct ref **refs;      /* Repository references; tags & branch heads. */
1831 };
1832
1833 static bool
1834 main_draw(struct view *view, struct line *line, unsigned int lineno)
1835 {
1836         char buf[DATE_COLS + 1];
1837         struct commit *commit = line->data;
1838         enum line_type type;
1839         int col = 0;
1840         size_t timelen;
1841         size_t authorlen;
1842         int trimmed = 1;
1843
1844         if (!*commit->author)
1845                 return FALSE;
1846
1847         wmove(view->win, lineno, col);
1848
1849         if (view->offset + lineno == view->lineno) {
1850                 string_copy(view->ref, commit->id);
1851                 string_copy(ref_commit, view->ref);
1852                 type = LINE_CURSOR;
1853                 wattrset(view->win, get_line_attr(type));
1854                 wchgat(view->win, -1, 0, type, NULL);
1855
1856         } else {
1857                 type = LINE_MAIN_COMMIT;
1858                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1859         }
1860
1861         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1862         waddnstr(view->win, buf, timelen);
1863         waddstr(view->win, " ");
1864
1865         col += DATE_COLS;
1866         wmove(view->win, lineno, col);
1867         if (type != LINE_CURSOR)
1868                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1869
1870         if (opt_utf8) {
1871                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1872         } else {
1873                 authorlen = strlen(commit->author);
1874                 if (authorlen > AUTHOR_COLS - 2) {
1875                         authorlen = AUTHOR_COLS - 2;
1876                         trimmed = 1;
1877                 }
1878         }
1879
1880         if (trimmed) {
1881                 waddnstr(view->win, commit->author, authorlen);
1882                 if (type != LINE_CURSOR)
1883                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1884                 waddch(view->win, '~');
1885         } else {
1886                 waddstr(view->win, commit->author);
1887         }
1888
1889         col += AUTHOR_COLS;
1890         if (type != LINE_CURSOR)
1891                 wattrset(view->win, A_NORMAL);
1892
1893         mvwaddch(view->win, lineno, col, ACS_LTEE);
1894         wmove(view->win, lineno, col + 2);
1895         col += 2;
1896
1897         if (commit->refs) {
1898                 size_t i = 0;
1899
1900                 do {
1901                         if (type == LINE_CURSOR)
1902                                 ;
1903                         else if (commit->refs[i]->tag)
1904                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1905                         else
1906                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1907                         waddstr(view->win, "[");
1908                         waddstr(view->win, commit->refs[i]->name);
1909                         waddstr(view->win, "]");
1910                         if (type != LINE_CURSOR)
1911                                 wattrset(view->win, A_NORMAL);
1912                         waddstr(view->win, " ");
1913                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1914                 } while (commit->refs[i++]->next);
1915         }
1916
1917         if (type != LINE_CURSOR)
1918                 wattrset(view->win, get_line_attr(type));
1919
1920         {
1921                 int titlelen = strlen(commit->title);
1922
1923                 if (col + titlelen > view->width)
1924                         titlelen = view->width - col;
1925
1926                 waddnstr(view->win, commit->title, titlelen);
1927         }
1928
1929         return TRUE;
1930 }
1931
1932 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1933 static bool
1934 main_read(struct view *view, struct line *prev, char *line)
1935 {
1936         enum line_type type = get_line_type(line);
1937         struct commit *commit;
1938
1939         switch (type) {
1940         case LINE_COMMIT:
1941                 commit = calloc(1, sizeof(struct commit));
1942                 if (!commit)
1943                         return FALSE;
1944
1945                 line += STRING_SIZE("commit ");
1946
1947                 view->line[view->lines++].data = commit;
1948                 string_copy(commit->id, line);
1949                 commit->refs = get_refs(commit->id);
1950                 break;
1951
1952         case LINE_AUTHOR:
1953         {
1954                 char *ident = line + STRING_SIZE("author ");
1955                 char *end = strchr(ident, '<');
1956
1957                 if (!prev)
1958                         break;
1959
1960                 commit = prev->data;
1961
1962                 if (end) {
1963                         for (; end > ident && isspace(end[-1]); end--) ;
1964                         *end = 0;
1965                 }
1966
1967                 string_copy(commit->author, ident);
1968
1969                 /* Parse epoch and timezone */
1970                 if (end) {
1971                         char *secs = strchr(end + 1, '>');
1972                         char *zone;
1973                         time_t time;
1974
1975                         if (!secs || secs[1] != ' ')
1976                                 break;
1977
1978                         secs += 2;
1979                         time = (time_t) atol(secs);
1980                         zone = strchr(secs, ' ');
1981                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1982                                 long tz;
1983
1984                                 zone++;
1985                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1986                                 tz += ('0' - zone[2]) * 60 * 60;
1987                                 tz += ('0' - zone[3]) * 60;
1988                                 tz += ('0' - zone[4]) * 60;
1989
1990                                 if (zone[0] == '-')
1991                                         tz = -tz;
1992
1993                                 time -= tz;
1994                         }
1995                         gmtime_r(&time, &commit->time);
1996                 }
1997                 break;
1998         }
1999         default:
2000                 if (!prev)
2001                         break;
2002
2003                 commit = prev->data;
2004
2005                 /* Fill in the commit title if it has not already been set. */
2006                 if (commit->title[0])
2007                         break;
2008
2009                 /* Require titles to start with a non-space character at the
2010                  * offset used by git log. */
2011                 /* FIXME: More gracefull handling of titles; append "..." to
2012                  * shortened titles, etc. */
2013                 if (strncmp(line, "    ", 4) ||
2014                     isspace(line[4]))
2015                         break;
2016
2017                 string_copy(commit->title, line + 4);
2018         }
2019
2020         return TRUE;
2021 }
2022
2023 static bool
2024 main_enter(struct view *view, struct line *line)
2025 {
2026         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2027
2028         open_view(view, REQ_VIEW_DIFF, flags);
2029         return TRUE;
2030 }
2031
2032 static struct view_ops main_ops = {
2033         "commit",
2034         main_draw,
2035         main_read,
2036         main_enter,
2037 };
2038
2039
2040 /*
2041  * Keys
2042  */
2043
2044 struct keymap {
2045         int alias;
2046         int request;
2047 };
2048
2049 static struct keymap keymap[] = {
2050         /* View switching */
2051         { 'm',          REQ_VIEW_MAIN },
2052         { 'd',          REQ_VIEW_DIFF },
2053         { 'l',          REQ_VIEW_LOG },
2054         { 'p',          REQ_VIEW_PAGER },
2055         { 'h',          REQ_VIEW_HELP },
2056         { '?',          REQ_VIEW_HELP },
2057
2058         /* View manipulation */
2059         { 'q',          REQ_VIEW_CLOSE },
2060         { KEY_TAB,      REQ_VIEW_NEXT },
2061         { KEY_RETURN,   REQ_ENTER },
2062         { KEY_UP,       REQ_PREVIOUS },
2063         { KEY_DOWN,     REQ_NEXT },
2064
2065         /* Cursor navigation */
2066         { 'k',          REQ_MOVE_UP },
2067         { 'j',          REQ_MOVE_DOWN },
2068         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
2069         { KEY_END,      REQ_MOVE_LAST_LINE },
2070         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
2071         { ' ',          REQ_MOVE_PAGE_DOWN },
2072         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
2073         { 'b',          REQ_MOVE_PAGE_UP },
2074         { '-',          REQ_MOVE_PAGE_UP },
2075
2076         /* Scrolling */
2077         { KEY_IC,       REQ_SCROLL_LINE_UP },
2078         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
2079         { 'w',          REQ_SCROLL_PAGE_UP },
2080         { 's',          REQ_SCROLL_PAGE_DOWN },
2081
2082         /* Misc */
2083         { 'Q',          REQ_QUIT },
2084         { 'z',          REQ_STOP_LOADING },
2085         { 'v',          REQ_SHOW_VERSION },
2086         { 'r',          REQ_SCREEN_REDRAW },
2087         { 'n',          REQ_TOGGLE_LINENO },
2088         { ':',          REQ_PROMPT },
2089
2090         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2091         { ERR,          REQ_SCREEN_UPDATE },
2092
2093         /* Use the ncurses SIGWINCH handler. */
2094         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
2095 };
2096
2097 static enum request
2098 get_request(int key)
2099 {
2100         int i;
2101
2102         for (i = 0; i < ARRAY_SIZE(keymap); i++)
2103                 if (keymap[i].alias == key)
2104                         return keymap[i].request;
2105
2106         return (enum request) key;
2107 }
2108
2109 struct key {
2110         char *name;
2111         int value;
2112 };
2113
2114 static struct key key_table[] = {
2115         { "Enter",      KEY_RETURN },
2116         { "Space",      ' ' },
2117         { "Backspace",  KEY_BACKSPACE },
2118         { "Tab",        KEY_TAB },
2119         { "Escape",     KEY_ESC },
2120         { "Left",       KEY_LEFT },
2121         { "Right",      KEY_RIGHT },
2122         { "Up",         KEY_UP },
2123         { "Down",       KEY_DOWN },
2124         { "Insert",     KEY_IC },
2125         { "Delete",     KEY_DC },
2126         { "Home",       KEY_HOME },
2127         { "End",        KEY_END },
2128         { "PageUp",     KEY_PPAGE },
2129         { "PageDown",   KEY_NPAGE },
2130         { "F1",         KEY_F(1) },
2131         { "F2",         KEY_F(2) },
2132         { "F3",         KEY_F(3) },
2133         { "F4",         KEY_F(4) },
2134         { "F5",         KEY_F(5) },
2135         { "F6",         KEY_F(6) },
2136         { "F7",         KEY_F(7) },
2137         { "F8",         KEY_F(8) },
2138         { "F9",         KEY_F(9) },
2139         { "F10",        KEY_F(10) },
2140         { "F11",        KEY_F(11) },
2141         { "F12",        KEY_F(12) },
2142 };
2143
2144 static char *
2145 get_key(enum request request)
2146 {
2147         static char buf[BUFSIZ];
2148         static char key_char[] = "'X'";
2149         int pos = 0;
2150         char *sep = "    ";
2151         int i;
2152
2153         buf[pos] = 0;
2154
2155         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2156                 char *seq = NULL;
2157                 int key;
2158
2159                 if (keymap[i].request != request)
2160                         continue;
2161
2162                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2163                         if (key_table[key].value == keymap[i].alias)
2164                                 seq = key_table[key].name;
2165
2166                 if (seq == NULL &&
2167                     keymap[i].alias < 127 &&
2168                     isprint(keymap[i].alias)) {
2169                         key_char[1] = (char) keymap[i].alias;
2170                         seq = key_char;
2171                 }
2172
2173                 if (!seq)
2174                         seq = "'?'";
2175
2176                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
2177                         return "Too many keybindings!";
2178                 sep = ", ";
2179         }
2180
2181         return buf;
2182 }
2183
2184 static void load_help_page(void)
2185 {
2186         char buf[BUFSIZ];
2187         struct view *view = VIEW(REQ_VIEW_HELP);
2188         int lines = ARRAY_SIZE(req_info) + 2;
2189         int i;
2190
2191         if (view->lines > 0)
2192                 return;
2193
2194         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2195                 if (!req_info[i].request)
2196                         lines++;
2197
2198         view->line = calloc(lines, sizeof(*view->line));
2199         if (!view->line) {
2200                 report("Allocation failure");
2201                 return;
2202         }
2203
2204         pager_read(view, NULL, "Quick reference for tig keybindings:");
2205
2206         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2207                 char *key;
2208
2209                 if (!req_info[i].request) {
2210                         pager_read(view, NULL, "");
2211                         pager_read(view, NULL, req_info[i].help);
2212                         continue;
2213                 }
2214
2215                 key = get_key(req_info[i].request);
2216                 if (string_format(buf, "%-25s %s", key, req_info[i].help))
2217                         continue;
2218
2219                 pager_read(view, NULL, buf);
2220         }
2221 }
2222
2223
2224 /*
2225  * Unicode / UTF-8 handling
2226  *
2227  * NOTE: Much of the following code for dealing with unicode is derived from
2228  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2229  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2230  */
2231
2232 /* I've (over)annotated a lot of code snippets because I am not entirely
2233  * confident that the approach taken by this small UTF-8 interface is correct.
2234  * --jonas */
2235
2236 static inline int
2237 unicode_width(unsigned long c)
2238 {
2239         if (c >= 0x1100 &&
2240            (c <= 0x115f                         /* Hangul Jamo */
2241             || c == 0x2329
2242             || c == 0x232a
2243             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2244                                                 /* CJK ... Yi */
2245             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2246             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2247             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2248             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2249             || (c >= 0xffe0  && c <= 0xffe6)
2250             || (c >= 0x20000 && c <= 0x2fffd)
2251             || (c >= 0x30000 && c <= 0x3fffd)))
2252                 return 2;
2253
2254         return 1;
2255 }
2256
2257 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2258  * Illegal bytes are set one. */
2259 static const unsigned char utf8_bytes[256] = {
2260         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,
2261         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,
2262         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,
2263         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,
2264         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,
2265         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,
2266         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,
2267         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,
2268 };
2269
2270 /* Decode UTF-8 multi-byte representation into a unicode character. */
2271 static inline unsigned long
2272 utf8_to_unicode(const char *string, size_t length)
2273 {
2274         unsigned long unicode;
2275
2276         switch (length) {
2277         case 1:
2278                 unicode  =   string[0];
2279                 break;
2280         case 2:
2281                 unicode  =  (string[0] & 0x1f) << 6;
2282                 unicode +=  (string[1] & 0x3f);
2283                 break;
2284         case 3:
2285                 unicode  =  (string[0] & 0x0f) << 12;
2286                 unicode += ((string[1] & 0x3f) << 6);
2287                 unicode +=  (string[2] & 0x3f);
2288                 break;
2289         case 4:
2290                 unicode  =  (string[0] & 0x0f) << 18;
2291                 unicode += ((string[1] & 0x3f) << 12);
2292                 unicode += ((string[2] & 0x3f) << 6);
2293                 unicode +=  (string[3] & 0x3f);
2294                 break;
2295         case 5:
2296                 unicode  =  (string[0] & 0x0f) << 24;
2297                 unicode += ((string[1] & 0x3f) << 18);
2298                 unicode += ((string[2] & 0x3f) << 12);
2299                 unicode += ((string[3] & 0x3f) << 6);
2300                 unicode +=  (string[4] & 0x3f);
2301                 break;
2302         case 6:
2303                 unicode  =  (string[0] & 0x01) << 30;
2304                 unicode += ((string[1] & 0x3f) << 24);
2305                 unicode += ((string[2] & 0x3f) << 18);
2306                 unicode += ((string[3] & 0x3f) << 12);
2307                 unicode += ((string[4] & 0x3f) << 6);
2308                 unicode +=  (string[5] & 0x3f);
2309                 break;
2310         default:
2311                 die("Invalid unicode length");
2312         }
2313
2314         /* Invalid characters could return the special 0xfffd value but NUL
2315          * should be just as good. */
2316         return unicode > 0xffff ? 0 : unicode;
2317 }
2318
2319 /* Calculates how much of string can be shown within the given maximum width
2320  * and sets trimmed parameter to non-zero value if all of string could not be
2321  * shown.
2322  *
2323  * Additionally, adds to coloffset how many many columns to move to align with
2324  * the expected position. Takes into account how multi-byte and double-width
2325  * characters will effect the cursor position.
2326  *
2327  * Returns the number of bytes to output from string to satisfy max_width. */
2328 static size_t
2329 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2330 {
2331         const char *start = string;
2332         const char *end = strchr(string, '\0');
2333         size_t mbwidth = 0;
2334         size_t width = 0;
2335
2336         *trimmed = 0;
2337
2338         while (string < end) {
2339                 int c = *(unsigned char *) string;
2340                 unsigned char bytes = utf8_bytes[c];
2341                 size_t ucwidth;
2342                 unsigned long unicode;
2343
2344                 if (string + bytes > end)
2345                         break;
2346
2347                 /* Change representation to figure out whether
2348                  * it is a single- or double-width character. */
2349
2350                 unicode = utf8_to_unicode(string, bytes);
2351                 /* FIXME: Graceful handling of invalid unicode character. */
2352                 if (!unicode)
2353                         break;
2354
2355                 ucwidth = unicode_width(unicode);
2356                 width  += ucwidth;
2357                 if (width > max_width) {
2358                         *trimmed = 1;
2359                         break;
2360                 }
2361
2362                 /* The column offset collects the differences between the
2363                  * number of bytes encoding a character and the number of
2364                  * columns will be used for rendering said character.
2365                  *
2366                  * So if some character A is encoded in 2 bytes, but will be
2367                  * represented on the screen using only 1 byte this will and up
2368                  * adding 1 to the multi-byte column offset.
2369                  *
2370                  * Assumes that no double-width character can be encoding in
2371                  * less than two bytes. */
2372                 if (bytes > ucwidth)
2373                         mbwidth += bytes - ucwidth;
2374
2375                 string  += bytes;
2376         }
2377
2378         *coloffset += mbwidth;
2379
2380         return string - start;
2381 }
2382
2383
2384 /*
2385  * Status management
2386  */
2387
2388 /* Whether or not the curses interface has been initialized. */
2389 static bool cursed = FALSE;
2390
2391 /* The status window is used for polling keystrokes. */
2392 static WINDOW *status_win;
2393
2394 /* Update status and title window. */
2395 static void
2396 report(const char *msg, ...)
2397 {
2398         static bool empty = TRUE;
2399         struct view *view = display[current_view];
2400
2401         if (!empty || *msg) {
2402                 va_list args;
2403
2404                 va_start(args, msg);
2405
2406                 werase(status_win);
2407                 wmove(status_win, 0, 0);
2408                 if (*msg) {
2409                         vwprintw(status_win, msg, args);
2410                         empty = FALSE;
2411                 } else {
2412                         empty = TRUE;
2413                 }
2414                 wrefresh(status_win);
2415
2416                 va_end(args);
2417         }
2418
2419         update_view_title(view);
2420         update_display_cursor();
2421 }
2422
2423 /* Controls when nodelay should be in effect when polling user input. */
2424 static void
2425 set_nonblocking_input(bool loading)
2426 {
2427         static unsigned int loading_views;
2428
2429         if ((loading == FALSE && loading_views-- == 1) ||
2430             (loading == TRUE  && loading_views++ == 0))
2431                 nodelay(status_win, loading);
2432 }
2433
2434 static void
2435 init_display(void)
2436 {
2437         int x, y;
2438
2439         /* Initialize the curses library */
2440         if (isatty(STDIN_FILENO)) {
2441                 cursed = !!initscr();
2442         } else {
2443                 /* Leave stdin and stdout alone when acting as a pager. */
2444                 FILE *io = fopen("/dev/tty", "r+");
2445
2446                 cursed = !!newterm(NULL, io, io);
2447         }
2448
2449         if (!cursed)
2450                 die("Failed to initialize curses");
2451
2452         nonl();         /* Tell curses not to do NL->CR/NL on output */
2453         cbreak();       /* Take input chars one at a time, no wait for \n */
2454         noecho();       /* Don't echo input */
2455         leaveok(stdscr, TRUE);
2456
2457         if (has_colors())
2458                 init_colors();
2459
2460         getmaxyx(stdscr, y, x);
2461         status_win = newwin(1, 0, y - 1, 0);
2462         if (!status_win)
2463                 die("Failed to create status window");
2464
2465         /* Enable keyboard mapping */
2466         keypad(status_win, TRUE);
2467         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2468 }
2469
2470
2471 /*
2472  * Repository references
2473  */
2474
2475 static struct ref *refs;
2476 static size_t refs_size;
2477
2478 /* Id <-> ref store */
2479 static struct ref ***id_refs;
2480 static size_t id_refs_size;
2481
2482 static struct ref **
2483 get_refs(char *id)
2484 {
2485         struct ref ***tmp_id_refs;
2486         struct ref **ref_list = NULL;
2487         size_t ref_list_size = 0;
2488         size_t i;
2489
2490         for (i = 0; i < id_refs_size; i++)
2491                 if (!strcmp(id, id_refs[i][0]->id))
2492                         return id_refs[i];
2493
2494         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2495         if (!tmp_id_refs)
2496                 return NULL;
2497
2498         id_refs = tmp_id_refs;
2499
2500         for (i = 0; i < refs_size; i++) {
2501                 struct ref **tmp;
2502
2503                 if (strcmp(id, refs[i].id))
2504                         continue;
2505
2506                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2507                 if (!tmp) {
2508                         if (ref_list)
2509                                 free(ref_list);
2510                         return NULL;
2511                 }
2512
2513                 ref_list = tmp;
2514                 if (ref_list_size > 0)
2515                         ref_list[ref_list_size - 1]->next = 1;
2516                 ref_list[ref_list_size] = &refs[i];
2517
2518                 /* XXX: The properties of the commit chains ensures that we can
2519                  * safely modify the shared ref. The repo references will
2520                  * always be similar for the same id. */
2521                 ref_list[ref_list_size]->next = 0;
2522                 ref_list_size++;
2523         }
2524
2525         if (ref_list)
2526                 id_refs[id_refs_size++] = ref_list;
2527
2528         return ref_list;
2529 }
2530
2531 static int
2532 read_ref(char *id, int idlen, char *name, int namelen)
2533 {
2534         struct ref *ref;
2535         bool tag = FALSE;
2536         bool tag_commit = FALSE;
2537
2538         /* Commits referenced by tags has "^{}" appended. */
2539         if (name[namelen - 1] == '}') {
2540                 while (namelen > 0 && name[namelen] != '^')
2541                         namelen--;
2542                 if (namelen > 0)
2543                         tag_commit = TRUE;
2544                 name[namelen] = 0;
2545         }
2546
2547         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2548                 if (!tag_commit)
2549                         return OK;
2550                 name += STRING_SIZE("refs/tags/");
2551                 tag = TRUE;
2552
2553         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2554                 name += STRING_SIZE("refs/heads/");
2555
2556         } else if (!strcmp(name, "HEAD")) {
2557                 return OK;
2558         }
2559
2560         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2561         if (!refs)
2562                 return ERR;
2563
2564         ref = &refs[refs_size++];
2565         ref->name = strdup(name);
2566         if (!ref->name)
2567                 return ERR;
2568
2569         ref->tag = tag;
2570         string_copy(ref->id, id);
2571
2572         return OK;
2573 }
2574
2575 static int
2576 load_refs(void)
2577 {
2578         const char *cmd_env = getenv("TIG_LS_REMOTE");
2579         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2580
2581         return read_properties(popen(cmd, "r"), "\t", read_ref);
2582 }
2583
2584 static int
2585 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2586 {
2587         if (!strcmp(name, "i18n.commitencoding")) {
2588                 string_copy(opt_encoding, value);
2589         }
2590
2591         return OK;
2592 }
2593
2594 static int
2595 load_repo_config(void)
2596 {
2597         return read_properties(popen("git repo-config --list", "r"),
2598                                "=", read_repo_config_option);
2599 }
2600
2601 static int
2602 read_properties(FILE *pipe, const char *separators,
2603                 int (*read_property)(char *, int, char *, int))
2604 {
2605         char buffer[BUFSIZ];
2606         char *name;
2607         int state = OK;
2608
2609         if (!pipe)
2610                 return ERR;
2611
2612         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2613                 char *value;
2614                 size_t namelen;
2615                 size_t valuelen;
2616
2617                 name = chomp_string(name);
2618                 namelen = strcspn(name, separators);
2619
2620                 if (name[namelen]) {
2621                         name[namelen] = 0;
2622                         value = chomp_string(name + namelen + 1);
2623                         valuelen = strlen(value);
2624
2625                 } else {
2626                         value = "";
2627                         valuelen = 0;
2628                 }
2629
2630                 state = read_property(name, namelen, value, valuelen);
2631         }
2632
2633         if (state != ERR && ferror(pipe))
2634                 state = ERR;
2635
2636         pclose(pipe);
2637
2638         return state;
2639 }
2640
2641
2642 /*
2643  * Main
2644  */
2645
2646 #if __GNUC__ >= 3
2647 #define __NORETURN __attribute__((__noreturn__))
2648 #else
2649 #define __NORETURN
2650 #endif
2651
2652 static void __NORETURN
2653 quit(int sig)
2654 {
2655         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2656         if (cursed)
2657                 endwin();
2658         exit(0);
2659 }
2660
2661 static void __NORETURN
2662 die(const char *err, ...)
2663 {
2664         va_list args;
2665
2666         endwin();
2667
2668         va_start(args, err);
2669         fputs("tig: ", stderr);
2670         vfprintf(stderr, err, args);
2671         fputs("\n", stderr);
2672         va_end(args);
2673
2674         exit(1);
2675 }
2676
2677 int
2678 main(int argc, char *argv[])
2679 {
2680         struct view *view;
2681         enum request request;
2682         size_t i;
2683
2684         signal(SIGINT, quit);
2685
2686         if (load_options() == ERR)
2687                 die("Failed to load user config.");
2688
2689         /* Load the repo config file so options can be overwritten from
2690          * the command line.  */
2691         if (load_repo_config() == ERR)
2692                 die("Failed to load repo config.");
2693
2694         if (!parse_options(argc, argv))
2695                 return 0;
2696
2697         if (load_refs() == ERR)
2698                 die("Failed to load refs.");
2699
2700         /* Require a git repository unless when running in pager mode. */
2701         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2702                 die("Not a git repository");
2703
2704         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2705                 view->cmd_env = getenv(view->cmd_env);
2706
2707         request = opt_request;
2708
2709         init_display();
2710
2711         while (view_driver(display[current_view], request)) {
2712                 int key;
2713                 int i;
2714
2715                 foreach_view (view, i)
2716                         update_view(view);
2717
2718                 /* Refresh, accept single keystroke of input */
2719                 key = wgetch(status_win);
2720                 request = get_request(key);
2721
2722                 /* Some low-level request handling. This keeps access to
2723                  * status_win restricted. */
2724                 switch (request) {
2725                 case REQ_PROMPT:
2726                         report(":");
2727                         /* Temporarily switch to line-oriented and echoed
2728                          * input. */
2729                         nocbreak();
2730                         echo();
2731
2732                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2733                                 memcpy(opt_cmd, "git ", 4);
2734                                 opt_request = REQ_VIEW_PAGER;
2735                         } else {
2736                                 report("Prompt interrupted by loading view, "
2737                                        "press 'z' to stop loading views");
2738                                 request = REQ_SCREEN_UPDATE;
2739                         }
2740
2741                         noecho();
2742                         cbreak();
2743                         break;
2744
2745                 case REQ_SCREEN_RESIZE:
2746                 {
2747                         int height, width;
2748
2749                         getmaxyx(stdscr, height, width);
2750
2751                         /* Resize the status view and let the view driver take
2752                          * care of resizing the displayed views. */
2753                         wresize(status_win, 1, width);
2754                         mvwin(status_win, height - 1, 0);
2755                         wrefresh(status_win);
2756                         break;
2757                 }
2758                 default:
2759                         break;
2760                 }
2761         }
2762
2763         quit(0);
2764
2765         return 0;
2766 }
2767
2768 /**
2769  * include::BUGS[]
2770  *
2771  * COPYRIGHT
2772  * ---------
2773  * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2774  *
2775  * This program is free software; you can redistribute it and/or modify
2776  * it under the terms of the GNU General Public License as published by
2777  * the Free Software Foundation; either version 2 of the License, or
2778  * (at your option) any later version.
2779  *
2780  * SEE ALSO
2781  * --------
2782  * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2783  * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2784  *
2785  * Other git repository browsers:
2786  *
2787  *  - gitk(1)
2788  *  - qgit(1)
2789  *  - gitview(1)
2790  *
2791  * Sites:
2792  *
2793  * include::SITES[]
2794  **/