Code

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