Code

More color and attribute maps closer to the users
[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  */
14 #ifndef VERSION
15 #define VERSION "tig-0.3"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <curses.h>
35 static void die(const char *err, ...);
36 static void report(const char *msg, ...);
37 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
38 static void set_nonblocking_input(bool loading);
39 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
40 static void load_help_page(void);
42 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
43 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
45 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
46 #define STRING_SIZE(x)  (sizeof(x) - 1)
48 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
49 #define SIZEOF_CMD      1024    /* Size of command buffer. */
51 /* This color name can be used to refer to the default term colors. */
52 #define COLOR_DEFAULT   (-1)
54 /* The format and size of the date column in the main view. */
55 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
56 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
58 #define AUTHOR_COLS     20
60 /* The default interval between line numbers. */
61 #define NUMBER_INTERVAL 1
63 #define TABSIZE         8
65 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
67 #define TIG_LS_REMOTE \
68         "git ls-remote . 2>/dev/null"
70 #define TIG_DIFF_CMD \
71         "git show --patch-with-stat --find-copies-harder -B -C %s"
73 #define TIG_LOG_CMD     \
74         "git log --cc --stat -n100 %s"
76 #define TIG_MAIN_CMD \
77         "git log --topo-order --stat --pretty=raw %s"
79 /* XXX: Needs to be defined to the empty string. */
80 #define TIG_HELP_CMD    ""
81 #define TIG_PAGER_CMD   ""
83 /* Some ascii-shorthands fitted into the ncurses namespace. */
84 #define KEY_TAB         '\t'
85 #define KEY_RETURN      '\r'
86 #define KEY_ESC         27
89 struct ref {
90         char *name;             /* Ref name; tag or head names are shortened. */
91         char id[41];            /* Commit SHA1 ID */
92         unsigned int tag:1;     /* Is it a tag? */
93         unsigned int next:1;    /* For ref lists: are there more refs? */
94 };
96 static struct ref **get_refs(char *id);
98 struct int_map {
99         const char *name;
100         int namelen;
101         int value;
102 };
104 static int
105 set_from_int_map(struct int_map *map, size_t map_size,
106                  int *value, const char *name, int namelen)
109         int i;
111         for (i = 0; i < map_size; i++)
112                 if (namelen == map[i].namelen &&
113                     !strncasecmp(name, map[i].name, namelen)) {
114                         *value = map[i].value;
115                         return OK;
116                 }
118         return ERR;
122 /*
123  * String helpers
124  */
126 static inline void
127 string_ncopy(char *dst, const char *src, int dstlen)
129         strncpy(dst, src, dstlen - 1);
130         dst[dstlen - 1] = 0;
134 /* Shorthand for safely copying into a fixed buffer. */
135 #define string_copy(dst, src) \
136         string_ncopy(dst, src, sizeof(dst))
138 static char *
139 chomp_string(char *name)
141         int namelen;
143         while (isspace(*name))
144                 name++;
146         namelen = strlen(name) - 1;
147         while (namelen > 0 && isspace(name[namelen]))
148                 name[namelen--] = 0;
150         return name;
153 static bool
154 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
156         va_list args;
157         int pos = bufpos ? *bufpos : 0;
159         va_start(args, fmt);
160         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
161         va_end(args);
163         if (bufpos)
164                 *bufpos = pos;
166         return pos >= bufsize ? FALSE : TRUE;
169 #define string_format(buf, fmt, args...) \
170         string_nformat(buf, sizeof(buf), NULL, fmt, args)
172 #define string_format_from(buf, from, fmt, args...) \
173         string_nformat(buf, sizeof(buf), from, fmt, args)
175 /* Shell quoting
176  *
177  * NOTE: The following is a slightly modified copy of the git project's shell
178  * quoting routines found in the quote.c file.
179  *
180  * Help to copy the thing properly quoted for the shell safety.  any single
181  * quote is replaced with '\'', any exclamation point is replaced with '\!',
182  * and the whole thing is enclosed in a
183  *
184  * E.g.
185  *  original     sq_quote     result
186  *  name     ==> name      ==> 'name'
187  *  a b      ==> a b       ==> 'a b'
188  *  a'b      ==> a'\''b    ==> 'a'\''b'
189  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
190  */
192 static size_t
193 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
195         char c;
197 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
199         BUFPUT('\'');
200         while ((c = *src++)) {
201                 if (c == '\'' || c == '!') {
202                         BUFPUT('\'');
203                         BUFPUT('\\');
204                         BUFPUT(c);
205                         BUFPUT('\'');
206                 } else {
207                         BUFPUT(c);
208                 }
209         }
210         BUFPUT('\'');
212         return bufsize;
216 /*
217  * User requests
218  */
220 #define REQ_INFO \
221         /* XXX: Keep the view request first and in sync with views[]. */ \
222         REQ_GROUP("View switching") \
223         REQ_(VIEW_MAIN,         "Show main view"), \
224         REQ_(VIEW_DIFF,         "Show diff view"), \
225         REQ_(VIEW_LOG,          "Show log view"), \
226         REQ_(VIEW_HELP,         "Show help page"), \
227         REQ_(VIEW_PAGER,        "Show pager view"), \
228         \
229         REQ_GROUP("View manipulation") \
230         REQ_(ENTER,             "Enter current line and scroll"), \
231         REQ_(NEXT,              "Move to next"), \
232         REQ_(PREVIOUS,          "Move to previous"), \
233         REQ_(VIEW_NEXT,         "Move focus to next view"), \
234         REQ_(VIEW_CLOSE,        "Close the current view"), \
235         REQ_(QUIT,              "Close all views and quit"), \
236         \
237         REQ_GROUP("Cursor navigation") \
238         REQ_(MOVE_UP,           "Move cursor one line up"), \
239         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
240         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
241         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
242         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
243         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
244         \
245         REQ_GROUP("Scrolling") \
246         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
247         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
248         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
249         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
250         \
251         REQ_GROUP("Misc") \
252         REQ_(PROMPT,            "Bring up the prompt"), \
253         REQ_(SCREEN_UPDATE,     "Update the screen"), \
254         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
255         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
256         REQ_(SHOW_VERSION,      "Show version information"), \
257         REQ_(STOP_LOADING,      "Stop all loading views"), \
258         REQ_(TOGGLE_LINENO,     "Toggle line numbers"),
261 /* User action requests. */
262 enum request {
263 #define REQ_GROUP(help)
264 #define REQ_(req, help) REQ_##req
266         /* Offset all requests to avoid conflicts with ncurses getch values. */
267         REQ_OFFSET = KEY_MAX + 1,
268         REQ_INFO
270 #undef  REQ_GROUP
271 #undef  REQ_
272 };
274 struct request_info {
275         enum request request;
276         char *help;
277 };
279 static struct request_info req_info[] = {
280 #define REQ_GROUP(help) { 0, (help) },
281 #define REQ_(req, help) { REQ_##req, (help) }
282         REQ_INFO
283 #undef  REQ_GROUP
284 #undef  REQ_
285 };
287 /*
288  * Options
289  */
291 static const char usage[] =
292 VERSION " (" __DATE__ ")\n"
293 "\n"
294 "Usage: tig [options]\n"
295 "   or: tig [options] [--] [git log options]\n"
296 "   or: tig [options] log  [git log options]\n"
297 "   or: tig [options] diff [git diff options]\n"
298 "   or: tig [options] show [git show options]\n"
299 "   or: tig [options] <    [git command output]\n"
300 "\n"
301 "Options:\n"
302 "  -l                          Start up in log view\n"
303 "  -d                          Start up in diff view\n"
304 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
305 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
306 "  --                          Mark end of tig options\n"
307 "  -v, --version               Show version and exit\n"
308 "  -h, --help                  Show help message and exit\n";
310 /* Option and state variables. */
311 static bool opt_line_number     = FALSE;
312 static int opt_num_interval     = NUMBER_INTERVAL;
313 static int opt_tab_size         = TABSIZE;
314 static enum request opt_request = REQ_VIEW_MAIN;
315 static char opt_cmd[SIZEOF_CMD] = "";
316 static char opt_encoding[20]    = "";
317 static bool opt_utf8            = TRUE;
318 static FILE *opt_pipe           = NULL;
320 enum option_type {
321         OPT_NONE,
322         OPT_INT,
323 };
325 static bool
326 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
328         va_list args;
329         char *value = "";
330         int *number;
332         if (opt[0] != '-')
333                 return FALSE;
335         if (opt[1] == '-') {
336                 int namelen = strlen(name);
338                 opt += 2;
340                 if (strncmp(opt, name, namelen))
341                         return FALSE;
343                 if (opt[namelen] == '=')
344                         value = opt + namelen + 1;
346         } else {
347                 if (!short_name || opt[1] != short_name)
348                         return FALSE;
349                 value = opt + 2;
350         }
352         va_start(args, type);
353         if (type == OPT_INT) {
354                 number = va_arg(args, int *);
355                 if (isdigit(*value))
356                         *number = atoi(value);
357         }
358         va_end(args);
360         return TRUE;
363 /* Returns the index of log or diff command or -1 to exit. */
364 static bool
365 parse_options(int argc, char *argv[])
367         int i;
369         for (i = 1; i < argc; i++) {
370                 char *opt = argv[i];
372                 if (!strcmp(opt, "-l")) {
373                         opt_request = REQ_VIEW_LOG;
374                         continue;
375                 }
377                 if (!strcmp(opt, "-d")) {
378                         opt_request = REQ_VIEW_DIFF;
379                         continue;
380                 }
382                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
383                         opt_line_number = TRUE;
384                         continue;
385                 }
387                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
388                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
389                         continue;
390                 }
392                 if (check_option(opt, 'v', "version", OPT_NONE)) {
393                         printf("tig version %s\n", VERSION);
394                         return FALSE;
395                 }
397                 if (check_option(opt, 'h', "help", OPT_NONE)) {
398                         printf(usage);
399                         return FALSE;
400                 }
402                 if (!strcmp(opt, "--")) {
403                         i++;
404                         break;
405                 }
407                 if (!strcmp(opt, "log") ||
408                     !strcmp(opt, "diff") ||
409                     !strcmp(opt, "show")) {
410                         opt_request = opt[0] == 'l'
411                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
412                         break;
413                 }
415                 if (opt[0] && opt[0] != '-')
416                         break;
418                 die("unknown command '%s'", opt);
419         }
421         if (!isatty(STDIN_FILENO)) {
422                 opt_request = REQ_VIEW_PAGER;
423                 opt_pipe = stdin;
425         } else if (i < argc) {
426                 size_t buf_size;
428                 if (opt_request == REQ_VIEW_MAIN)
429                         /* XXX: This is vulnerable to the user overriding
430                          * options required for the main view parser. */
431                         string_copy(opt_cmd, "git log --stat --pretty=raw");
432                 else
433                         string_copy(opt_cmd, "git");
434                 buf_size = strlen(opt_cmd);
436                 while (buf_size < sizeof(opt_cmd) && i < argc) {
437                         opt_cmd[buf_size++] = ' ';
438                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
439                 }
441                 if (buf_size >= sizeof(opt_cmd))
442                         die("command too long");
444                 opt_cmd[buf_size] = 0;
446         }
448         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
449                 opt_utf8 = FALSE;
451         return TRUE;
455 #define LINE_INFO \
456 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
457 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
458 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
459 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
460 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
461 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
462 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
463 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
464 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
465 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
466 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
467 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
468 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
469 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
470 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
471 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
472 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
473 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
474 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
475 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
476 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
477 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
478 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
479 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
480 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
481 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
482 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
483 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
484 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
485 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
486 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
487 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
488 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
489 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
490 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
491 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
492 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
493 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
496 /*
497  * Line-oriented content detection.
498  */
500 enum line_type {
501 #define LINE(type, line, fg, bg, attr) \
502         LINE_##type
503         LINE_INFO
504 #undef  LINE
505 };
507 struct line_info {
508         const char *name;       /* Option name. */
509         int namelen;            /* Size of option name. */
510         const char *line;       /* The start of line to match. */
511         int linelen;            /* Size of string to match. */
512         int fg, bg, attr;       /* Color and text attributes for the lines. */
513 };
515 static struct line_info line_info[] = {
516 #define LINE(type, line, fg, bg, attr) \
517         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
518         LINE_INFO
519 #undef  LINE
520 };
522 static enum line_type
523 get_line_type(char *line)
525         int linelen = strlen(line);
526         enum line_type type;
528         for (type = 0; type < ARRAY_SIZE(line_info); type++)
529                 /* Case insensitive search matches Signed-off-by lines better. */
530                 if (linelen >= line_info[type].linelen &&
531                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
532                         return type;
534         return LINE_DEFAULT;
537 static inline int
538 get_line_attr(enum line_type type)
540         assert(type < ARRAY_SIZE(line_info));
541         return COLOR_PAIR(type) | line_info[type].attr;
544 static struct line_info *
545 get_line_info(char *name, int namelen)
547         enum line_type type;
548         int i;
550         /* Diff-Header -> DIFF_HEADER */
551         for (i = 0; i < namelen; i++) {
552                 if (name[i] == '-')
553                         name[i] = '_';
554                 else if (name[i] == '.')
555                         name[i] = '_';
556         }
558         for (type = 0; type < ARRAY_SIZE(line_info); type++)
559                 if (namelen == line_info[type].namelen &&
560                     !strncasecmp(line_info[type].name, name, namelen))
561                         return &line_info[type];
563         return NULL;
566 static void
567 init_colors(void)
569         int default_bg = COLOR_BLACK;
570         int default_fg = COLOR_WHITE;
571         enum line_type type;
573         start_color();
575         if (use_default_colors() != ERR) {
576                 default_bg = -1;
577                 default_fg = -1;
578         }
580         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
581                 struct line_info *info = &line_info[type];
582                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
583                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
585                 init_pair(type, fg, bg);
586         }
589 struct line {
590         enum line_type type;
591         void *data;             /* User data */
592 };
595 /*
596  * User config file handling.
597  */
599 static struct int_map color_map[] = {
600 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
601         COLOR_MAP(DEFAULT),
602         COLOR_MAP(BLACK),
603         COLOR_MAP(BLUE),
604         COLOR_MAP(CYAN),
605         COLOR_MAP(GREEN),
606         COLOR_MAP(MAGENTA),
607         COLOR_MAP(RED),
608         COLOR_MAP(WHITE),
609         COLOR_MAP(YELLOW),
610 };
612 #define set_color(color, name, namelen) \
613         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
615 static struct int_map attr_map[] = {
616 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
617         ATTR_MAP(NORMAL),
618         ATTR_MAP(BLINK),
619         ATTR_MAP(BOLD),
620         ATTR_MAP(DIM),
621         ATTR_MAP(REVERSE),
622         ATTR_MAP(STANDOUT),
623         ATTR_MAP(UNDERLINE),
624 };
626 #define set_attribute(attr, name, namelen) \
627         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
629 static int   config_lineno;
630 static bool  config_errors;
631 static char *config_msg;
633 static int
634 set_option(char *opt, int optlen, char *value, int valuelen)
636         /* Reads: "color" object fgcolor bgcolor [attr] */
637         if (!strcmp(opt, "color")) {
638                 struct line_info *info;
640                 value = chomp_string(value);
641                 valuelen = strcspn(value, " \t");
642                 info = get_line_info(value, valuelen);
643                 if (!info) {
644                         config_msg = "Unknown color name";
645                         return ERR;
646                 }
648                 value = chomp_string(value + valuelen);
649                 valuelen = strcspn(value, " \t");
650                 if (set_color(&info->fg, value, valuelen) == ERR) {
651                         config_msg = "Unknown color";
652                         return ERR;
653                 }
655                 value = chomp_string(value + valuelen);
656                 valuelen = strcspn(value, " \t");
657                 if (set_color(&info->bg, value, valuelen) == ERR) {
658                         config_msg = "Unknown color";
659                         return ERR;
660                 }
662                 value = chomp_string(value + valuelen);
663                 if (*value &&
664                     set_attribute(&info->attr, value, strlen(value)) == ERR) {
665                         config_msg = "Unknown attribute";
666                         return ERR;
667                 }
669                 return OK;
670         }
672         return ERR;
675 static int
676 read_option(char *opt, int optlen, char *value, int valuelen)
678         config_lineno++;
679         config_msg = "Internal error";
681         optlen = strcspn(opt, "#;");
682         if (optlen == 0) {
683                 /* The whole line is a commend or empty. */
684                 return OK;
686         } else if (opt[optlen] != 0) {
687                 /* Part of the option name is a comment, so the value part
688                  * should be ignored. */
689                 valuelen = 0;
690                 opt[optlen] = value[valuelen] = 0;
691         } else {
692                 /* Else look for comment endings in the value. */
693                 valuelen = strcspn(value, "#;");
694                 value[valuelen] = 0;
695         }
697         if (set_option(opt, optlen, value, valuelen) == ERR) {
698                 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
699                         config_lineno, optlen, opt, config_msg);
700                 config_errors = TRUE;
701         }
703         /* Always keep going if errors are encountered. */
704         return OK;
707 static int
708 load_options(void)
710         char *home = getenv("HOME");
711         char buf[1024];
712         FILE *file;
714         config_lineno = 0;
715         config_errors = FALSE;
717         if (!home || !string_format(buf, "%s/.tigrc", home))
718                 return ERR;
720         /* It's ok that the file doesn't exist. */
721         file = fopen(buf, "r");
722         if (!file)
723                 return OK;
725         if (read_properties(file, " \t", read_option) == ERR ||
726             config_errors == TRUE)
727                 fprintf(stderr, "Errors while loading %s.\n", buf);
729         return OK;
733 /*
734  * The viewer
735  */
737 struct view;
738 struct view_ops;
740 /* The display array of active views and the index of the current view. */
741 static struct view *display[2];
742 static unsigned int current_view;
744 #define foreach_view(view, i) \
745         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
747 #define displayed_views()       (display[1] != NULL ? 2 : 1)
749 /* Current head and commit ID */
750 static char ref_commit[SIZEOF_REF]      = "HEAD";
751 static char ref_head[SIZEOF_REF]        = "HEAD";
753 struct view {
754         const char *name;       /* View name */
755         const char *cmd_fmt;    /* Default command line format */
756         const char *cmd_env;    /* Command line set via environment */
757         const char *id;         /* Points to either of ref_{head,commit} */
759         struct view_ops *ops;   /* View operations */
761         char cmd[SIZEOF_CMD];   /* Command buffer */
762         char ref[SIZEOF_REF];   /* Hovered commit reference */
763         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
765         int height, width;      /* The width and height of the main window */
766         WINDOW *win;            /* The main window */
767         WINDOW *title;          /* The title window living below the main window */
769         /* Navigation */
770         unsigned long offset;   /* Offset of the window top */
771         unsigned long lineno;   /* Current line number */
773         /* If non-NULL, points to the view that opened this view. If this view
774          * is closed tig will switch back to the parent view. */
775         struct view *parent;
777         /* Buffering */
778         unsigned long lines;    /* Total number of lines */
779         struct line *line;      /* Line index */
780         unsigned long line_size;/* Total number of allocated lines */
781         unsigned int digits;    /* Number of digits in the lines member. */
783         /* Loading */
784         FILE *pipe;
785         time_t start_time;
786 };
788 struct view_ops {
789         /* What type of content being displayed. Used in the title bar. */
790         const char *type;
791         /* Draw one line; @lineno must be < view->height. */
792         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
793         /* Read one line; updates view->line. */
794         bool (*read)(struct view *view, char *data);
795         /* Depending on view, change display based on current line. */
796         bool (*enter)(struct view *view, struct line *line);
797 };
799 static struct view_ops pager_ops;
800 static struct view_ops main_ops;
802 #define VIEW_STR(name, cmd, env, ref, ops) \
803         { name, cmd, #env, ref, ops }
805 #define VIEW_(id, name, ops, ref) \
806         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
809 static struct view views[] = {
810         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
811         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
812         VIEW_(LOG,   "log",   &pager_ops, ref_head),
813         VIEW_(HELP,  "help",  &pager_ops, "static"),
814         VIEW_(PAGER, "pager", &pager_ops, "static"),
815 };
817 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
820 static bool
821 draw_view_line(struct view *view, unsigned int lineno)
823         if (view->offset + lineno >= view->lines)
824                 return FALSE;
826         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
829 static void
830 redraw_view_from(struct view *view, int lineno)
832         assert(0 <= lineno && lineno < view->height);
834         for (; lineno < view->height; lineno++) {
835                 if (!draw_view_line(view, lineno))
836                         break;
837         }
839         redrawwin(view->win);
840         wrefresh(view->win);
843 static void
844 redraw_view(struct view *view)
846         wclear(view->win);
847         redraw_view_from(view, 0);
851 static void
852 update_view_title(struct view *view)
854         if (view == display[current_view])
855                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
856         else
857                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
859         werase(view->title);
860         wmove(view->title, 0, 0);
862         if (*view->ref)
863                 wprintw(view->title, "[%s] %s", view->name, view->ref);
864         else
865                 wprintw(view->title, "[%s]", view->name);
867         if (view->lines || view->pipe) {
868                 unsigned int lines = view->lines
869                                    ? (view->lineno + 1) * 100 / view->lines
870                                    : 0;
872                 wprintw(view->title, " - %s %d of %d (%d%%)",
873                         view->ops->type,
874                         view->lineno + 1,
875                         view->lines,
876                         lines);
877         }
879         if (view->pipe) {
880                 time_t secs = time(NULL) - view->start_time;
882                 /* Three git seconds are a long time ... */
883                 if (secs > 2)
884                         wprintw(view->title, " %lds", secs);
885         }
887         wmove(view->title, 0, view->width - 1);
888         wrefresh(view->title);
891 static void
892 resize_display(void)
894         int offset, i;
895         struct view *base = display[0];
896         struct view *view = display[1] ? display[1] : display[0];
898         /* Setup window dimensions */
900         getmaxyx(stdscr, base->height, base->width);
902         /* Make room for the status window. */
903         base->height -= 1;
905         if (view != base) {
906                 /* Horizontal split. */
907                 view->width   = base->width;
908                 view->height  = SCALE_SPLIT_VIEW(base->height);
909                 base->height -= view->height;
911                 /* Make room for the title bar. */
912                 view->height -= 1;
913         }
915         /* Make room for the title bar. */
916         base->height -= 1;
918         offset = 0;
920         foreach_view (view, i) {
921                 if (!view->win) {
922                         view->win = newwin(view->height, 0, offset, 0);
923                         if (!view->win)
924                                 die("Failed to create %s view", view->name);
926                         scrollok(view->win, TRUE);
928                         view->title = newwin(1, 0, offset + view->height, 0);
929                         if (!view->title)
930                                 die("Failed to create title window");
932                 } else {
933                         wresize(view->win, view->height, view->width);
934                         mvwin(view->win,   offset, 0);
935                         mvwin(view->title, offset + view->height, 0);
936                 }
938                 offset += view->height + 1;
939         }
942 static void
943 redraw_display(void)
945         struct view *view;
946         int i;
948         foreach_view (view, i) {
949                 redraw_view(view);
950                 update_view_title(view);
951         }
954 static void
955 update_display_cursor(void)
957         struct view *view = display[current_view];
959         /* Move the cursor to the right-most column of the cursor line.
960          *
961          * XXX: This could turn out to be a bit expensive, but it ensures that
962          * the cursor does not jump around. */
963         if (view->lines) {
964                 wmove(view->win, view->lineno - view->offset, view->width - 1);
965                 wrefresh(view->win);
966         }
969 /*
970  * Navigation
971  */
973 /* Scrolling backend */
974 static void
975 do_scroll_view(struct view *view, int lines, bool redraw)
977         /* The rendering expects the new offset. */
978         view->offset += lines;
980         assert(0 <= view->offset && view->offset < view->lines);
981         assert(lines);
983         /* Redraw the whole screen if scrolling is pointless. */
984         if (view->height < ABS(lines)) {
985                 redraw_view(view);
987         } else {
988                 int line = lines > 0 ? view->height - lines : 0;
989                 int end = line + ABS(lines);
991                 wscrl(view->win, lines);
993                 for (; line < end; line++) {
994                         if (!draw_view_line(view, line))
995                                 break;
996                 }
997         }
999         /* Move current line into the view. */
1000         if (view->lineno < view->offset) {
1001                 view->lineno = view->offset;
1002                 draw_view_line(view, 0);
1004         } else if (view->lineno >= view->offset + view->height) {
1005                 if (view->lineno == view->offset + view->height) {
1006                         /* Clear the hidden line so it doesn't show if the view
1007                          * is scrolled up. */
1008                         wmove(view->win, view->height, 0);
1009                         wclrtoeol(view->win);
1010                 }
1011                 view->lineno = view->offset + view->height - 1;
1012                 draw_view_line(view, view->lineno - view->offset);
1013         }
1015         assert(view->offset <= view->lineno && view->lineno < view->lines);
1017         if (!redraw)
1018                 return;
1020         redrawwin(view->win);
1021         wrefresh(view->win);
1022         report("");
1025 /* Scroll frontend */
1026 static void
1027 scroll_view(struct view *view, enum request request)
1029         int lines = 1;
1031         switch (request) {
1032         case REQ_SCROLL_PAGE_DOWN:
1033                 lines = view->height;
1034         case REQ_SCROLL_LINE_DOWN:
1035                 if (view->offset + lines > view->lines)
1036                         lines = view->lines - view->offset;
1038                 if (lines == 0 || view->offset + view->height >= view->lines) {
1039                         report("Cannot scroll beyond the last line");
1040                         return;
1041                 }
1042                 break;
1044         case REQ_SCROLL_PAGE_UP:
1045                 lines = view->height;
1046         case REQ_SCROLL_LINE_UP:
1047                 if (lines > view->offset)
1048                         lines = view->offset;
1050                 if (lines == 0) {
1051                         report("Cannot scroll beyond the first line");
1052                         return;
1053                 }
1055                 lines = -lines;
1056                 break;
1058         default:
1059                 die("request %d not handled in switch", request);
1060         }
1062         do_scroll_view(view, lines, TRUE);
1065 /* Cursor moving */
1066 static void
1067 move_view(struct view *view, enum request request, bool redraw)
1069         int steps;
1071         switch (request) {
1072         case REQ_MOVE_FIRST_LINE:
1073                 steps = -view->lineno;
1074                 break;
1076         case REQ_MOVE_LAST_LINE:
1077                 steps = view->lines - view->lineno - 1;
1078                 break;
1080         case REQ_MOVE_PAGE_UP:
1081                 steps = view->height > view->lineno
1082                       ? -view->lineno : -view->height;
1083                 break;
1085         case REQ_MOVE_PAGE_DOWN:
1086                 steps = view->lineno + view->height >= view->lines
1087                       ? view->lines - view->lineno - 1 : view->height;
1088                 break;
1090         case REQ_MOVE_UP:
1091                 steps = -1;
1092                 break;
1094         case REQ_MOVE_DOWN:
1095                 steps = 1;
1096                 break;
1098         default:
1099                 die("request %d not handled in switch", request);
1100         }
1102         if (steps <= 0 && view->lineno == 0) {
1103                 report("Cannot move beyond the first line");
1104                 return;
1106         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1107                 report("Cannot move beyond the last line");
1108                 return;
1109         }
1111         /* Move the current line */
1112         view->lineno += steps;
1113         assert(0 <= view->lineno && view->lineno < view->lines);
1115         /* Repaint the old "current" line if we be scrolling */
1116         if (ABS(steps) < view->height) {
1117                 int prev_lineno = view->lineno - steps - view->offset;
1119                 wmove(view->win, prev_lineno, 0);
1120                 wclrtoeol(view->win);
1121                 draw_view_line(view,  prev_lineno);
1122         }
1124         /* Check whether the view needs to be scrolled */
1125         if (view->lineno < view->offset ||
1126             view->lineno >= view->offset + view->height) {
1127                 if (steps < 0 && -steps > view->offset) {
1128                         steps = -view->offset;
1130                 } else if (steps > 0) {
1131                         if (view->lineno == view->lines - 1 &&
1132                             view->lines > view->height) {
1133                                 steps = view->lines - view->offset - 1;
1134                                 if (steps >= view->height)
1135                                         steps -= view->height - 1;
1136                         }
1137                 }
1139                 do_scroll_view(view, steps, redraw);
1140                 return;
1141         }
1143         /* Draw the current line */
1144         draw_view_line(view, view->lineno - view->offset);
1146         if (!redraw)
1147                 return;
1149         redrawwin(view->win);
1150         wrefresh(view->win);
1151         report("");
1155 /*
1156  * Incremental updating
1157  */
1159 static void
1160 end_update(struct view *view)
1162         if (!view->pipe)
1163                 return;
1164         set_nonblocking_input(FALSE);
1165         if (view->pipe == stdin)
1166                 fclose(view->pipe);
1167         else
1168                 pclose(view->pipe);
1169         view->pipe = NULL;
1172 static bool
1173 begin_update(struct view *view)
1175         const char *id = view->id;
1177         if (view->pipe)
1178                 end_update(view);
1180         if (opt_cmd[0]) {
1181                 string_copy(view->cmd, opt_cmd);
1182                 opt_cmd[0] = 0;
1183                 /* When running random commands, the view ref could have become
1184                  * invalid so clear it. */
1185                 view->ref[0] = 0;
1186         } else {
1187                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1189                 if (!string_format(view->cmd, format, id, id, id, id, id))
1190                         return FALSE;
1191         }
1193         /* Special case for the pager view. */
1194         if (opt_pipe) {
1195                 view->pipe = opt_pipe;
1196                 opt_pipe = NULL;
1197         } else {
1198                 view->pipe = popen(view->cmd, "r");
1199         }
1201         if (!view->pipe)
1202                 return FALSE;
1204         set_nonblocking_input(TRUE);
1206         view->offset = 0;
1207         view->lines  = 0;
1208         view->lineno = 0;
1209         string_copy(view->vid, id);
1211         if (view->line) {
1212                 int i;
1214                 for (i = 0; i < view->lines; i++)
1215                         if (view->line[i].data)
1216                                 free(view->line[i].data);
1218                 free(view->line);
1219                 view->line = NULL;
1220         }
1222         view->start_time = time(NULL);
1224         return TRUE;
1227 static struct line *
1228 realloc_lines(struct view *view, size_t line_size)
1230         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1232         if (!tmp)
1233                 return NULL;
1235         view->line = tmp;
1236         view->line_size = line_size;
1237         return view->line;
1240 static bool
1241 update_view(struct view *view)
1243         char buffer[BUFSIZ];
1244         char *line;
1245         /* The number of lines to read. If too low it will cause too much
1246          * redrawing (and possible flickering), if too high responsiveness
1247          * will suffer. */
1248         unsigned long lines = view->height;
1249         int redraw_from = -1;
1251         if (!view->pipe)
1252                 return TRUE;
1254         /* Only redraw if lines are visible. */
1255         if (view->offset + view->height >= view->lines)
1256                 redraw_from = view->lines - view->offset;
1258         if (!realloc_lines(view, view->lines + lines))
1259                 goto alloc_error;
1261         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1262                 int linelen = strlen(line);
1264                 if (linelen)
1265                         line[linelen - 1] = 0;
1267                 if (!view->ops->read(view, line))
1268                         goto alloc_error;
1270                 if (lines-- == 1)
1271                         break;
1272         }
1274         {
1275                 int digits;
1277                 lines = view->lines;
1278                 for (digits = 0; lines; digits++)
1279                         lines /= 10;
1281                 /* Keep the displayed view in sync with line number scaling. */
1282                 if (digits != view->digits) {
1283                         view->digits = digits;
1284                         redraw_from = 0;
1285                 }
1286         }
1288         if (redraw_from >= 0) {
1289                 /* If this is an incremental update, redraw the previous line
1290                  * since for commits some members could have changed when
1291                  * loading the main view. */
1292                 if (redraw_from > 0)
1293                         redraw_from--;
1295                 /* Incrementally draw avoids flickering. */
1296                 redraw_view_from(view, redraw_from);
1297         }
1299         /* Update the title _after_ the redraw so that if the redraw picks up a
1300          * commit reference in view->ref it'll be available here. */
1301         update_view_title(view);
1303         if (ferror(view->pipe)) {
1304                 report("Failed to read: %s", strerror(errno));
1305                 goto end;
1307         } else if (feof(view->pipe)) {
1308                 report("");
1309                 goto end;
1310         }
1312         return TRUE;
1314 alloc_error:
1315         report("Allocation failure");
1317 end:
1318         end_update(view);
1319         return FALSE;
1322 enum open_flags {
1323         OPEN_DEFAULT = 0,       /* Use default view switching. */
1324         OPEN_SPLIT = 1,         /* Split current view. */
1325         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1326         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1327 };
1329 static void
1330 open_view(struct view *prev, enum request request, enum open_flags flags)
1332         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1333         bool split = !!(flags & OPEN_SPLIT);
1334         bool reload = !!(flags & OPEN_RELOAD);
1335         struct view *view = VIEW(request);
1336         int nviews = displayed_views();
1337         struct view *base_view = display[0];
1339         if (view == prev && nviews == 1 && !reload) {
1340                 report("Already in %s view", view->name);
1341                 return;
1342         }
1344         if ((reload || strcmp(view->vid, view->id)) &&
1345             !begin_update(view)) {
1346                 report("Failed to load %s view", view->name);
1347                 return;
1348         }
1350         if (split) {
1351                 display[1] = view;
1352                 if (!backgrounded)
1353                         current_view = 1;
1354         } else {
1355                 /* Maximize the current view. */
1356                 memset(display, 0, sizeof(display));
1357                 current_view = 0;
1358                 display[current_view] = view;
1359         }
1361         /* Resize the view when switching between split- and full-screen,
1362          * or when switching between two different full-screen views. */
1363         if (nviews != displayed_views() ||
1364             (nviews == 1 && base_view != display[0]))
1365                 resize_display();
1367         if (split && prev->lineno - prev->offset >= prev->height) {
1368                 /* Take the title line into account. */
1369                 int lines = prev->lineno - prev->offset - prev->height + 1;
1371                 /* Scroll the view that was split if the current line is
1372                  * outside the new limited view. */
1373                 do_scroll_view(prev, lines, TRUE);
1374         }
1376         if (prev && view != prev) {
1377                 if (split && !backgrounded) {
1378                         /* "Blur" the previous view. */
1379                         update_view_title(prev);
1380                 }
1382                 view->parent = prev;
1383         }
1385         if (view == VIEW(REQ_VIEW_HELP))
1386                 load_help_page();
1388         if (view->pipe && view->lines == 0) {
1389                 /* Clear the old view and let the incremental updating refill
1390                  * the screen. */
1391                 wclear(view->win);
1392                 report("");
1393         } else {
1394                 redraw_view(view);
1395                 report("");
1396         }
1398         /* If the view is backgrounded the above calls to report()
1399          * won't redraw the view title. */
1400         if (backgrounded)
1401                 update_view_title(view);
1405 /*
1406  * User request switch noodle
1407  */
1409 static int
1410 view_driver(struct view *view, enum request request)
1412         int i;
1414         switch (request) {
1415         case REQ_MOVE_UP:
1416         case REQ_MOVE_DOWN:
1417         case REQ_MOVE_PAGE_UP:
1418         case REQ_MOVE_PAGE_DOWN:
1419         case REQ_MOVE_FIRST_LINE:
1420         case REQ_MOVE_LAST_LINE:
1421                 move_view(view, request, TRUE);
1422                 break;
1424         case REQ_SCROLL_LINE_DOWN:
1425         case REQ_SCROLL_LINE_UP:
1426         case REQ_SCROLL_PAGE_DOWN:
1427         case REQ_SCROLL_PAGE_UP:
1428                 scroll_view(view, request);
1429                 break;
1431         case REQ_VIEW_MAIN:
1432         case REQ_VIEW_DIFF:
1433         case REQ_VIEW_LOG:
1434         case REQ_VIEW_HELP:
1435         case REQ_VIEW_PAGER:
1436                 open_view(view, request, OPEN_DEFAULT);
1437                 break;
1439         case REQ_NEXT:
1440         case REQ_PREVIOUS:
1441                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1443                 if (view == VIEW(REQ_VIEW_DIFF) &&
1444                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1445                         bool redraw = display[1] == view;
1447                         view = view->parent;
1448                         move_view(view, request, redraw);
1449                         if (redraw)
1450                                 update_view_title(view);
1451                 } else {
1452                         move_view(view, request, TRUE);
1453                         break;
1454                 }
1455                 /* Fall-through */
1457         case REQ_ENTER:
1458                 if (!view->lines) {
1459                         report("Nothing to enter");
1460                         break;
1461                 }
1462                 return view->ops->enter(view, &view->line[view->lineno]);
1464         case REQ_VIEW_NEXT:
1465         {
1466                 int nviews = displayed_views();
1467                 int next_view = (current_view + 1) % nviews;
1469                 if (next_view == current_view) {
1470                         report("Only one view is displayed");
1471                         break;
1472                 }
1474                 current_view = next_view;
1475                 /* Blur out the title of the previous view. */
1476                 update_view_title(view);
1477                 report("");
1478                 break;
1479         }
1480         case REQ_TOGGLE_LINENO:
1481                 opt_line_number = !opt_line_number;
1482                 redraw_display();
1483                 break;
1485         case REQ_PROMPT:
1486                 /* Always reload^Wrerun commands from the prompt. */
1487                 open_view(view, opt_request, OPEN_RELOAD);
1488                 break;
1490         case REQ_STOP_LOADING:
1491                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1492                         view = &views[i];
1493                         if (view->pipe)
1494                                 report("Stopped loading the %s view", view->name),
1495                         end_update(view);
1496                 }
1497                 break;
1499         case REQ_SHOW_VERSION:
1500                 report("%s (built %s)", VERSION, __DATE__);
1501                 return TRUE;
1503         case REQ_SCREEN_RESIZE:
1504                 resize_display();
1505                 /* Fall-through */
1506         case REQ_SCREEN_REDRAW:
1507                 redraw_display();
1508                 break;
1510         case REQ_SCREEN_UPDATE:
1511                 doupdate();
1512                 return TRUE;
1514         case REQ_VIEW_CLOSE:
1515                 /* XXX: Mark closed views by letting view->parent point to the
1516                  * view itself. Parents to closed view should never be
1517                  * followed. */
1518                 if (view->parent &&
1519                     view->parent->parent != view->parent) {
1520                         memset(display, 0, sizeof(display));
1521                         current_view = 0;
1522                         display[current_view] = view->parent;
1523                         view->parent = view;
1524                         resize_display();
1525                         redraw_display();
1526                         break;
1527                 }
1528                 /* Fall-through */
1529         case REQ_QUIT:
1530                 return FALSE;
1532         default:
1533                 /* An unknown key will show most commonly used commands. */
1534                 report("Unknown key, press 'h' for help");
1535                 return TRUE;
1536         }
1538         return TRUE;
1542 /*
1543  * Pager backend
1544  */
1546 static bool
1547 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1549         char *text = line->data;
1550         enum line_type type = line->type;
1551         int textlen = strlen(text);
1552         int attr;
1554         wmove(view->win, lineno, 0);
1556         if (view->offset + lineno == view->lineno) {
1557                 if (type == LINE_COMMIT) {
1558                         string_copy(view->ref, text + 7);
1559                         string_copy(ref_commit, view->ref);
1560                 }
1562                 type = LINE_CURSOR;
1563                 wchgat(view->win, -1, 0, type, NULL);
1564         }
1566         attr = get_line_attr(type);
1567         wattrset(view->win, attr);
1569         if (opt_line_number || opt_tab_size < TABSIZE) {
1570                 static char spaces[] = "                    ";
1571                 int col_offset = 0, col = 0;
1573                 if (opt_line_number) {
1574                         unsigned long real_lineno = view->offset + lineno + 1;
1576                         if (real_lineno == 1 ||
1577                             (real_lineno % opt_num_interval) == 0) {
1578                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1580                         } else {
1581                                 waddnstr(view->win, spaces,
1582                                          MIN(view->digits, STRING_SIZE(spaces)));
1583                         }
1584                         waddstr(view->win, ": ");
1585                         col_offset = view->digits + 2;
1586                 }
1588                 while (text && col_offset + col < view->width) {
1589                         int cols_max = view->width - col_offset - col;
1590                         char *pos = text;
1591                         int cols;
1593                         if (*text == '\t') {
1594                                 text++;
1595                                 assert(sizeof(spaces) > TABSIZE);
1596                                 pos = spaces;
1597                                 cols = opt_tab_size - (col % opt_tab_size);
1599                         } else {
1600                                 text = strchr(text, '\t');
1601                                 cols = line ? text - pos : strlen(pos);
1602                         }
1604                         waddnstr(view->win, pos, MIN(cols, cols_max));
1605                         col += cols;
1606                 }
1608         } else {
1609                 int col = 0, pos = 0;
1611                 for (; pos < textlen && col < view->width; pos++, col++)
1612                         if (text[pos] == '\t')
1613                                 col += TABSIZE - (col % TABSIZE) - 1;
1615                 waddnstr(view->win, text, pos);
1616         }
1618         return TRUE;
1621 static void
1622 add_pager_refs(struct view *view, struct line *line)
1624         char buf[1024];
1625         char *data = line->data;
1626         struct ref **refs;
1627         int bufpos = 0, refpos = 0;
1628         const char *sep = "Refs: ";
1630         assert(line->type == LINE_COMMIT);
1632         refs = get_refs(data + STRING_SIZE("commit "));
1633         if (!refs)
1634                 return;
1636         do {
1637                 struct ref *ref = refs[refpos];
1638                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1640                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1641                         return;
1642                 sep = ", ";
1643         } while (refs[refpos++]->next);
1645         if (!realloc_lines(view, view->line_size + 1))
1646                 return;
1648         line = &view->line[view->lines];
1649         line->data = strdup(buf);
1650         if (!line->data)
1651                 return;
1653         line->type = LINE_PP_REFS;
1654         view->lines++;
1657 static bool
1658 pager_read(struct view *view, char *data)
1660         struct line *line = &view->line[view->lines];
1662         line->data = strdup(data);
1663         if (!line->data)
1664                 return FALSE;
1666         line->type = get_line_type(line->data);
1667         view->lines++;
1669         if (line->type == LINE_COMMIT &&
1670             (view == VIEW(REQ_VIEW_DIFF) ||
1671              view == VIEW(REQ_VIEW_LOG)))
1672                 add_pager_refs(view, line);
1674         return TRUE;
1677 static bool
1678 pager_enter(struct view *view, struct line *line)
1680         int split = 0;
1682         if (line->type == LINE_COMMIT &&
1683            (view == VIEW(REQ_VIEW_LOG) ||
1684             view == VIEW(REQ_VIEW_PAGER))) {
1685                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1686                 split = 1;
1687         }
1689         /* Always scroll the view even if it was split. That way
1690          * you can use Enter to scroll through the log view and
1691          * split open each commit diff. */
1692         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1694         /* FIXME: A minor workaround. Scrolling the view will call report("")
1695          * but if we are scrolling a non-current view this won't properly
1696          * update the view title. */
1697         if (split)
1698                 update_view_title(view);
1700         return TRUE;
1703 static struct view_ops pager_ops = {
1704         "line",
1705         pager_draw,
1706         pager_read,
1707         pager_enter,
1708 };
1711 /*
1712  * Main view backend
1713  */
1715 struct commit {
1716         char id[41];            /* SHA1 ID. */
1717         char title[75];         /* The first line of the commit message. */
1718         char author[75];        /* The author of the commit. */
1719         struct tm time;         /* Date from the author ident. */
1720         struct ref **refs;      /* Repository references; tags & branch heads. */
1721 };
1723 static bool
1724 main_draw(struct view *view, struct line *line, unsigned int lineno)
1726         char buf[DATE_COLS + 1];
1727         struct commit *commit = line->data;
1728         enum line_type type;
1729         int col = 0;
1730         size_t timelen;
1731         size_t authorlen;
1732         int trimmed = 1;
1734         if (!*commit->author)
1735                 return FALSE;
1737         wmove(view->win, lineno, col);
1739         if (view->offset + lineno == view->lineno) {
1740                 string_copy(view->ref, commit->id);
1741                 string_copy(ref_commit, view->ref);
1742                 type = LINE_CURSOR;
1743                 wattrset(view->win, get_line_attr(type));
1744                 wchgat(view->win, -1, 0, type, NULL);
1746         } else {
1747                 type = LINE_MAIN_COMMIT;
1748                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1749         }
1751         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1752         waddnstr(view->win, buf, timelen);
1753         waddstr(view->win, " ");
1755         col += DATE_COLS;
1756         wmove(view->win, lineno, col);
1757         if (type != LINE_CURSOR)
1758                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1760         if (opt_utf8) {
1761                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1762         } else {
1763                 authorlen = strlen(commit->author);
1764                 if (authorlen > AUTHOR_COLS - 2) {
1765                         authorlen = AUTHOR_COLS - 2;
1766                         trimmed = 1;
1767                 }
1768         }
1770         if (trimmed) {
1771                 waddnstr(view->win, commit->author, authorlen);
1772                 if (type != LINE_CURSOR)
1773                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1774                 waddch(view->win, '~');
1775         } else {
1776                 waddstr(view->win, commit->author);
1777         }
1779         col += AUTHOR_COLS;
1780         if (type != LINE_CURSOR)
1781                 wattrset(view->win, A_NORMAL);
1783         mvwaddch(view->win, lineno, col, ACS_LTEE);
1784         wmove(view->win, lineno, col + 2);
1785         col += 2;
1787         if (commit->refs) {
1788                 size_t i = 0;
1790                 do {
1791                         if (type == LINE_CURSOR)
1792                                 ;
1793                         else if (commit->refs[i]->tag)
1794                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1795                         else
1796                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1797                         waddstr(view->win, "[");
1798                         waddstr(view->win, commit->refs[i]->name);
1799                         waddstr(view->win, "]");
1800                         if (type != LINE_CURSOR)
1801                                 wattrset(view->win, A_NORMAL);
1802                         waddstr(view->win, " ");
1803                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1804                 } while (commit->refs[i++]->next);
1805         }
1807         if (type != LINE_CURSOR)
1808                 wattrset(view->win, get_line_attr(type));
1810         {
1811                 int titlelen = strlen(commit->title);
1813                 if (col + titlelen > view->width)
1814                         titlelen = view->width - col;
1816                 waddnstr(view->win, commit->title, titlelen);
1817         }
1819         return TRUE;
1822 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1823 static bool
1824 main_read(struct view *view, char *line)
1826         enum line_type type = get_line_type(line);
1827         struct commit *commit = view->lines
1828                               ? view->line[view->lines - 1].data : NULL;
1830         switch (type) {
1831         case LINE_COMMIT:
1832                 commit = calloc(1, sizeof(struct commit));
1833                 if (!commit)
1834                         return FALSE;
1836                 line += STRING_SIZE("commit ");
1838                 view->line[view->lines++].data = commit;
1839                 string_copy(commit->id, line);
1840                 commit->refs = get_refs(commit->id);
1841                 break;
1843         case LINE_AUTHOR:
1844         {
1845                 char *ident = line + STRING_SIZE("author ");
1846                 char *end = strchr(ident, '<');
1848                 if (!commit)
1849                         break;
1851                 if (end) {
1852                         for (; end > ident && isspace(end[-1]); end--) ;
1853                         *end = 0;
1854                 }
1856                 string_copy(commit->author, ident);
1858                 /* Parse epoch and timezone */
1859                 if (end) {
1860                         char *secs = strchr(end + 1, '>');
1861                         char *zone;
1862                         time_t time;
1864                         if (!secs || secs[1] != ' ')
1865                                 break;
1867                         secs += 2;
1868                         time = (time_t) atol(secs);
1869                         zone = strchr(secs, ' ');
1870                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1871                                 long tz;
1873                                 zone++;
1874                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1875                                 tz += ('0' - zone[2]) * 60 * 60;
1876                                 tz += ('0' - zone[3]) * 60;
1877                                 tz += ('0' - zone[4]) * 60;
1879                                 if (zone[0] == '-')
1880                                         tz = -tz;
1882                                 time -= tz;
1883                         }
1884                         gmtime_r(&time, &commit->time);
1885                 }
1886                 break;
1887         }
1888         default:
1889                 if (!commit)
1890                         break;
1892                 /* Fill in the commit title if it has not already been set. */
1893                 if (commit->title[0])
1894                         break;
1896                 /* Require titles to start with a non-space character at the
1897                  * offset used by git log. */
1898                 /* FIXME: More gracefull handling of titles; append "..." to
1899                  * shortened titles, etc. */
1900                 if (strncmp(line, "    ", 4) ||
1901                     isspace(line[4]))
1902                         break;
1904                 string_copy(commit->title, line + 4);
1905         }
1907         return TRUE;
1910 static bool
1911 main_enter(struct view *view, struct line *line)
1913         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1915         open_view(view, REQ_VIEW_DIFF, flags);
1916         return TRUE;
1919 static struct view_ops main_ops = {
1920         "commit",
1921         main_draw,
1922         main_read,
1923         main_enter,
1924 };
1927 /*
1928  * Keys
1929  */
1931 struct keymap {
1932         int alias;
1933         int request;
1934 };
1936 static struct keymap keymap[] = {
1937         /* View switching */
1938         { 'm',          REQ_VIEW_MAIN },
1939         { 'd',          REQ_VIEW_DIFF },
1940         { 'l',          REQ_VIEW_LOG },
1941         { 'p',          REQ_VIEW_PAGER },
1942         { 'h',          REQ_VIEW_HELP },
1943         { '?',          REQ_VIEW_HELP },
1945         /* View manipulation */
1946         { 'q',          REQ_VIEW_CLOSE },
1947         { KEY_TAB,      REQ_VIEW_NEXT },
1948         { KEY_RETURN,   REQ_ENTER },
1949         { KEY_UP,       REQ_PREVIOUS },
1950         { KEY_DOWN,     REQ_NEXT },
1952         /* Cursor navigation */
1953         { 'k',          REQ_MOVE_UP },
1954         { 'j',          REQ_MOVE_DOWN },
1955         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1956         { KEY_END,      REQ_MOVE_LAST_LINE },
1957         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1958         { ' ',          REQ_MOVE_PAGE_DOWN },
1959         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1960         { 'b',          REQ_MOVE_PAGE_UP },
1961         { '-',          REQ_MOVE_PAGE_UP },
1963         /* Scrolling */
1964         { KEY_IC,       REQ_SCROLL_LINE_UP },
1965         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1966         { 'w',          REQ_SCROLL_PAGE_UP },
1967         { 's',          REQ_SCROLL_PAGE_DOWN },
1969         /* Misc */
1970         { 'Q',          REQ_QUIT },
1971         { 'z',          REQ_STOP_LOADING },
1972         { 'v',          REQ_SHOW_VERSION },
1973         { 'r',          REQ_SCREEN_REDRAW },
1974         { 'n',          REQ_TOGGLE_LINENO },
1975         { ':',          REQ_PROMPT },
1977         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1978         { ERR,          REQ_SCREEN_UPDATE },
1980         /* Use the ncurses SIGWINCH handler. */
1981         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1982 };
1984 static enum request
1985 get_request(int key)
1987         int i;
1989         for (i = 0; i < ARRAY_SIZE(keymap); i++)
1990                 if (keymap[i].alias == key)
1991                         return keymap[i].request;
1993         return (enum request) key;
1996 struct key {
1997         char *name;
1998         int value;
1999 };
2001 static struct key key_table[] = {
2002         { "Enter",      KEY_RETURN },
2003         { "Space",      ' ' },
2004         { "Backspace",  KEY_BACKSPACE },
2005         { "Tab",        KEY_TAB },
2006         { "Escape",     KEY_ESC },
2007         { "Left",       KEY_LEFT },
2008         { "Right",      KEY_RIGHT },
2009         { "Up",         KEY_UP },
2010         { "Down",       KEY_DOWN },
2011         { "Insert",     KEY_IC },
2012         { "Delete",     KEY_DC },
2013         { "Home",       KEY_HOME },
2014         { "End",        KEY_END },
2015         { "PageUp",     KEY_PPAGE },
2016         { "PageDown",   KEY_NPAGE },
2017         { "F1",         KEY_F(1) },
2018         { "F2",         KEY_F(2) },
2019         { "F3",         KEY_F(3) },
2020         { "F4",         KEY_F(4) },
2021         { "F5",         KEY_F(5) },
2022         { "F6",         KEY_F(6) },
2023         { "F7",         KEY_F(7) },
2024         { "F8",         KEY_F(8) },
2025         { "F9",         KEY_F(9) },
2026         { "F10",        KEY_F(10) },
2027         { "F11",        KEY_F(11) },
2028         { "F12",        KEY_F(12) },
2029 };
2031 static char *
2032 get_key(enum request request)
2034         static char buf[BUFSIZ];
2035         static char key_char[] = "'X'";
2036         int pos = 0;
2037         char *sep = "    ";
2038         int i;
2040         buf[pos] = 0;
2042         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2043                 char *seq = NULL;
2044                 int key;
2046                 if (keymap[i].request != request)
2047                         continue;
2049                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2050                         if (key_table[key].value == keymap[i].alias)
2051                                 seq = key_table[key].name;
2053                 if (seq == NULL &&
2054                     keymap[i].alias < 127 &&
2055                     isprint(keymap[i].alias)) {
2056                         key_char[1] = (char) keymap[i].alias;
2057                         seq = key_char;
2058                 }
2060                 if (!seq)
2061                         seq = "'?'";
2063                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
2064                         return "Too many keybindings!";
2065                 sep = ", ";
2066         }
2068         return buf;
2071 static void load_help_page(void)
2073         char buf[BUFSIZ];
2074         struct view *view = VIEW(REQ_VIEW_HELP);
2075         int lines = ARRAY_SIZE(req_info) + 2;
2076         int i;
2078         if (view->lines > 0)
2079                 return;
2081         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2082                 if (!req_info[i].request)
2083                         lines++;
2085         view->line = calloc(lines, sizeof(*view->line));
2086         if (!view->line) {
2087                 report("Allocation failure");
2088                 return;
2089         }
2091         pager_read(view, "Quick reference for tig keybindings:");
2093         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2094                 char *key;
2096                 if (!req_info[i].request) {
2097                         pager_read(view, "");
2098                         pager_read(view, req_info[i].help);
2099                         continue;
2100                 }
2102                 key = get_key(req_info[i].request);
2103                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
2104                         continue;
2106                 pager_read(view, buf);
2107         }
2111 /*
2112  * Unicode / UTF-8 handling
2113  *
2114  * NOTE: Much of the following code for dealing with unicode is derived from
2115  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2116  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2117  */
2119 /* I've (over)annotated a lot of code snippets because I am not entirely
2120  * confident that the approach taken by this small UTF-8 interface is correct.
2121  * --jonas */
2123 static inline int
2124 unicode_width(unsigned long c)
2126         if (c >= 0x1100 &&
2127            (c <= 0x115f                         /* Hangul Jamo */
2128             || c == 0x2329
2129             || c == 0x232a
2130             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2131                                                 /* CJK ... Yi */
2132             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2133             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2134             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2135             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2136             || (c >= 0xffe0  && c <= 0xffe6)
2137             || (c >= 0x20000 && c <= 0x2fffd)
2138             || (c >= 0x30000 && c <= 0x3fffd)))
2139                 return 2;
2141         return 1;
2144 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2145  * Illegal bytes are set one. */
2146 static const unsigned char utf8_bytes[256] = {
2147         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,
2148         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,
2149         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,
2150         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,
2151         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,
2152         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,
2153         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,
2154         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,
2155 };
2157 /* Decode UTF-8 multi-byte representation into a unicode character. */
2158 static inline unsigned long
2159 utf8_to_unicode(const char *string, size_t length)
2161         unsigned long unicode;
2163         switch (length) {
2164         case 1:
2165                 unicode  =   string[0];
2166                 break;
2167         case 2:
2168                 unicode  =  (string[0] & 0x1f) << 6;
2169                 unicode +=  (string[1] & 0x3f);
2170                 break;
2171         case 3:
2172                 unicode  =  (string[0] & 0x0f) << 12;
2173                 unicode += ((string[1] & 0x3f) << 6);
2174                 unicode +=  (string[2] & 0x3f);
2175                 break;
2176         case 4:
2177                 unicode  =  (string[0] & 0x0f) << 18;
2178                 unicode += ((string[1] & 0x3f) << 12);
2179                 unicode += ((string[2] & 0x3f) << 6);
2180                 unicode +=  (string[3] & 0x3f);
2181                 break;
2182         case 5:
2183                 unicode  =  (string[0] & 0x0f) << 24;
2184                 unicode += ((string[1] & 0x3f) << 18);
2185                 unicode += ((string[2] & 0x3f) << 12);
2186                 unicode += ((string[3] & 0x3f) << 6);
2187                 unicode +=  (string[4] & 0x3f);
2188                 break;
2189         case 6:
2190                 unicode  =  (string[0] & 0x01) << 30;
2191                 unicode += ((string[1] & 0x3f) << 24);
2192                 unicode += ((string[2] & 0x3f) << 18);
2193                 unicode += ((string[3] & 0x3f) << 12);
2194                 unicode += ((string[4] & 0x3f) << 6);
2195                 unicode +=  (string[5] & 0x3f);
2196                 break;
2197         default:
2198                 die("Invalid unicode length");
2199         }
2201         /* Invalid characters could return the special 0xfffd value but NUL
2202          * should be just as good. */
2203         return unicode > 0xffff ? 0 : unicode;
2206 /* Calculates how much of string can be shown within the given maximum width
2207  * and sets trimmed parameter to non-zero value if all of string could not be
2208  * shown.
2209  *
2210  * Additionally, adds to coloffset how many many columns to move to align with
2211  * the expected position. Takes into account how multi-byte and double-width
2212  * characters will effect the cursor position.
2213  *
2214  * Returns the number of bytes to output from string to satisfy max_width. */
2215 static size_t
2216 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2218         const char *start = string;
2219         const char *end = strchr(string, '\0');
2220         size_t mbwidth = 0;
2221         size_t width = 0;
2223         *trimmed = 0;
2225         while (string < end) {
2226                 int c = *(unsigned char *) string;
2227                 unsigned char bytes = utf8_bytes[c];
2228                 size_t ucwidth;
2229                 unsigned long unicode;
2231                 if (string + bytes > end)
2232                         break;
2234                 /* Change representation to figure out whether
2235                  * it is a single- or double-width character. */
2237                 unicode = utf8_to_unicode(string, bytes);
2238                 /* FIXME: Graceful handling of invalid unicode character. */
2239                 if (!unicode)
2240                         break;
2242                 ucwidth = unicode_width(unicode);
2243                 width  += ucwidth;
2244                 if (width > max_width) {
2245                         *trimmed = 1;
2246                         break;
2247                 }
2249                 /* The column offset collects the differences between the
2250                  * number of bytes encoding a character and the number of
2251                  * columns will be used for rendering said character.
2252                  *
2253                  * So if some character A is encoded in 2 bytes, but will be
2254                  * represented on the screen using only 1 byte this will and up
2255                  * adding 1 to the multi-byte column offset.
2256                  *
2257                  * Assumes that no double-width character can be encoding in
2258                  * less than two bytes. */
2259                 if (bytes > ucwidth)
2260                         mbwidth += bytes - ucwidth;
2262                 string  += bytes;
2263         }
2265         *coloffset += mbwidth;
2267         return string - start;
2271 /*
2272  * Status management
2273  */
2275 /* Whether or not the curses interface has been initialized. */
2276 static bool cursed = FALSE;
2278 /* The status window is used for polling keystrokes. */
2279 static WINDOW *status_win;
2281 /* Update status and title window. */
2282 static void
2283 report(const char *msg, ...)
2285         static bool empty = TRUE;
2286         struct view *view = display[current_view];
2288         if (!empty || *msg) {
2289                 va_list args;
2291                 va_start(args, msg);
2293                 werase(status_win);
2294                 wmove(status_win, 0, 0);
2295                 if (*msg) {
2296                         vwprintw(status_win, msg, args);
2297                         empty = FALSE;
2298                 } else {
2299                         empty = TRUE;
2300                 }
2301                 wrefresh(status_win);
2303                 va_end(args);
2304         }
2306         update_view_title(view);
2307         update_display_cursor();
2310 /* Controls when nodelay should be in effect when polling user input. */
2311 static void
2312 set_nonblocking_input(bool loading)
2314         static unsigned int loading_views;
2316         if ((loading == FALSE && loading_views-- == 1) ||
2317             (loading == TRUE  && loading_views++ == 0))
2318                 nodelay(status_win, loading);
2321 static void
2322 init_display(void)
2324         int x, y;
2326         /* Initialize the curses library */
2327         if (isatty(STDIN_FILENO)) {
2328                 cursed = !!initscr();
2329         } else {
2330                 /* Leave stdin and stdout alone when acting as a pager. */
2331                 FILE *io = fopen("/dev/tty", "r+");
2333                 cursed = !!newterm(NULL, io, io);
2334         }
2336         if (!cursed)
2337                 die("Failed to initialize curses");
2339         nonl();         /* Tell curses not to do NL->CR/NL on output */
2340         cbreak();       /* Take input chars one at a time, no wait for \n */
2341         noecho();       /* Don't echo input */
2342         leaveok(stdscr, TRUE);
2344         if (has_colors())
2345                 init_colors();
2347         getmaxyx(stdscr, y, x);
2348         status_win = newwin(1, 0, y - 1, 0);
2349         if (!status_win)
2350                 die("Failed to create status window");
2352         /* Enable keyboard mapping */
2353         keypad(status_win, TRUE);
2354         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2358 /*
2359  * Repository references
2360  */
2362 static struct ref *refs;
2363 static size_t refs_size;
2365 /* Id <-> ref store */
2366 static struct ref ***id_refs;
2367 static size_t id_refs_size;
2369 static struct ref **
2370 get_refs(char *id)
2372         struct ref ***tmp_id_refs;
2373         struct ref **ref_list = NULL;
2374         size_t ref_list_size = 0;
2375         size_t i;
2377         for (i = 0; i < id_refs_size; i++)
2378                 if (!strcmp(id, id_refs[i][0]->id))
2379                         return id_refs[i];
2381         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2382         if (!tmp_id_refs)
2383                 return NULL;
2385         id_refs = tmp_id_refs;
2387         for (i = 0; i < refs_size; i++) {
2388                 struct ref **tmp;
2390                 if (strcmp(id, refs[i].id))
2391                         continue;
2393                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2394                 if (!tmp) {
2395                         if (ref_list)
2396                                 free(ref_list);
2397                         return NULL;
2398                 }
2400                 ref_list = tmp;
2401                 if (ref_list_size > 0)
2402                         ref_list[ref_list_size - 1]->next = 1;
2403                 ref_list[ref_list_size] = &refs[i];
2405                 /* XXX: The properties of the commit chains ensures that we can
2406                  * safely modify the shared ref. The repo references will
2407                  * always be similar for the same id. */
2408                 ref_list[ref_list_size]->next = 0;
2409                 ref_list_size++;
2410         }
2412         if (ref_list)
2413                 id_refs[id_refs_size++] = ref_list;
2415         return ref_list;
2418 static int
2419 read_ref(char *id, int idlen, char *name, int namelen)
2421         struct ref *ref;
2422         bool tag = FALSE;
2424         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2425                 /* Commits referenced by tags has "^{}" appended. */
2426                 if (name[namelen - 1] != '}')
2427                         return OK;
2429                 while (namelen > 0 && name[namelen] != '^')
2430                         namelen--;
2432                 tag = TRUE;
2433                 namelen -= STRING_SIZE("refs/tags/");
2434                 name    += STRING_SIZE("refs/tags/");
2436         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2437                 namelen -= STRING_SIZE("refs/heads/");
2438                 name    += STRING_SIZE("refs/heads/");
2440         } else if (!strcmp(name, "HEAD")) {
2441                 return OK;
2442         }
2444         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2445         if (!refs)
2446                 return ERR;
2448         ref = &refs[refs_size++];
2449         ref->name = malloc(namelen + 1);
2450         if (!ref->name)
2451                 return ERR;
2453         strncpy(ref->name, name, namelen);
2454         ref->name[namelen] = 0;
2455         ref->tag = tag;
2456         string_copy(ref->id, id);
2458         return OK;
2461 static int
2462 load_refs(void)
2464         const char *cmd_env = getenv("TIG_LS_REMOTE");
2465         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2467         return read_properties(popen(cmd, "r"), "\t", read_ref);
2470 static int
2471 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2473         if (!strcmp(name, "i18n.commitencoding"))
2474                 string_copy(opt_encoding, value);
2476         return OK;
2479 static int
2480 load_repo_config(void)
2482         return read_properties(popen("git repo-config --list", "r"),
2483                                "=", read_repo_config_option);
2486 static int
2487 read_properties(FILE *pipe, const char *separators,
2488                 int (*read_property)(char *, int, char *, int))
2490         char buffer[BUFSIZ];
2491         char *name;
2492         int state = OK;
2494         if (!pipe)
2495                 return ERR;
2497         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2498                 char *value;
2499                 size_t namelen;
2500                 size_t valuelen;
2502                 name = chomp_string(name);
2503                 namelen = strcspn(name, separators);
2505                 if (name[namelen]) {
2506                         name[namelen] = 0;
2507                         value = chomp_string(name + namelen + 1);
2508                         valuelen = strlen(value);
2510                 } else {
2511                         value = "";
2512                         valuelen = 0;
2513                 }
2515                 state = read_property(name, namelen, value, valuelen);
2516         }
2518         if (state != ERR && ferror(pipe))
2519                 state = ERR;
2521         pclose(pipe);
2523         return state;
2527 /*
2528  * Main
2529  */
2531 #if __GNUC__ >= 3
2532 #define __NORETURN __attribute__((__noreturn__))
2533 #else
2534 #define __NORETURN
2535 #endif
2537 static void __NORETURN
2538 quit(int sig)
2540         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2541         if (cursed)
2542                 endwin();
2543         exit(0);
2546 static void __NORETURN
2547 die(const char *err, ...)
2549         va_list args;
2551         endwin();
2553         va_start(args, err);
2554         fputs("tig: ", stderr);
2555         vfprintf(stderr, err, args);
2556         fputs("\n", stderr);
2557         va_end(args);
2559         exit(1);
2562 int
2563 main(int argc, char *argv[])
2565         struct view *view;
2566         enum request request;
2567         size_t i;
2569         signal(SIGINT, quit);
2571         if (load_options() == ERR)
2572                 die("Failed to load user config.");
2574         /* Load the repo config file so options can be overwritten from
2575          * the command line.  */
2576         if (load_repo_config() == ERR)
2577                 die("Failed to load repo config.");
2579         if (!parse_options(argc, argv))
2580                 return 0;
2582         if (load_refs() == ERR)
2583                 die("Failed to load refs.");
2585         /* Require a git repository unless when running in pager mode. */
2586         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2587                 die("Not a git repository");
2589         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2590                 view->cmd_env = getenv(view->cmd_env);
2592         request = opt_request;
2594         init_display();
2596         while (view_driver(display[current_view], request)) {
2597                 int key;
2598                 int i;
2600                 foreach_view (view, i)
2601                         update_view(view);
2603                 /* Refresh, accept single keystroke of input */
2604                 key = wgetch(status_win);
2605                 request = get_request(key);
2607                 /* Some low-level request handling. This keeps access to
2608                  * status_win restricted. */
2609                 switch (request) {
2610                 case REQ_PROMPT:
2611                         report(":");
2612                         /* Temporarily switch to line-oriented and echoed
2613                          * input. */
2614                         nocbreak();
2615                         echo();
2617                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2618                                 memcpy(opt_cmd, "git ", 4);
2619                                 opt_request = REQ_VIEW_PAGER;
2620                         } else {
2621                                 report("Prompt interrupted by loading view, "
2622                                        "press 'z' to stop loading views");
2623                                 request = REQ_SCREEN_UPDATE;
2624                         }
2626                         noecho();
2627                         cbreak();
2628                         break;
2630                 case REQ_SCREEN_RESIZE:
2631                 {
2632                         int height, width;
2634                         getmaxyx(stdscr, height, width);
2636                         /* Resize the status view and let the view driver take
2637                          * care of resizing the displayed views. */
2638                         wresize(status_win, 1, width);
2639                         mvwin(status_win, height - 1, 0);
2640                         wrefresh(status_win);
2641                         break;
2642                 }
2643                 default:
2644                         break;
2645                 }
2646         }
2648         quit(0);
2650         return 0;