Code

Refactor view->line reallocation
[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 long line_size;/* Total number of allocated lines */
866         unsigned int digits;    /* Number of digits in the lines member. */
868         /* Loading */
869         FILE *pipe;
870         time_t start_time;
871 };
873 struct view_ops {
874         /* What type of content being displayed. Used in the title bar. */
875         const char *type;
876         /* Draw one line; @lineno must be < view->height. */
877         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
878         /* Read one line; updates view->line. */
879         bool (*read)(struct view *view, struct line *prev, char *data);
880         /* Depending on view, change display based on current line. */
881         bool (*enter)(struct view *view, struct line *line);
882 };
884 static struct view_ops pager_ops;
885 static struct view_ops main_ops;
887 #define VIEW_STR(name, cmd, env, ref, ops) \
888         { name, cmd, #env, ref, ops }
890 #define VIEW_(id, name, ops, ref) \
891         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
894 static struct view views[] = {
895         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
896         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
897         VIEW_(LOG,   "log",   &pager_ops, ref_head),
898         VIEW_(HELP,  "help",  &pager_ops, "static"),
899         VIEW_(PAGER, "pager", &pager_ops, "static"),
900 };
902 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
905 static bool
906 draw_view_line(struct view *view, unsigned int lineno)
908         if (view->offset + lineno >= view->lines)
909                 return FALSE;
911         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
914 static void
915 redraw_view_from(struct view *view, int lineno)
917         assert(0 <= lineno && lineno < view->height);
919         for (; lineno < view->height; lineno++) {
920                 if (!draw_view_line(view, lineno))
921                         break;
922         }
924         redrawwin(view->win);
925         wrefresh(view->win);
928 static void
929 redraw_view(struct view *view)
931         wclear(view->win);
932         redraw_view_from(view, 0);
936 static void
937 update_view_title(struct view *view)
939         if (view == display[current_view])
940                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
941         else
942                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
944         werase(view->title);
945         wmove(view->title, 0, 0);
947         if (*view->ref)
948                 wprintw(view->title, "[%s] %s", view->name, view->ref);
949         else
950                 wprintw(view->title, "[%s]", view->name);
952         if (view->lines || view->pipe) {
953                 unsigned int lines = view->lines
954                                    ? (view->lineno + 1) * 100 / view->lines
955                                    : 0;
957                 wprintw(view->title, " - %s %d of %d (%d%%)",
958                         view->ops->type,
959                         view->lineno + 1,
960                         view->lines,
961                         lines);
962         }
964         if (view->pipe) {
965                 time_t secs = time(NULL) - view->start_time;
967                 /* Three git seconds are a long time ... */
968                 if (secs > 2)
969                         wprintw(view->title, " %lds", secs);
970         }
972         wmove(view->title, 0, view->width - 1);
973         wrefresh(view->title);
976 static void
977 resize_display(void)
979         int offset, i;
980         struct view *base = display[0];
981         struct view *view = display[1] ? display[1] : display[0];
983         /* Setup window dimensions */
985         getmaxyx(stdscr, base->height, base->width);
987         /* Make room for the status window. */
988         base->height -= 1;
990         if (view != base) {
991                 /* Horizontal split. */
992                 view->width   = base->width;
993                 view->height  = SCALE_SPLIT_VIEW(base->height);
994                 base->height -= view->height;
996                 /* Make room for the title bar. */
997                 view->height -= 1;
998         }
1000         /* Make room for the title bar. */
1001         base->height -= 1;
1003         offset = 0;
1005         foreach_view (view, i) {
1006                 if (!view->win) {
1007                         view->win = newwin(view->height, 0, offset, 0);
1008                         if (!view->win)
1009                                 die("Failed to create %s view", view->name);
1011                         scrollok(view->win, TRUE);
1013                         view->title = newwin(1, 0, offset + view->height, 0);
1014                         if (!view->title)
1015                                 die("Failed to create title window");
1017                 } else {
1018                         wresize(view->win, view->height, view->width);
1019                         mvwin(view->win,   offset, 0);
1020                         mvwin(view->title, offset + view->height, 0);
1021                 }
1023                 offset += view->height + 1;
1024         }
1027 static void
1028 redraw_display(void)
1030         struct view *view;
1031         int i;
1033         foreach_view (view, i) {
1034                 redraw_view(view);
1035                 update_view_title(view);
1036         }
1039 static void
1040 update_display_cursor(void)
1042         struct view *view = display[current_view];
1044         /* Move the cursor to the right-most column of the cursor line.
1045          *
1046          * XXX: This could turn out to be a bit expensive, but it ensures that
1047          * the cursor does not jump around. */
1048         if (view->lines) {
1049                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1050                 wrefresh(view->win);
1051         }
1054 /*
1055  * Navigation
1056  */
1058 /* Scrolling backend */
1059 static void
1060 do_scroll_view(struct view *view, int lines, bool redraw)
1062         /* The rendering expects the new offset. */
1063         view->offset += lines;
1065         assert(0 <= view->offset && view->offset < view->lines);
1066         assert(lines);
1068         /* Redraw the whole screen if scrolling is pointless. */
1069         if (view->height < ABS(lines)) {
1070                 redraw_view(view);
1072         } else {
1073                 int line = lines > 0 ? view->height - lines : 0;
1074                 int end = line + ABS(lines);
1076                 wscrl(view->win, lines);
1078                 for (; line < end; line++) {
1079                         if (!draw_view_line(view, line))
1080                                 break;
1081                 }
1082         }
1084         /* Move current line into the view. */
1085         if (view->lineno < view->offset) {
1086                 view->lineno = view->offset;
1087                 draw_view_line(view, 0);
1089         } else if (view->lineno >= view->offset + view->height) {
1090                 if (view->lineno == view->offset + view->height) {
1091                         /* Clear the hidden line so it doesn't show if the view
1092                          * is scrolled up. */
1093                         wmove(view->win, view->height, 0);
1094                         wclrtoeol(view->win);
1095                 }
1096                 view->lineno = view->offset + view->height - 1;
1097                 draw_view_line(view, view->lineno - view->offset);
1098         }
1100         assert(view->offset <= view->lineno && view->lineno < view->lines);
1102         if (!redraw)
1103                 return;
1105         redrawwin(view->win);
1106         wrefresh(view->win);
1107         report("");
1110 /* Scroll frontend */
1111 static void
1112 scroll_view(struct view *view, enum request request)
1114         int lines = 1;
1116         switch (request) {
1117         case REQ_SCROLL_PAGE_DOWN:
1118                 lines = view->height;
1119         case REQ_SCROLL_LINE_DOWN:
1120                 if (view->offset + lines > view->lines)
1121                         lines = view->lines - view->offset;
1123                 if (lines == 0 || view->offset + view->height >= view->lines) {
1124                         report("Cannot scroll beyond the last line");
1125                         return;
1126                 }
1127                 break;
1129         case REQ_SCROLL_PAGE_UP:
1130                 lines = view->height;
1131         case REQ_SCROLL_LINE_UP:
1132                 if (lines > view->offset)
1133                         lines = view->offset;
1135                 if (lines == 0) {
1136                         report("Cannot scroll beyond the first line");
1137                         return;
1138                 }
1140                 lines = -lines;
1141                 break;
1143         default:
1144                 die("request %d not handled in switch", request);
1145         }
1147         do_scroll_view(view, lines, TRUE);
1150 /* Cursor moving */
1151 static void
1152 move_view(struct view *view, enum request request, bool redraw)
1154         int steps;
1156         switch (request) {
1157         case REQ_MOVE_FIRST_LINE:
1158                 steps = -view->lineno;
1159                 break;
1161         case REQ_MOVE_LAST_LINE:
1162                 steps = view->lines - view->lineno - 1;
1163                 break;
1165         case REQ_MOVE_PAGE_UP:
1166                 steps = view->height > view->lineno
1167                       ? -view->lineno : -view->height;
1168                 break;
1170         case REQ_MOVE_PAGE_DOWN:
1171                 steps = view->lineno + view->height >= view->lines
1172                       ? view->lines - view->lineno - 1 : view->height;
1173                 break;
1175         case REQ_MOVE_UP:
1176                 steps = -1;
1177                 break;
1179         case REQ_MOVE_DOWN:
1180                 steps = 1;
1181                 break;
1183         default:
1184                 die("request %d not handled in switch", request);
1185         }
1187         if (steps <= 0 && view->lineno == 0) {
1188                 report("Cannot move beyond the first line");
1189                 return;
1191         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1192                 report("Cannot move beyond the last line");
1193                 return;
1194         }
1196         /* Move the current line */
1197         view->lineno += steps;
1198         assert(0 <= view->lineno && view->lineno < view->lines);
1200         /* Repaint the old "current" line if we be scrolling */
1201         if (ABS(steps) < view->height) {
1202                 int prev_lineno = view->lineno - steps - view->offset;
1204                 wmove(view->win, prev_lineno, 0);
1205                 wclrtoeol(view->win);
1206                 draw_view_line(view,  prev_lineno);
1207         }
1209         /* Check whether the view needs to be scrolled */
1210         if (view->lineno < view->offset ||
1211             view->lineno >= view->offset + view->height) {
1212                 if (steps < 0 && -steps > view->offset) {
1213                         steps = -view->offset;
1215                 } else if (steps > 0) {
1216                         if (view->lineno == view->lines - 1 &&
1217                             view->lines > view->height) {
1218                                 steps = view->lines - view->offset - 1;
1219                                 if (steps >= view->height)
1220                                         steps -= view->height - 1;
1221                         }
1222                 }
1224                 do_scroll_view(view, steps, redraw);
1225                 return;
1226         }
1228         /* Draw the current line */
1229         draw_view_line(view, view->lineno - view->offset);
1231         if (!redraw)
1232                 return;
1234         redrawwin(view->win);
1235         wrefresh(view->win);
1236         report("");
1240 /*
1241  * Incremental updating
1242  */
1244 static void
1245 end_update(struct view *view)
1247         if (!view->pipe)
1248                 return;
1249         set_nonblocking_input(FALSE);
1250         if (view->pipe == stdin)
1251                 fclose(view->pipe);
1252         else
1253                 pclose(view->pipe);
1254         view->pipe = NULL;
1257 static bool
1258 begin_update(struct view *view)
1260         const char *id = view->id;
1262         if (view->pipe)
1263                 end_update(view);
1265         if (opt_cmd[0]) {
1266                 string_copy(view->cmd, opt_cmd);
1267                 opt_cmd[0] = 0;
1268                 /* When running random commands, the view ref could have become
1269                  * invalid so clear it. */
1270                 view->ref[0] = 0;
1271         } else {
1272                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1274                 if (snprintf(view->cmd, sizeof(view->cmd), format,
1275                              id, id, id, id, id) >= sizeof(view->cmd))
1276                         return FALSE;
1277         }
1279         /* Special case for the pager view. */
1280         if (opt_pipe) {
1281                 view->pipe = opt_pipe;
1282                 opt_pipe = NULL;
1283         } else {
1284                 view->pipe = popen(view->cmd, "r");
1285         }
1287         if (!view->pipe)
1288                 return FALSE;
1290         set_nonblocking_input(TRUE);
1292         view->offset = 0;
1293         view->lines  = 0;
1294         view->lineno = 0;
1295         string_copy(view->vid, id);
1297         if (view->line) {
1298                 int i;
1300                 for (i = 0; i < view->lines; i++)
1301                         if (view->line[i].data)
1302                                 free(view->line[i].data);
1304                 free(view->line);
1305                 view->line = NULL;
1306         }
1308         view->start_time = time(NULL);
1310         return TRUE;
1313 static struct line *
1314 realloc_lines(struct view *view, size_t line_size)
1316         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1318         if (!tmp)
1319                 return NULL;
1321         view->line = tmp;
1322         view->line_size = line_size;
1323         return view->line;
1326 static bool
1327 update_view(struct view *view)
1329         char buffer[BUFSIZ];
1330         char *line;
1331         /* The number of lines to read. If too low it will cause too much
1332          * redrawing (and possible flickering), if too high responsiveness
1333          * will suffer. */
1334         unsigned long lines = view->height;
1335         int redraw_from = -1;
1337         if (!view->pipe)
1338                 return TRUE;
1340         /* Only redraw if lines are visible. */
1341         if (view->offset + view->height >= view->lines)
1342                 redraw_from = view->lines - view->offset;
1344         if (!realloc_lines(view, view->lines + lines))
1345                 goto alloc_error;
1347         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1348                 int linelen = strlen(line);
1350                 struct line *prev = view->lines
1351                                   ? &view->line[view->lines - 1]
1352                                   : NULL;
1354                 if (linelen)
1355                         line[linelen - 1] = 0;
1357                 if (!view->ops->read(view, prev, line))
1358                         goto alloc_error;
1360                 if (lines-- == 1)
1361                         break;
1362         }
1364         {
1365                 int digits;
1367                 lines = view->lines;
1368                 for (digits = 0; lines; digits++)
1369                         lines /= 10;
1371                 /* Keep the displayed view in sync with line number scaling. */
1372                 if (digits != view->digits) {
1373                         view->digits = digits;
1374                         redraw_from = 0;
1375                 }
1376         }
1378         if (redraw_from >= 0) {
1379                 /* If this is an incremental update, redraw the previous line
1380                  * since for commits some members could have changed when
1381                  * loading the main view. */
1382                 if (redraw_from > 0)
1383                         redraw_from--;
1385                 /* Incrementally draw avoids flickering. */
1386                 redraw_view_from(view, redraw_from);
1387         }
1389         /* Update the title _after_ the redraw so that if the redraw picks up a
1390          * commit reference in view->ref it'll be available here. */
1391         update_view_title(view);
1393         if (ferror(view->pipe)) {
1394                 report("Failed to read: %s", strerror(errno));
1395                 goto end;
1397         } else if (feof(view->pipe)) {
1398                 report("");
1399                 goto end;
1400         }
1402         return TRUE;
1404 alloc_error:
1405         report("Allocation failure");
1407 end:
1408         end_update(view);
1409         return FALSE;
1412 enum open_flags {
1413         OPEN_DEFAULT = 0,       /* Use default view switching. */
1414         OPEN_SPLIT = 1,         /* Split current view. */
1415         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1416         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1417 };
1419 static void
1420 open_view(struct view *prev, enum request request, enum open_flags flags)
1422         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1423         bool split = !!(flags & OPEN_SPLIT);
1424         bool reload = !!(flags & OPEN_RELOAD);
1425         struct view *view = VIEW(request);
1426         int nviews = displayed_views();
1427         struct view *base_view = display[0];
1429         if (view == prev && nviews == 1 && !reload) {
1430                 report("Already in %s view", view->name);
1431                 return;
1432         }
1434         if ((reload || strcmp(view->vid, view->id)) &&
1435             !begin_update(view)) {
1436                 report("Failed to load %s view", view->name);
1437                 return;
1438         }
1440         if (split) {
1441                 display[1] = view;
1442                 if (!backgrounded)
1443                         current_view = 1;
1444         } else {
1445                 /* Maximize the current view. */
1446                 memset(display, 0, sizeof(display));
1447                 current_view = 0;
1448                 display[current_view] = view;
1449         }
1451         /* Resize the view when switching between split- and full-screen,
1452          * or when switching between two different full-screen views. */
1453         if (nviews != displayed_views() ||
1454             (nviews == 1 && base_view != display[0]))
1455                 resize_display();
1457         if (split && prev->lineno - prev->offset >= prev->height) {
1458                 /* Take the title line into account. */
1459                 int lines = prev->lineno - prev->offset - prev->height + 1;
1461                 /* Scroll the view that was split if the current line is
1462                  * outside the new limited view. */
1463                 do_scroll_view(prev, lines, TRUE);
1464         }
1466         if (prev && view != prev) {
1467                 if (split && !backgrounded) {
1468                         /* "Blur" the previous view. */
1469                         update_view_title(prev);
1470                 }
1472                 view->parent = prev;
1473         }
1475         if (view == VIEW(REQ_VIEW_HELP))
1476                 load_help_page();
1478         if (view->pipe && view->lines == 0) {
1479                 /* Clear the old view and let the incremental updating refill
1480                  * the screen. */
1481                 wclear(view->win);
1482                 report("");
1483         } else {
1484                 redraw_view(view);
1485                 report("");
1486         }
1488         /* If the view is backgrounded the above calls to report()
1489          * won't redraw the view title. */
1490         if (backgrounded)
1491                 update_view_title(view);
1495 /*
1496  * User request switch noodle
1497  */
1499 static int
1500 view_driver(struct view *view, enum request request)
1502         int i;
1504         switch (request) {
1505         case REQ_MOVE_UP:
1506         case REQ_MOVE_DOWN:
1507         case REQ_MOVE_PAGE_UP:
1508         case REQ_MOVE_PAGE_DOWN:
1509         case REQ_MOVE_FIRST_LINE:
1510         case REQ_MOVE_LAST_LINE:
1511                 move_view(view, request, TRUE);
1512                 break;
1514         case REQ_SCROLL_LINE_DOWN:
1515         case REQ_SCROLL_LINE_UP:
1516         case REQ_SCROLL_PAGE_DOWN:
1517         case REQ_SCROLL_PAGE_UP:
1518                 scroll_view(view, request);
1519                 break;
1521         case REQ_VIEW_MAIN:
1522         case REQ_VIEW_DIFF:
1523         case REQ_VIEW_LOG:
1524         case REQ_VIEW_HELP:
1525         case REQ_VIEW_PAGER:
1526                 open_view(view, request, OPEN_DEFAULT);
1527                 break;
1529         case REQ_NEXT:
1530         case REQ_PREVIOUS:
1531                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1533                 if (view == VIEW(REQ_VIEW_DIFF) &&
1534                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1535                         bool redraw = display[1] == view;
1537                         view = view->parent;
1538                         move_view(view, request, redraw);
1539                         if (redraw)
1540                                 update_view_title(view);
1541                 } else {
1542                         move_view(view, request, TRUE);
1543                         break;
1544                 }
1545                 /* Fall-through */
1547         case REQ_ENTER:
1548                 if (!view->lines) {
1549                         report("Nothing to enter");
1550                         break;
1551                 }
1552                 return view->ops->enter(view, &view->line[view->lineno]);
1554         case REQ_VIEW_NEXT:
1555         {
1556                 int nviews = displayed_views();
1557                 int next_view = (current_view + 1) % nviews;
1559                 if (next_view == current_view) {
1560                         report("Only one view is displayed");
1561                         break;
1562                 }
1564                 current_view = next_view;
1565                 /* Blur out the title of the previous view. */
1566                 update_view_title(view);
1567                 report("");
1568                 break;
1569         }
1570         case REQ_TOGGLE_LINENO:
1571                 opt_line_number = !opt_line_number;
1572                 redraw_display();
1573                 break;
1575         case REQ_PROMPT:
1576                 /* Always reload^Wrerun commands from the prompt. */
1577                 open_view(view, opt_request, OPEN_RELOAD);
1578                 break;
1580         case REQ_STOP_LOADING:
1581                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1582                         view = &views[i];
1583                         if (view->pipe)
1584                                 report("Stopped loading the %s view", view->name),
1585                         end_update(view);
1586                 }
1587                 break;
1589         case REQ_SHOW_VERSION:
1590                 report("%s (built %s)", VERSION, __DATE__);
1591                 return TRUE;
1593         case REQ_SCREEN_RESIZE:
1594                 resize_display();
1595                 /* Fall-through */
1596         case REQ_SCREEN_REDRAW:
1597                 redraw_display();
1598                 break;
1600         case REQ_SCREEN_UPDATE:
1601                 doupdate();
1602                 return TRUE;
1604         case REQ_VIEW_CLOSE:
1605                 /* XXX: Mark closed views by letting view->parent point to the
1606                  * view itself. Parents to closed view should never be
1607                  * followed. */
1608                 if (view->parent &&
1609                     view->parent->parent != view->parent) {
1610                         memset(display, 0, sizeof(display));
1611                         current_view = 0;
1612                         display[current_view] = view->parent;
1613                         view->parent = view;
1614                         resize_display();
1615                         redraw_display();
1616                         break;
1617                 }
1618                 /* Fall-through */
1619         case REQ_QUIT:
1620                 return FALSE;
1622         default:
1623                 /* An unknown key will show most commonly used commands. */
1624                 report("Unknown key, press 'h' for help");
1625                 return TRUE;
1626         }
1628         return TRUE;
1632 /*
1633  * Pager backend
1634  */
1636 static bool
1637 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1639         char *text = line->data;
1640         enum line_type type = line->type;
1641         int textlen = strlen(text);
1642         int attr;
1644         wmove(view->win, lineno, 0);
1646         if (view->offset + lineno == view->lineno) {
1647                 if (type == LINE_COMMIT) {
1648                         string_copy(view->ref, text + 7);
1649                         string_copy(ref_commit, view->ref);
1650                 }
1652                 type = LINE_CURSOR;
1653                 wchgat(view->win, -1, 0, type, NULL);
1654         }
1656         attr = get_line_attr(type);
1657         wattrset(view->win, attr);
1659         if (opt_line_number || opt_tab_size < TABSIZE) {
1660                 static char spaces[] = "                    ";
1661                 int col_offset = 0, col = 0;
1663                 if (opt_line_number) {
1664                         unsigned long real_lineno = view->offset + lineno + 1;
1666                         if (real_lineno == 1 ||
1667                             (real_lineno % opt_num_interval) == 0) {
1668                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1670                         } else {
1671                                 waddnstr(view->win, spaces,
1672                                          MIN(view->digits, STRING_SIZE(spaces)));
1673                         }
1674                         waddstr(view->win, ": ");
1675                         col_offset = view->digits + 2;
1676                 }
1678                 while (text && col_offset + col < view->width) {
1679                         int cols_max = view->width - col_offset - col;
1680                         char *pos = text;
1681                         int cols;
1683                         if (*text == '\t') {
1684                                 text++;
1685                                 assert(sizeof(spaces) > TABSIZE);
1686                                 pos = spaces;
1687                                 cols = opt_tab_size - (col % opt_tab_size);
1689                         } else {
1690                                 text = strchr(text, '\t');
1691                                 cols = line ? text - pos : strlen(pos);
1692                         }
1694                         waddnstr(view->win, pos, MIN(cols, cols_max));
1695                         col += cols;
1696                 }
1698         } else {
1699                 int col = 0, pos = 0;
1701                 for (; pos < textlen && col < view->width; pos++, col++)
1702                         if (text[pos] == '\t')
1703                                 col += TABSIZE - (col % TABSIZE) - 1;
1705                 waddnstr(view->win, text, pos);
1706         }
1708         return TRUE;
1711 static bool
1712 pager_read(struct view *view, struct line *prev, char *line)
1714         view->line[view->lines].data = strdup(line);
1715         if (!view->line[view->lines].data)
1716                 return FALSE;
1718         view->line[view->lines].type = get_line_type(line);
1720         view->lines++;
1721         return TRUE;
1724 static bool
1725 pager_enter(struct view *view, struct line *line)
1727         int split = 0;
1729         if (line->type == LINE_COMMIT &&
1730            (view == VIEW(REQ_VIEW_LOG) ||
1731             view == VIEW(REQ_VIEW_PAGER))) {
1732                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1733                 split = 1;
1734         }
1736         /* Always scroll the view even if it was split. That way
1737          * you can use Enter to scroll through the log view and
1738          * split open each commit diff. */
1739         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1741         /* FIXME: A minor workaround. Scrolling the view will call report("")
1742          * but if we are scrolling a non-current view this won't properly
1743          * update the view title. */
1744         if (split)
1745                 update_view_title(view);
1747         return TRUE;
1750 static struct view_ops pager_ops = {
1751         "line",
1752         pager_draw,
1753         pager_read,
1754         pager_enter,
1755 };
1758 /*
1759  * Main view backend
1760  */
1762 struct commit {
1763         char id[41];            /* SHA1 ID. */
1764         char title[75];         /* The first line of the commit message. */
1765         char author[75];        /* The author of the commit. */
1766         struct tm time;         /* Date from the author ident. */
1767         struct ref **refs;      /* Repository references; tags & branch heads. */
1768 };
1770 static bool
1771 main_draw(struct view *view, struct line *line, unsigned int lineno)
1773         char buf[DATE_COLS + 1];
1774         struct commit *commit = line->data;
1775         enum line_type type;
1776         int col = 0;
1777         size_t timelen;
1778         size_t authorlen;
1779         int trimmed = 1;
1781         if (!*commit->author)
1782                 return FALSE;
1784         wmove(view->win, lineno, col);
1786         if (view->offset + lineno == view->lineno) {
1787                 string_copy(view->ref, commit->id);
1788                 string_copy(ref_commit, view->ref);
1789                 type = LINE_CURSOR;
1790                 wattrset(view->win, get_line_attr(type));
1791                 wchgat(view->win, -1, 0, type, NULL);
1793         } else {
1794                 type = LINE_MAIN_COMMIT;
1795                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
1796         }
1798         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
1799         waddnstr(view->win, buf, timelen);
1800         waddstr(view->win, " ");
1802         col += DATE_COLS;
1803         wmove(view->win, lineno, col);
1804         if (type != LINE_CURSOR)
1805                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
1807         if (opt_utf8) {
1808                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1809         } else {
1810                 authorlen = strlen(commit->author);
1811                 if (authorlen > AUTHOR_COLS - 2) {
1812                         authorlen = AUTHOR_COLS - 2;
1813                         trimmed = 1;
1814                 }
1815         }
1817         if (trimmed) {
1818                 waddnstr(view->win, commit->author, authorlen);
1819                 if (type != LINE_CURSOR)
1820                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
1821                 waddch(view->win, '~');
1822         } else {
1823                 waddstr(view->win, commit->author);
1824         }
1826         col += AUTHOR_COLS;
1827         if (type != LINE_CURSOR)
1828                 wattrset(view->win, A_NORMAL);
1830         mvwaddch(view->win, lineno, col, ACS_LTEE);
1831         wmove(view->win, lineno, col + 2);
1832         col += 2;
1834         if (commit->refs) {
1835                 size_t i = 0;
1837                 do {
1838                         if (type == LINE_CURSOR)
1839                                 ;
1840                         else if (commit->refs[i]->tag)
1841                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1842                         else
1843                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1844                         waddstr(view->win, "[");
1845                         waddstr(view->win, commit->refs[i]->name);
1846                         waddstr(view->win, "]");
1847                         if (type != LINE_CURSOR)
1848                                 wattrset(view->win, A_NORMAL);
1849                         waddstr(view->win, " ");
1850                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
1851                 } while (commit->refs[i++]->next);
1852         }
1854         if (type != LINE_CURSOR)
1855                 wattrset(view->win, get_line_attr(type));
1857         {
1858                 int titlelen = strlen(commit->title);
1860                 if (col + titlelen > view->width)
1861                         titlelen = view->width - col;
1863                 waddnstr(view->win, commit->title, titlelen);
1864         }
1866         return TRUE;
1869 /* Reads git log --pretty=raw output and parses it into the commit struct. */
1870 static bool
1871 main_read(struct view *view, struct line *prev, char *line)
1873         enum line_type type = get_line_type(line);
1874         struct commit *commit;
1876         switch (type) {
1877         case LINE_COMMIT:
1878                 commit = calloc(1, sizeof(struct commit));
1879                 if (!commit)
1880                         return FALSE;
1882                 line += STRING_SIZE("commit ");
1884                 view->line[view->lines++].data = commit;
1885                 string_copy(commit->id, line);
1886                 commit->refs = get_refs(commit->id);
1887                 break;
1889         case LINE_AUTHOR:
1890         {
1891                 char *ident = line + STRING_SIZE("author ");
1892                 char *end = strchr(ident, '<');
1894                 if (!prev)
1895                         break;
1897                 commit = prev->data;
1899                 if (end) {
1900                         for (; end > ident && isspace(end[-1]); end--) ;
1901                         *end = 0;
1902                 }
1904                 string_copy(commit->author, ident);
1906                 /* Parse epoch and timezone */
1907                 if (end) {
1908                         char *secs = strchr(end + 1, '>');
1909                         char *zone;
1910                         time_t time;
1912                         if (!secs || secs[1] != ' ')
1913                                 break;
1915                         secs += 2;
1916                         time = (time_t) atol(secs);
1917                         zone = strchr(secs, ' ');
1918                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
1919                                 long tz;
1921                                 zone++;
1922                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
1923                                 tz += ('0' - zone[2]) * 60 * 60;
1924                                 tz += ('0' - zone[3]) * 60;
1925                                 tz += ('0' - zone[4]) * 60;
1927                                 if (zone[0] == '-')
1928                                         tz = -tz;
1930                                 time -= tz;
1931                         }
1932                         gmtime_r(&time, &commit->time);
1933                 }
1934                 break;
1935         }
1936         default:
1937                 if (!prev)
1938                         break;
1940                 commit = prev->data;
1942                 /* Fill in the commit title if it has not already been set. */
1943                 if (commit->title[0])
1944                         break;
1946                 /* Require titles to start with a non-space character at the
1947                  * offset used by git log. */
1948                 /* FIXME: More gracefull handling of titles; append "..." to
1949                  * shortened titles, etc. */
1950                 if (strncmp(line, "    ", 4) ||
1951                     isspace(line[4]))
1952                         break;
1954                 string_copy(commit->title, line + 4);
1955         }
1957         return TRUE;
1960 static bool
1961 main_enter(struct view *view, struct line *line)
1963         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
1965         open_view(view, REQ_VIEW_DIFF, flags);
1966         return TRUE;
1969 static struct view_ops main_ops = {
1970         "commit",
1971         main_draw,
1972         main_read,
1973         main_enter,
1974 };
1977 /*
1978  * Keys
1979  */
1981 struct keymap {
1982         int alias;
1983         int request;
1984 };
1986 static struct keymap keymap[] = {
1987         /* View switching */
1988         { 'm',          REQ_VIEW_MAIN },
1989         { 'd',          REQ_VIEW_DIFF },
1990         { 'l',          REQ_VIEW_LOG },
1991         { 'p',          REQ_VIEW_PAGER },
1992         { 'h',          REQ_VIEW_HELP },
1993         { '?',          REQ_VIEW_HELP },
1995         /* View manipulation */
1996         { 'q',          REQ_VIEW_CLOSE },
1997         { KEY_TAB,      REQ_VIEW_NEXT },
1998         { KEY_RETURN,   REQ_ENTER },
1999         { KEY_UP,       REQ_PREVIOUS },
2000         { KEY_DOWN,     REQ_NEXT },
2002         /* Cursor navigation */
2003         { 'k',          REQ_MOVE_UP },
2004         { 'j',          REQ_MOVE_DOWN },
2005         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
2006         { KEY_END,      REQ_MOVE_LAST_LINE },
2007         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
2008         { ' ',          REQ_MOVE_PAGE_DOWN },
2009         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
2010         { 'b',          REQ_MOVE_PAGE_UP },
2011         { '-',          REQ_MOVE_PAGE_UP },
2013         /* Scrolling */
2014         { KEY_IC,       REQ_SCROLL_LINE_UP },
2015         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
2016         { 'w',          REQ_SCROLL_PAGE_UP },
2017         { 's',          REQ_SCROLL_PAGE_DOWN },
2019         /* Misc */
2020         { 'Q',          REQ_QUIT },
2021         { 'z',          REQ_STOP_LOADING },
2022         { 'v',          REQ_SHOW_VERSION },
2023         { 'r',          REQ_SCREEN_REDRAW },
2024         { 'n',          REQ_TOGGLE_LINENO },
2025         { ':',          REQ_PROMPT },
2027         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
2028         { ERR,          REQ_SCREEN_UPDATE },
2030         /* Use the ncurses SIGWINCH handler. */
2031         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
2032 };
2034 static enum request
2035 get_request(int key)
2037         int i;
2039         for (i = 0; i < ARRAY_SIZE(keymap); i++)
2040                 if (keymap[i].alias == key)
2041                         return keymap[i].request;
2043         return (enum request) key;
2046 struct key {
2047         char *name;
2048         int value;
2049 };
2051 static struct key key_table[] = {
2052         { "Enter",      KEY_RETURN },
2053         { "Space",      ' ' },
2054         { "Backspace",  KEY_BACKSPACE },
2055         { "Tab",        KEY_TAB },
2056         { "Escape",     KEY_ESC },
2057         { "Left",       KEY_LEFT },
2058         { "Right",      KEY_RIGHT },
2059         { "Up",         KEY_UP },
2060         { "Down",       KEY_DOWN },
2061         { "Insert",     KEY_IC },
2062         { "Delete",     KEY_DC },
2063         { "Home",       KEY_HOME },
2064         { "End",        KEY_END },
2065         { "PageUp",     KEY_PPAGE },
2066         { "PageDown",   KEY_NPAGE },
2067         { "F1",         KEY_F(1) },
2068         { "F2",         KEY_F(2) },
2069         { "F3",         KEY_F(3) },
2070         { "F4",         KEY_F(4) },
2071         { "F5",         KEY_F(5) },
2072         { "F6",         KEY_F(6) },
2073         { "F7",         KEY_F(7) },
2074         { "F8",         KEY_F(8) },
2075         { "F9",         KEY_F(9) },
2076         { "F10",        KEY_F(10) },
2077         { "F11",        KEY_F(11) },
2078         { "F12",        KEY_F(12) },
2079 };
2081 static char *
2082 get_key(enum request request)
2084         static char buf[BUFSIZ];
2085         static char key_char[] = "'X'";
2086         int pos = 0;
2087         char *sep = "    ";
2088         int i;
2090         buf[pos] = 0;
2092         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
2093                 char *seq = NULL;
2094                 int key;
2096                 if (keymap[i].request != request)
2097                         continue;
2099                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
2100                         if (key_table[key].value == keymap[i].alias)
2101                                 seq = key_table[key].name;
2103                 if (seq == NULL &&
2104                     keymap[i].alias < 127 &&
2105                     isprint(keymap[i].alias)) {
2106                         key_char[1] = (char) keymap[i].alias;
2107                         seq = key_char;
2108                 }
2110                 if (!seq)
2111                         seq = "'?'";
2113                 pos += snprintf(buf + pos, sizeof(buf) - pos, "%s%s", sep, seq);
2114                 if (pos >= sizeof(buf))
2115                         return "Too many keybindings!";
2116                 sep = ", ";
2117         }
2119         return buf;
2122 static void load_help_page(void)
2124         char buf[BUFSIZ];
2125         struct view *view = VIEW(REQ_VIEW_HELP);
2126         int lines = ARRAY_SIZE(req_info) + 2;
2127         int i;
2129         if (view->lines > 0)
2130                 return;
2132         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2133                 if (!req_info[i].request)
2134                         lines++;
2136         view->line = calloc(lines, sizeof(*view->line));
2137         if (!view->line) {
2138                 report("Allocation failure");
2139                 return;
2140         }
2142         pager_read(view, NULL, "Quick reference for tig keybindings:");
2144         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2145                 char *key;
2147                 if (!req_info[i].request) {
2148                         pager_read(view, NULL, "");
2149                         pager_read(view, NULL, req_info[i].help);
2150                         continue;
2151                 }
2153                 key = get_key(req_info[i].request);
2154                 if (snprintf(buf, sizeof(buf), "%-25s %s", key, req_info[i].help)
2155                     >= sizeof(buf))
2156                         continue;
2158                 pager_read(view, NULL, buf);
2159         }
2163 /*
2164  * Unicode / UTF-8 handling
2165  *
2166  * NOTE: Much of the following code for dealing with unicode is derived from
2167  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2168  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2169  */
2171 /* I've (over)annotated a lot of code snippets because I am not entirely
2172  * confident that the approach taken by this small UTF-8 interface is correct.
2173  * --jonas */
2175 static inline int
2176 unicode_width(unsigned long c)
2178         if (c >= 0x1100 &&
2179            (c <= 0x115f                         /* Hangul Jamo */
2180             || c == 0x2329
2181             || c == 0x232a
2182             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2183                                                 /* CJK ... Yi */
2184             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2185             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2186             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2187             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2188             || (c >= 0xffe0  && c <= 0xffe6)
2189             || (c >= 0x20000 && c <= 0x2fffd)
2190             || (c >= 0x30000 && c <= 0x3fffd)))
2191                 return 2;
2193         return 1;
2196 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2197  * Illegal bytes are set one. */
2198 static const unsigned char utf8_bytes[256] = {
2199         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,
2200         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,
2201         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,
2202         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,
2203         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,
2204         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,
2205         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,
2206         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,
2207 };
2209 /* Decode UTF-8 multi-byte representation into a unicode character. */
2210 static inline unsigned long
2211 utf8_to_unicode(const char *string, size_t length)
2213         unsigned long unicode;
2215         switch (length) {
2216         case 1:
2217                 unicode  =   string[0];
2218                 break;
2219         case 2:
2220                 unicode  =  (string[0] & 0x1f) << 6;
2221                 unicode +=  (string[1] & 0x3f);
2222                 break;
2223         case 3:
2224                 unicode  =  (string[0] & 0x0f) << 12;
2225                 unicode += ((string[1] & 0x3f) << 6);
2226                 unicode +=  (string[2] & 0x3f);
2227                 break;
2228         case 4:
2229                 unicode  =  (string[0] & 0x0f) << 18;
2230                 unicode += ((string[1] & 0x3f) << 12);
2231                 unicode += ((string[2] & 0x3f) << 6);
2232                 unicode +=  (string[3] & 0x3f);
2233                 break;
2234         case 5:
2235                 unicode  =  (string[0] & 0x0f) << 24;
2236                 unicode += ((string[1] & 0x3f) << 18);
2237                 unicode += ((string[2] & 0x3f) << 12);
2238                 unicode += ((string[3] & 0x3f) << 6);
2239                 unicode +=  (string[4] & 0x3f);
2240                 break;
2241         case 6:
2242                 unicode  =  (string[0] & 0x01) << 30;
2243                 unicode += ((string[1] & 0x3f) << 24);
2244                 unicode += ((string[2] & 0x3f) << 18);
2245                 unicode += ((string[3] & 0x3f) << 12);
2246                 unicode += ((string[4] & 0x3f) << 6);
2247                 unicode +=  (string[5] & 0x3f);
2248                 break;
2249         default:
2250                 die("Invalid unicode length");
2251         }
2253         /* Invalid characters could return the special 0xfffd value but NUL
2254          * should be just as good. */
2255         return unicode > 0xffff ? 0 : unicode;
2258 /* Calculates how much of string can be shown within the given maximum width
2259  * and sets trimmed parameter to non-zero value if all of string could not be
2260  * shown.
2261  *
2262  * Additionally, adds to coloffset how many many columns to move to align with
2263  * the expected position. Takes into account how multi-byte and double-width
2264  * characters will effect the cursor position.
2265  *
2266  * Returns the number of bytes to output from string to satisfy max_width. */
2267 static size_t
2268 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2270         const char *start = string;
2271         const char *end = strchr(string, '\0');
2272         size_t mbwidth = 0;
2273         size_t width = 0;
2275         *trimmed = 0;
2277         while (string < end) {
2278                 int c = *(unsigned char *) string;
2279                 unsigned char bytes = utf8_bytes[c];
2280                 size_t ucwidth;
2281                 unsigned long unicode;
2283                 if (string + bytes > end)
2284                         break;
2286                 /* Change representation to figure out whether
2287                  * it is a single- or double-width character. */
2289                 unicode = utf8_to_unicode(string, bytes);
2290                 /* FIXME: Graceful handling of invalid unicode character. */
2291                 if (!unicode)
2292                         break;
2294                 ucwidth = unicode_width(unicode);
2295                 width  += ucwidth;
2296                 if (width > max_width) {
2297                         *trimmed = 1;
2298                         break;
2299                 }
2301                 /* The column offset collects the differences between the
2302                  * number of bytes encoding a character and the number of
2303                  * columns will be used for rendering said character.
2304                  *
2305                  * So if some character A is encoded in 2 bytes, but will be
2306                  * represented on the screen using only 1 byte this will and up
2307                  * adding 1 to the multi-byte column offset.
2308                  *
2309                  * Assumes that no double-width character can be encoding in
2310                  * less than two bytes. */
2311                 if (bytes > ucwidth)
2312                         mbwidth += bytes - ucwidth;
2314                 string  += bytes;
2315         }
2317         *coloffset += mbwidth;
2319         return string - start;
2323 /*
2324  * Status management
2325  */
2327 /* Whether or not the curses interface has been initialized. */
2328 static bool cursed = FALSE;
2330 /* The status window is used for polling keystrokes. */
2331 static WINDOW *status_win;
2333 /* Update status and title window. */
2334 static void
2335 report(const char *msg, ...)
2337         static bool empty = TRUE;
2338         struct view *view = display[current_view];
2340         if (!empty || *msg) {
2341                 va_list args;
2343                 va_start(args, msg);
2345                 werase(status_win);
2346                 wmove(status_win, 0, 0);
2347                 if (*msg) {
2348                         vwprintw(status_win, msg, args);
2349                         empty = FALSE;
2350                 } else {
2351                         empty = TRUE;
2352                 }
2353                 wrefresh(status_win);
2355                 va_end(args);
2356         }
2358         update_view_title(view);
2359         update_display_cursor();
2362 /* Controls when nodelay should be in effect when polling user input. */
2363 static void
2364 set_nonblocking_input(bool loading)
2366         static unsigned int loading_views;
2368         if ((loading == FALSE && loading_views-- == 1) ||
2369             (loading == TRUE  && loading_views++ == 0))
2370                 nodelay(status_win, loading);
2373 static void
2374 init_display(void)
2376         int x, y;
2378         /* Initialize the curses library */
2379         if (isatty(STDIN_FILENO)) {
2380                 cursed = !!initscr();
2381         } else {
2382                 /* Leave stdin and stdout alone when acting as a pager. */
2383                 FILE *io = fopen("/dev/tty", "r+");
2385                 cursed = !!newterm(NULL, io, io);
2386         }
2388         if (!cursed)
2389                 die("Failed to initialize curses");
2391         nonl();         /* Tell curses not to do NL->CR/NL on output */
2392         cbreak();       /* Take input chars one at a time, no wait for \n */
2393         noecho();       /* Don't echo input */
2394         leaveok(stdscr, TRUE);
2396         if (has_colors())
2397                 init_colors();
2399         getmaxyx(stdscr, y, x);
2400         status_win = newwin(1, 0, y - 1, 0);
2401         if (!status_win)
2402                 die("Failed to create status window");
2404         /* Enable keyboard mapping */
2405         keypad(status_win, TRUE);
2406         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2410 /*
2411  * Repository references
2412  */
2414 static struct ref *refs;
2415 static size_t refs_size;
2417 /* Id <-> ref store */
2418 static struct ref ***id_refs;
2419 static size_t id_refs_size;
2421 static struct ref **
2422 get_refs(char *id)
2424         struct ref ***tmp_id_refs;
2425         struct ref **ref_list = NULL;
2426         size_t ref_list_size = 0;
2427         size_t i;
2429         for (i = 0; i < id_refs_size; i++)
2430                 if (!strcmp(id, id_refs[i][0]->id))
2431                         return id_refs[i];
2433         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2434         if (!tmp_id_refs)
2435                 return NULL;
2437         id_refs = tmp_id_refs;
2439         for (i = 0; i < refs_size; i++) {
2440                 struct ref **tmp;
2442                 if (strcmp(id, refs[i].id))
2443                         continue;
2445                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2446                 if (!tmp) {
2447                         if (ref_list)
2448                                 free(ref_list);
2449                         return NULL;
2450                 }
2452                 ref_list = tmp;
2453                 if (ref_list_size > 0)
2454                         ref_list[ref_list_size - 1]->next = 1;
2455                 ref_list[ref_list_size] = &refs[i];
2457                 /* XXX: The properties of the commit chains ensures that we can
2458                  * safely modify the shared ref. The repo references will
2459                  * always be similar for the same id. */
2460                 ref_list[ref_list_size]->next = 0;
2461                 ref_list_size++;
2462         }
2464         if (ref_list)
2465                 id_refs[id_refs_size++] = ref_list;
2467         return ref_list;
2470 static int
2471 read_ref(char *id, int idlen, char *name, int namelen)
2473         struct ref *ref;
2474         bool tag = FALSE;
2475         bool tag_commit = FALSE;
2477         /* Commits referenced by tags has "^{}" appended. */
2478         if (name[namelen - 1] == '}') {
2479                 while (namelen > 0 && name[namelen] != '^')
2480                         namelen--;
2481                 if (namelen > 0)
2482                         tag_commit = TRUE;
2483                 name[namelen] = 0;
2484         }
2486         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2487                 if (!tag_commit)
2488                         return OK;
2489                 name += STRING_SIZE("refs/tags/");
2490                 tag = TRUE;
2492         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2493                 name += STRING_SIZE("refs/heads/");
2495         } else if (!strcmp(name, "HEAD")) {
2496                 return OK;
2497         }
2499         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2500         if (!refs)
2501                 return ERR;
2503         ref = &refs[refs_size++];
2504         ref->name = strdup(name);
2505         if (!ref->name)
2506                 return ERR;
2508         ref->tag = tag;
2509         string_copy(ref->id, id);
2511         return OK;
2514 static int
2515 load_refs(void)
2517         const char *cmd_env = getenv("TIG_LS_REMOTE");
2518         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2520         return read_properties(popen(cmd, "r"), "\t", read_ref);
2523 static int
2524 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2526         if (!strcmp(name, "i18n.commitencoding")) {
2527                 string_copy(opt_encoding, value);
2528         }
2530         return OK;
2533 static int
2534 load_repo_config(void)
2536         return read_properties(popen("git repo-config --list", "r"),
2537                                "=", read_repo_config_option);
2540 static int
2541 read_properties(FILE *pipe, const char *separators,
2542                 int (*read_property)(char *, int, char *, int))
2544         char buffer[BUFSIZ];
2545         char *name;
2546         int state = OK;
2548         if (!pipe)
2549                 return ERR;
2551         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2552                 char *value;
2553                 size_t namelen;
2554                 size_t valuelen;
2556                 name = chomp_string(name);
2557                 namelen = strcspn(name, separators);
2559                 if (name[namelen]) {
2560                         name[namelen] = 0;
2561                         value = chomp_string(name + namelen + 1);
2562                         valuelen = strlen(value);
2564                 } else {
2565                         value = "";
2566                         valuelen = 0;
2567                 }
2569                 state = read_property(name, namelen, value, valuelen);
2570         }
2572         if (state != ERR && ferror(pipe))
2573                 state = ERR;
2575         pclose(pipe);
2577         return state;
2581 /*
2582  * Main
2583  */
2585 #if __GNUC__ >= 3
2586 #define __NORETURN __attribute__((__noreturn__))
2587 #else
2588 #define __NORETURN
2589 #endif
2591 static void __NORETURN
2592 quit(int sig)
2594         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2595         if (cursed)
2596                 endwin();
2597         exit(0);
2600 static void __NORETURN
2601 die(const char *err, ...)
2603         va_list args;
2605         endwin();
2607         va_start(args, err);
2608         fputs("tig: ", stderr);
2609         vfprintf(stderr, err, args);
2610         fputs("\n", stderr);
2611         va_end(args);
2613         exit(1);
2616 int
2617 main(int argc, char *argv[])
2619         struct view *view;
2620         enum request request;
2621         size_t i;
2623         signal(SIGINT, quit);
2625         if (load_options() == ERR)
2626                 die("Failed to load user config.");
2628         /* Load the repo config file so options can be overwritten from
2629          * the command line.  */
2630         if (load_repo_config() == ERR)
2631                 die("Failed to load repo config.");
2633         if (!parse_options(argc, argv))
2634                 return 0;
2636         if (load_refs() == ERR)
2637                 die("Failed to load refs.");
2639         /* Require a git repository unless when running in pager mode. */
2640         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2641                 die("Not a git repository");
2643         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2644                 view->cmd_env = getenv(view->cmd_env);
2646         request = opt_request;
2648         init_display();
2650         while (view_driver(display[current_view], request)) {
2651                 int key;
2652                 int i;
2654                 foreach_view (view, i)
2655                         update_view(view);
2657                 /* Refresh, accept single keystroke of input */
2658                 key = wgetch(status_win);
2659                 request = get_request(key);
2661                 /* Some low-level request handling. This keeps access to
2662                  * status_win restricted. */
2663                 switch (request) {
2664                 case REQ_PROMPT:
2665                         report(":");
2666                         /* Temporarily switch to line-oriented and echoed
2667                          * input. */
2668                         nocbreak();
2669                         echo();
2671                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2672                                 memcpy(opt_cmd, "git ", 4);
2673                                 opt_request = REQ_VIEW_PAGER;
2674                         } else {
2675                                 report("Prompt interrupted by loading view, "
2676                                        "press 'z' to stop loading views");
2677                                 request = REQ_SCREEN_UPDATE;
2678                         }
2680                         noecho();
2681                         cbreak();
2682                         break;
2684                 case REQ_SCREEN_RESIZE:
2685                 {
2686                         int height, width;
2688                         getmaxyx(stdscr, height, width);
2690                         /* Resize the status view and let the view driver take
2691                          * care of resizing the displayed views. */
2692                         wresize(status_win, 1, width);
2693                         mvwin(status_win, height - 1, 0);
2694                         wrefresh(status_win);
2695                         break;
2696                 }
2697                 default:
2698                         break;
2699                 }
2700         }
2702         quit(0);
2704         return 0;
2707 /**
2708  * include::BUGS[]
2709  *
2710  * COPYRIGHT
2711  * ---------
2712  * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2713  *
2714  * This program is free software; you can redistribute it and/or modify
2715  * it under the terms of the GNU General Public License as published by
2716  * the Free Software Foundation; either version 2 of the License, or
2717  * (at your option) any later version.
2718  *
2719  * SEE ALSO
2720  * --------
2721  * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2722  * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
2723  *
2724  * Other git repository browsers:
2725  *
2726  *  - gitk(1)
2727  *  - qgit(1)
2728  *  - gitview(1)
2729  *
2730  * Sites:
2731  *
2732  * include::SITES[]
2733  **/