Code

Some more refactoring and cleanups
[tig.git] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifndef VERSION
15 #define VERSION "tig-0.4.git"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <sys/types.h>
34 #include <regex.h>
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
40 #include <curses.h>
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
55 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
57 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x)  (sizeof(x) - 1)
60 #define SIZEOF_STR      1024    /* Default string size. */
61 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
64 /* Revision graph */
66 #define REVGRAPH_INIT   'I'
67 #define REVGRAPH_MERGE  'M'
68 #define REVGRAPH_BRANCH '+'
69 #define REVGRAPH_COMMIT '*'
70 #define REVGRAPH_LINE   '|'
72 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
74 /* Size of rev graph with no  "padding" columns */
75 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
77 /* This color name can be used to refer to the default term colors. */
78 #define COLOR_DEFAULT   (-1)
80 #define ICONV_NONE      ((iconv_t) -1)
82 /* The format and size of the date column in the main view. */
83 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
84 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
86 #define AUTHOR_COLS     20
88 /* The default interval between line numbers. */
89 #define NUMBER_INTERVAL 1
91 #define TABSIZE         8
93 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
95 #define TIG_LS_REMOTE \
96         "git ls-remote . 2>/dev/null"
98 #define TIG_DIFF_CMD \
99         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
101 #define TIG_LOG_CMD     \
102         "git log --cc --stat -n100 %s 2>/dev/null"
104 #define TIG_MAIN_CMD \
105         "git log --topo-order --pretty=raw %s 2>/dev/null"
107 #define TIG_TREE_CMD    \
108         "git ls-tree %s %s"
110 #define TIG_BLOB_CMD    \
111         "git cat-file blob %s"
113 /* XXX: Needs to be defined to the empty string. */
114 #define TIG_HELP_CMD    ""
115 #define TIG_PAGER_CMD   ""
117 /* Some ascii-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char *name;             /* Ref name; tag or head names are shortened. */
125         char id[SIZEOF_REV];    /* Commit SHA1 ID */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int next:1;    /* For ref lists: are there more refs? */
128 };
130 static struct ref **get_refs(char *id);
132 struct int_map {
133         const char *name;
134         int namelen;
135         int value;
136 };
138 static int
139 set_from_int_map(struct int_map *map, size_t map_size,
140                  int *value, const char *name, int namelen)
143         int i;
145         for (i = 0; i < map_size; i++)
146                 if (namelen == map[i].namelen &&
147                     !strncasecmp(name, map[i].name, namelen)) {
148                         *value = map[i].value;
149                         return OK;
150                 }
152         return ERR;
156 /*
157  * String helpers
158  */
160 static inline void
161 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
163         if (srclen > dstlen - 1)
164                 srclen = dstlen - 1;
166         strncpy(dst, src, srclen);
167         dst[srclen] = 0;
170 /* Shorthands for safely copying into a fixed buffer. */
172 #define string_copy(dst, src) \
173         string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
175 #define string_ncopy(dst, src, srclen) \
176         string_ncopy_do(dst, sizeof(dst), src, srclen)
178 static char *
179 chomp_string(char *name)
181         int namelen;
183         while (isspace(*name))
184                 name++;
186         namelen = strlen(name) - 1;
187         while (namelen > 0 && isspace(name[namelen]))
188                 name[namelen--] = 0;
190         return name;
193 static bool
194 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
196         va_list args;
197         size_t pos = bufpos ? *bufpos : 0;
199         va_start(args, fmt);
200         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
201         va_end(args);
203         if (bufpos)
204                 *bufpos = pos;
206         return pos >= bufsize ? FALSE : TRUE;
209 #define string_format(buf, fmt, args...) \
210         string_nformat(buf, sizeof(buf), NULL, fmt, args)
212 #define string_format_from(buf, from, fmt, args...) \
213         string_nformat(buf, sizeof(buf), from, fmt, args)
215 static int
216 string_enum_compare(const char *str1, const char *str2, int len)
218         size_t i;
220 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
222         /* Diff-Header == DIFF_HEADER */
223         for (i = 0; i < len; i++) {
224                 if (toupper(str1[i]) == toupper(str2[i]))
225                         continue;
227                 if (string_enum_sep(str1[i]) &&
228                     string_enum_sep(str2[i]))
229                         continue;
231                 return str1[i] - str2[i];
232         }
234         return 0;
237 /* Shell quoting
238  *
239  * NOTE: The following is a slightly modified copy of the git project's shell
240  * quoting routines found in the quote.c file.
241  *
242  * Help to copy the thing properly quoted for the shell safety.  any single
243  * quote is replaced with '\'', any exclamation point is replaced with '\!',
244  * and the whole thing is enclosed in a
245  *
246  * E.g.
247  *  original     sq_quote     result
248  *  name     ==> name      ==> 'name'
249  *  a b      ==> a b       ==> 'a b'
250  *  a'b      ==> a'\''b    ==> 'a'\''b'
251  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
252  */
254 static size_t
255 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
257         char c;
259 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
261         BUFPUT('\'');
262         while ((c = *src++)) {
263                 if (c == '\'' || c == '!') {
264                         BUFPUT('\'');
265                         BUFPUT('\\');
266                         BUFPUT(c);
267                         BUFPUT('\'');
268                 } else {
269                         BUFPUT(c);
270                 }
271         }
272         BUFPUT('\'');
274         return bufsize;
278 /*
279  * User requests
280  */
282 #define REQ_INFO \
283         /* XXX: Keep the view request first and in sync with views[]. */ \
284         REQ_GROUP("View switching") \
285         REQ_(VIEW_MAIN,         "Show main view"), \
286         REQ_(VIEW_DIFF,         "Show diff view"), \
287         REQ_(VIEW_LOG,          "Show log view"), \
288         REQ_(VIEW_TREE,         "Show tree view"), \
289         REQ_(VIEW_BLOB,         "Show blob view"), \
290         REQ_(VIEW_HELP,         "Show help page"), \
291         REQ_(VIEW_PAGER,        "Show pager view"), \
292         \
293         REQ_GROUP("View manipulation") \
294         REQ_(ENTER,             "Enter current line and scroll"), \
295         REQ_(NEXT,              "Move to next"), \
296         REQ_(PREVIOUS,          "Move to previous"), \
297         REQ_(VIEW_NEXT,         "Move focus to next view"), \
298         REQ_(VIEW_CLOSE,        "Close the current view"), \
299         REQ_(QUIT,              "Close all views and quit"), \
300         \
301         REQ_GROUP("Cursor navigation") \
302         REQ_(MOVE_UP,           "Move cursor one line up"), \
303         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
304         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
305         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
306         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
307         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
308         \
309         REQ_GROUP("Scrolling") \
310         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
311         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
312         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
313         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
314         \
315         REQ_GROUP("Searching") \
316         REQ_(SEARCH,            "Search the view"), \
317         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
318         REQ_(FIND_NEXT,         "Find next search match"), \
319         REQ_(FIND_PREV,         "Find previous search match"), \
320         \
321         REQ_GROUP("Misc") \
322         REQ_(NONE,              "Do nothing"), \
323         REQ_(PROMPT,            "Bring up the prompt"), \
324         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
325         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
326         REQ_(SHOW_VERSION,      "Show version information"), \
327         REQ_(STOP_LOADING,      "Stop all loading views"), \
328         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
329         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
332 /* User action requests. */
333 enum request {
334 #define REQ_GROUP(help)
335 #define REQ_(req, help) REQ_##req
337         /* Offset all requests to avoid conflicts with ncurses getch values. */
338         REQ_OFFSET = KEY_MAX + 1,
339         REQ_INFO,
340         REQ_UNKNOWN,
342 #undef  REQ_GROUP
343 #undef  REQ_
344 };
346 struct request_info {
347         enum request request;
348         char *name;
349         int namelen;
350         char *help;
351 };
353 static struct request_info req_info[] = {
354 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
355 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
356         REQ_INFO
357 #undef  REQ_GROUP
358 #undef  REQ_
359 };
361 static enum request
362 get_request(const char *name)
364         int namelen = strlen(name);
365         int i;
367         for (i = 0; i < ARRAY_SIZE(req_info); i++)
368                 if (req_info[i].namelen == namelen &&
369                     !string_enum_compare(req_info[i].name, name, namelen))
370                         return req_info[i].request;
372         return REQ_UNKNOWN;
376 /*
377  * Options
378  */
380 static const char usage[] =
381 VERSION " (" __DATE__ ")\n"
382 "\n"
383 "Usage: tig [options]\n"
384 "   or: tig [options] [--] [git log options]\n"
385 "   or: tig [options] log  [git log options]\n"
386 "   or: tig [options] diff [git diff options]\n"
387 "   or: tig [options] show [git show options]\n"
388 "   or: tig [options] <    [git command output]\n"
389 "\n"
390 "Options:\n"
391 "  -l                          Start up in log view\n"
392 "  -d                          Start up in diff view\n"
393 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
394 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
395 "  --                          Mark end of tig options\n"
396 "  -v, --version               Show version and exit\n"
397 "  -h, --help                  Show help message and exit\n";
399 /* Option and state variables. */
400 static bool opt_line_number             = FALSE;
401 static bool opt_rev_graph               = TRUE;
402 static int opt_num_interval             = NUMBER_INTERVAL;
403 static int opt_tab_size                 = TABSIZE;
404 static enum request opt_request         = REQ_VIEW_MAIN;
405 static char opt_cmd[SIZEOF_STR]         = "";
406 static char opt_path[SIZEOF_STR]        = "";
407 static FILE *opt_pipe                   = NULL;
408 static char opt_encoding[20]            = "UTF-8";
409 static bool opt_utf8                    = TRUE;
410 static char opt_codeset[20]             = "UTF-8";
411 static iconv_t opt_iconv                = ICONV_NONE;
412 static char opt_search[SIZEOF_STR]      = "";
414 enum option_type {
415         OPT_NONE,
416         OPT_INT,
417 };
419 static bool
420 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
422         va_list args;
423         char *value = "";
424         int *number;
426         if (opt[0] != '-')
427                 return FALSE;
429         if (opt[1] == '-') {
430                 int namelen = strlen(name);
432                 opt += 2;
434                 if (strncmp(opt, name, namelen))
435                         return FALSE;
437                 if (opt[namelen] == '=')
438                         value = opt + namelen + 1;
440         } else {
441                 if (!short_name || opt[1] != short_name)
442                         return FALSE;
443                 value = opt + 2;
444         }
446         va_start(args, type);
447         if (type == OPT_INT) {
448                 number = va_arg(args, int *);
449                 if (isdigit(*value))
450                         *number = atoi(value);
451         }
452         va_end(args);
454         return TRUE;
457 /* Returns the index of log or diff command or -1 to exit. */
458 static bool
459 parse_options(int argc, char *argv[])
461         int i;
463         for (i = 1; i < argc; i++) {
464                 char *opt = argv[i];
466                 if (!strcmp(opt, "-l")) {
467                         opt_request = REQ_VIEW_LOG;
468                         continue;
469                 }
471                 if (!strcmp(opt, "-d")) {
472                         opt_request = REQ_VIEW_DIFF;
473                         continue;
474                 }
476                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
477                         opt_line_number = TRUE;
478                         continue;
479                 }
481                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
482                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
483                         continue;
484                 }
486                 if (check_option(opt, 'v', "version", OPT_NONE)) {
487                         printf("tig version %s\n", VERSION);
488                         return FALSE;
489                 }
491                 if (check_option(opt, 'h', "help", OPT_NONE)) {
492                         printf(usage);
493                         return FALSE;
494                 }
496                 if (!strcmp(opt, "--")) {
497                         i++;
498                         break;
499                 }
501                 if (!strcmp(opt, "log") ||
502                     !strcmp(opt, "diff") ||
503                     !strcmp(opt, "show")) {
504                         opt_request = opt[0] == 'l'
505                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
506                         break;
507                 }
509                 if (opt[0] && opt[0] != '-')
510                         break;
512                 die("unknown option '%s'\n\n%s", opt, usage);
513         }
515         if (!isatty(STDIN_FILENO)) {
516                 opt_request = REQ_VIEW_PAGER;
517                 opt_pipe = stdin;
519         } else if (i < argc) {
520                 size_t buf_size;
522                 if (opt_request == REQ_VIEW_MAIN)
523                         /* XXX: This is vulnerable to the user overriding
524                          * options required for the main view parser. */
525                         string_copy(opt_cmd, "git log --stat --pretty=raw");
526                 else
527                         string_copy(opt_cmd, "git");
528                 buf_size = strlen(opt_cmd);
530                 while (buf_size < sizeof(opt_cmd) && i < argc) {
531                         opt_cmd[buf_size++] = ' ';
532                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
533                 }
535                 if (buf_size >= sizeof(opt_cmd))
536                         die("command too long");
538                 opt_cmd[buf_size] = 0;
540         }
542         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
543                 opt_utf8 = FALSE;
545         return TRUE;
549 /*
550  * Line-oriented content detection.
551  */
553 #define LINE_INFO \
554 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
555 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
556 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
557 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
558 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
559 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
560 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
561 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
562 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
568 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
569 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
570 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
571 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
572 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
573 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
574 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
575 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
576 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
577 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
578 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
579 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
580 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
581 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
582 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
583 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
584 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
585 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
586 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
587 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
588 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
589 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
590 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
592 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
593 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
594 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
596 enum line_type {
597 #define LINE(type, line, fg, bg, attr) \
598         LINE_##type
599         LINE_INFO
600 #undef  LINE
601 };
603 struct line_info {
604         const char *name;       /* Option name. */
605         int namelen;            /* Size of option name. */
606         const char *line;       /* The start of line to match. */
607         int linelen;            /* Size of string to match. */
608         int fg, bg, attr;       /* Color and text attributes for the lines. */
609 };
611 static struct line_info line_info[] = {
612 #define LINE(type, line, fg, bg, attr) \
613         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
614         LINE_INFO
615 #undef  LINE
616 };
618 static enum line_type
619 get_line_type(char *line)
621         int linelen = strlen(line);
622         enum line_type type;
624         for (type = 0; type < ARRAY_SIZE(line_info); type++)
625                 /* Case insensitive search matches Signed-off-by lines better. */
626                 if (linelen >= line_info[type].linelen &&
627                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
628                         return type;
630         return LINE_DEFAULT;
633 static inline int
634 get_line_attr(enum line_type type)
636         assert(type < ARRAY_SIZE(line_info));
637         return COLOR_PAIR(type) | line_info[type].attr;
640 static struct line_info *
641 get_line_info(char *name, int namelen)
643         enum line_type type;
645         for (type = 0; type < ARRAY_SIZE(line_info); type++)
646                 if (namelen == line_info[type].namelen &&
647                     !string_enum_compare(line_info[type].name, name, namelen))
648                         return &line_info[type];
650         return NULL;
653 static void
654 init_colors(void)
656         int default_bg = COLOR_BLACK;
657         int default_fg = COLOR_WHITE;
658         enum line_type type;
660         start_color();
662         if (use_default_colors() != ERR) {
663                 default_bg = -1;
664                 default_fg = -1;
665         }
667         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
668                 struct line_info *info = &line_info[type];
669                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
670                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
672                 init_pair(type, fg, bg);
673         }
676 struct line {
677         enum line_type type;
679         /* State flags */
680         unsigned int selected:1;
682         void *data;             /* User data */
683 };
686 /*
687  * Keys
688  */
690 struct keybinding {
691         int alias;
692         enum request request;
693         struct keybinding *next;
694 };
696 static struct keybinding default_keybindings[] = {
697         /* View switching */
698         { 'm',          REQ_VIEW_MAIN },
699         { 'd',          REQ_VIEW_DIFF },
700         { 'l',          REQ_VIEW_LOG },
701         { 't',          REQ_VIEW_TREE },
702         { 'f',          REQ_VIEW_BLOB },
703         { 'p',          REQ_VIEW_PAGER },
704         { 'h',          REQ_VIEW_HELP },
706         /* View manipulation */
707         { 'q',          REQ_VIEW_CLOSE },
708         { KEY_TAB,      REQ_VIEW_NEXT },
709         { KEY_RETURN,   REQ_ENTER },
710         { KEY_UP,       REQ_PREVIOUS },
711         { KEY_DOWN,     REQ_NEXT },
713         /* Cursor navigation */
714         { 'k',          REQ_MOVE_UP },
715         { 'j',          REQ_MOVE_DOWN },
716         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
717         { KEY_END,      REQ_MOVE_LAST_LINE },
718         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
719         { ' ',          REQ_MOVE_PAGE_DOWN },
720         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
721         { 'b',          REQ_MOVE_PAGE_UP },
722         { '-',          REQ_MOVE_PAGE_UP },
724         /* Scrolling */
725         { KEY_IC,       REQ_SCROLL_LINE_UP },
726         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
727         { 'w',          REQ_SCROLL_PAGE_UP },
728         { 's',          REQ_SCROLL_PAGE_DOWN },
730         /* Searching */
731         { '/',          REQ_SEARCH },
732         { '?',          REQ_SEARCH_BACK },
733         { 'n',          REQ_FIND_NEXT },
734         { 'N',          REQ_FIND_PREV },
736         /* Misc */
737         { 'Q',          REQ_QUIT },
738         { 'z',          REQ_STOP_LOADING },
739         { 'v',          REQ_SHOW_VERSION },
740         { 'r',          REQ_SCREEN_REDRAW },
741         { '.',          REQ_TOGGLE_LINENO },
742         { 'g',          REQ_TOGGLE_REV_GRAPH },
743         { ':',          REQ_PROMPT },
745         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
746         { ERR,          REQ_NONE },
748         /* Using the ncurses SIGWINCH handler. */
749         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
750 };
752 #define KEYMAP_INFO \
753         KEYMAP_(GENERIC), \
754         KEYMAP_(MAIN), \
755         KEYMAP_(DIFF), \
756         KEYMAP_(LOG), \
757         KEYMAP_(TREE), \
758         KEYMAP_(BLOB), \
759         KEYMAP_(PAGER), \
760         KEYMAP_(HELP) \
762 enum keymap {
763 #define KEYMAP_(name) KEYMAP_##name
764         KEYMAP_INFO
765 #undef  KEYMAP_
766 };
768 static struct int_map keymap_table[] = {
769 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
770         KEYMAP_INFO
771 #undef  KEYMAP_
772 };
774 #define set_keymap(map, name) \
775         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
777 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
779 static void
780 add_keybinding(enum keymap keymap, enum request request, int key)
782         struct keybinding *keybinding;
784         keybinding = calloc(1, sizeof(*keybinding));
785         if (!keybinding)
786                 die("Failed to allocate keybinding");
788         keybinding->alias = key;
789         keybinding->request = request;
790         keybinding->next = keybindings[keymap];
791         keybindings[keymap] = keybinding;
794 /* Looks for a key binding first in the given map, then in the generic map, and
795  * lastly in the default keybindings. */
796 static enum request
797 get_keybinding(enum keymap keymap, int key)
799         struct keybinding *kbd;
800         int i;
802         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
803                 if (kbd->alias == key)
804                         return kbd->request;
806         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
807                 if (kbd->alias == key)
808                         return kbd->request;
810         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
811                 if (default_keybindings[i].alias == key)
812                         return default_keybindings[i].request;
814         return (enum request) key;
818 struct key {
819         char *name;
820         int value;
821 };
823 static struct key key_table[] = {
824         { "Enter",      KEY_RETURN },
825         { "Space",      ' ' },
826         { "Backspace",  KEY_BACKSPACE },
827         { "Tab",        KEY_TAB },
828         { "Escape",     KEY_ESC },
829         { "Left",       KEY_LEFT },
830         { "Right",      KEY_RIGHT },
831         { "Up",         KEY_UP },
832         { "Down",       KEY_DOWN },
833         { "Insert",     KEY_IC },
834         { "Delete",     KEY_DC },
835         { "Hash",       '#' },
836         { "Home",       KEY_HOME },
837         { "End",        KEY_END },
838         { "PageUp",     KEY_PPAGE },
839         { "PageDown",   KEY_NPAGE },
840         { "F1",         KEY_F(1) },
841         { "F2",         KEY_F(2) },
842         { "F3",         KEY_F(3) },
843         { "F4",         KEY_F(4) },
844         { "F5",         KEY_F(5) },
845         { "F6",         KEY_F(6) },
846         { "F7",         KEY_F(7) },
847         { "F8",         KEY_F(8) },
848         { "F9",         KEY_F(9) },
849         { "F10",        KEY_F(10) },
850         { "F11",        KEY_F(11) },
851         { "F12",        KEY_F(12) },
852 };
854 static int
855 get_key_value(const char *name)
857         int i;
859         for (i = 0; i < ARRAY_SIZE(key_table); i++)
860                 if (!strcasecmp(key_table[i].name, name))
861                         return key_table[i].value;
863         if (strlen(name) == 1 && isprint(*name))
864                 return (int) *name;
866         return ERR;
869 static char *
870 get_key(enum request request)
872         static char buf[BUFSIZ];
873         static char key_char[] = "'X'";
874         size_t pos = 0;
875         char *sep = "    ";
876         int i;
878         buf[pos] = 0;
880         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
881                 struct keybinding *keybinding = &default_keybindings[i];
882                 char *seq = NULL;
883                 int key;
885                 if (keybinding->request != request)
886                         continue;
888                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
889                         if (key_table[key].value == keybinding->alias)
890                                 seq = key_table[key].name;
892                 if (seq == NULL &&
893                     keybinding->alias < 127 &&
894                     isprint(keybinding->alias)) {
895                         key_char[1] = (char) keybinding->alias;
896                         seq = key_char;
897                 }
899                 if (!seq)
900                         seq = "'?'";
902                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
903                         return "Too many keybindings!";
904                 sep = ", ";
905         }
907         return buf;
911 /*
912  * User config file handling.
913  */
915 static struct int_map color_map[] = {
916 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
917         COLOR_MAP(DEFAULT),
918         COLOR_MAP(BLACK),
919         COLOR_MAP(BLUE),
920         COLOR_MAP(CYAN),
921         COLOR_MAP(GREEN),
922         COLOR_MAP(MAGENTA),
923         COLOR_MAP(RED),
924         COLOR_MAP(WHITE),
925         COLOR_MAP(YELLOW),
926 };
928 #define set_color(color, name) \
929         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
931 static struct int_map attr_map[] = {
932 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
933         ATTR_MAP(NORMAL),
934         ATTR_MAP(BLINK),
935         ATTR_MAP(BOLD),
936         ATTR_MAP(DIM),
937         ATTR_MAP(REVERSE),
938         ATTR_MAP(STANDOUT),
939         ATTR_MAP(UNDERLINE),
940 };
942 #define set_attribute(attr, name) \
943         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
945 static int   config_lineno;
946 static bool  config_errors;
947 static char *config_msg;
949 /* Wants: object fgcolor bgcolor [attr] */
950 static int
951 option_color_command(int argc, char *argv[])
953         struct line_info *info;
955         if (argc != 3 && argc != 4) {
956                 config_msg = "Wrong number of arguments given to color command";
957                 return ERR;
958         }
960         info = get_line_info(argv[0], strlen(argv[0]));
961         if (!info) {
962                 config_msg = "Unknown color name";
963                 return ERR;
964         }
966         if (set_color(&info->fg, argv[1]) == ERR ||
967             set_color(&info->bg, argv[2]) == ERR) {
968                 config_msg = "Unknown color";
969                 return ERR;
970         }
972         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
973                 config_msg = "Unknown attribute";
974                 return ERR;
975         }
977         return OK;
980 /* Wants: name = value */
981 static int
982 option_set_command(int argc, char *argv[])
984         if (argc != 3) {
985                 config_msg = "Wrong number of arguments given to set command";
986                 return ERR;
987         }
989         if (strcmp(argv[1], "=")) {
990                 config_msg = "No value assigned";
991                 return ERR;
992         }
994         if (!strcmp(argv[0], "show-rev-graph")) {
995                 opt_rev_graph = (!strcmp(argv[2], "1") ||
996                                  !strcmp(argv[2], "true") ||
997                                  !strcmp(argv[2], "yes"));
998                 return OK;
999         }
1001         if (!strcmp(argv[0], "line-number-interval")) {
1002                 opt_num_interval = atoi(argv[2]);
1003                 return OK;
1004         }
1006         if (!strcmp(argv[0], "tab-size")) {
1007                 opt_tab_size = atoi(argv[2]);
1008                 return OK;
1009         }
1011         if (!strcmp(argv[0], "commit-encoding")) {
1012                 char *arg = argv[2];
1013                 int delimiter = *arg;
1014                 int i;
1016                 switch (delimiter) {
1017                 case '"':
1018                 case '\'':
1019                         for (arg++, i = 0; arg[i]; i++)
1020                                 if (arg[i] == delimiter) {
1021                                         arg[i] = 0;
1022                                         break;
1023                                 }
1024                 default:
1025                         string_copy(opt_encoding, arg);
1026                         return OK;
1027                 }
1028         }
1030         config_msg = "Unknown variable name";
1031         return ERR;
1034 /* Wants: mode request key */
1035 static int
1036 option_bind_command(int argc, char *argv[])
1038         enum request request;
1039         int keymap;
1040         int key;
1042         if (argc != 3) {
1043                 config_msg = "Wrong number of arguments given to bind command";
1044                 return ERR;
1045         }
1047         if (set_keymap(&keymap, argv[0]) == ERR) {
1048                 config_msg = "Unknown key map";
1049                 return ERR;
1050         }
1052         key = get_key_value(argv[1]);
1053         if (key == ERR) {
1054                 config_msg = "Unknown key";
1055                 return ERR;
1056         }
1058         request = get_request(argv[2]);
1059         if (request == REQ_UNKNOWN) {
1060                 config_msg = "Unknown request name";
1061                 return ERR;
1062         }
1064         add_keybinding(keymap, request, key);
1066         return OK;
1069 static int
1070 set_option(char *opt, char *value)
1072         char *argv[16];
1073         int valuelen;
1074         int argc = 0;
1076         /* Tokenize */
1077         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1078                 argv[argc++] = value;
1080                 value += valuelen;
1081                 if (!*value)
1082                         break;
1084                 *value++ = 0;
1085                 while (isspace(*value))
1086                         value++;
1087         }
1089         if (!strcmp(opt, "color"))
1090                 return option_color_command(argc, argv);
1092         if (!strcmp(opt, "set"))
1093                 return option_set_command(argc, argv);
1095         if (!strcmp(opt, "bind"))
1096                 return option_bind_command(argc, argv);
1098         config_msg = "Unknown option command";
1099         return ERR;
1102 static int
1103 read_option(char *opt, int optlen, char *value, int valuelen)
1105         int status = OK;
1107         config_lineno++;
1108         config_msg = "Internal error";
1110         /* Check for comment markers, since read_properties() will
1111          * only ensure opt and value are split at first " \t". */
1112         optlen = strcspn(opt, "#");
1113         if (optlen == 0)
1114                 return OK;
1116         if (opt[optlen] != 0) {
1117                 config_msg = "No option value";
1118                 status = ERR;
1120         }  else {
1121                 /* Look for comment endings in the value. */
1122                 int len = strcspn(value, "#");
1124                 if (len < valuelen) {
1125                         valuelen = len;
1126                         value[valuelen] = 0;
1127                 }
1129                 status = set_option(opt, value);
1130         }
1132         if (status == ERR) {
1133                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1134                         config_lineno, optlen, opt, config_msg);
1135                 config_errors = TRUE;
1136         }
1138         /* Always keep going if errors are encountered. */
1139         return OK;
1142 static int
1143 load_options(void)
1145         char *home = getenv("HOME");
1146         char buf[SIZEOF_STR];
1147         FILE *file;
1149         config_lineno = 0;
1150         config_errors = FALSE;
1152         if (!home || !string_format(buf, "%s/.tigrc", home))
1153                 return ERR;
1155         /* It's ok that the file doesn't exist. */
1156         file = fopen(buf, "r");
1157         if (!file)
1158                 return OK;
1160         if (read_properties(file, " \t", read_option) == ERR ||
1161             config_errors == TRUE)
1162                 fprintf(stderr, "Errors while loading %s.\n", buf);
1164         return OK;
1168 /*
1169  * The viewer
1170  */
1172 struct view;
1173 struct view_ops;
1175 /* The display array of active views and the index of the current view. */
1176 static struct view *display[2];
1177 static unsigned int current_view;
1179 #define foreach_displayed_view(view, i) \
1180         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1182 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1184 /* Current head and commit ID */
1185 static char ref_blob[SIZEOF_REF]        = "";
1186 static char ref_commit[SIZEOF_REF]      = "HEAD";
1187 static char ref_head[SIZEOF_REF]        = "HEAD";
1189 struct view {
1190         const char *name;       /* View name */
1191         const char *cmd_fmt;    /* Default command line format */
1192         const char *cmd_env;    /* Command line set via environment */
1193         const char *id;         /* Points to either of ref_{head,commit,blob} */
1195         struct view_ops *ops;   /* View operations */
1197         enum keymap keymap;     /* What keymap does this view have */
1199         char cmd[SIZEOF_STR];   /* Command buffer */
1200         char ref[SIZEOF_REF];   /* Hovered commit reference */
1201         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1203         int height, width;      /* The width and height of the main window */
1204         WINDOW *win;            /* The main window */
1205         WINDOW *title;          /* The title window living below the main window */
1207         /* Navigation */
1208         unsigned long offset;   /* Offset of the window top */
1209         unsigned long lineno;   /* Current line number */
1211         /* Searching */
1212         char grep[SIZEOF_STR];  /* Search string */
1213         regex_t *regex;         /* Pre-compiled regex */
1215         /* If non-NULL, points to the view that opened this view. If this view
1216          * is closed tig will switch back to the parent view. */
1217         struct view *parent;
1219         /* Buffering */
1220         unsigned long lines;    /* Total number of lines */
1221         struct line *line;      /* Line index */
1222         unsigned long line_size;/* Total number of allocated lines */
1223         unsigned int digits;    /* Number of digits in the lines member. */
1225         /* Loading */
1226         FILE *pipe;
1227         time_t start_time;
1228 };
1230 struct view_ops {
1231         /* What type of content being displayed. Used in the title bar. */
1232         const char *type;
1233         /* Draw one line; @lineno must be < view->height. */
1234         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1235         /* Read one line; updates view->line. */
1236         bool (*read)(struct view *view, char *data);
1237         /* Depending on view, change display based on current line. */
1238         bool (*enter)(struct view *view, struct line *line);
1239         /* Search for regex in a line. */
1240         bool (*grep)(struct view *view, struct line *line);
1241         /* Select line */
1242         void (*select)(struct view *view, struct line *line);
1243 };
1245 static struct view_ops pager_ops;
1246 static struct view_ops main_ops;
1247 static struct view_ops tree_ops;
1248 static struct view_ops blob_ops;
1250 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1251         { name, cmd, #env, ref, ops, map}
1253 #define VIEW_(id, name, ops, ref) \
1254         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1257 static struct view views[] = {
1258         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1259         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1260         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1261         VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
1262         VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
1263         VIEW_(HELP,  "help",  &pager_ops, "static"),
1264         VIEW_(PAGER, "pager", &pager_ops, "static"),
1265 };
1267 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1269 #define foreach_view(view, i) \
1270         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1272 #define view_is_displayed(view) \
1273         (view == display[0] || view == display[1])
1275 static bool
1276 draw_view_line(struct view *view, unsigned int lineno)
1278         struct line *line;
1279         bool selected = (view->offset + lineno == view->lineno);
1281         assert(view_is_displayed(view));
1283         if (view->offset + lineno >= view->lines)
1284                 return FALSE;
1286         line = &view->line[view->offset + lineno];
1288         if (selected) {
1289                 line->selected = TRUE;
1290                 view->ops->select(view, line);
1291         } else if (line->selected) {
1292                 line->selected = FALSE;
1293                 wmove(view->win, lineno, 0);
1294                 wclrtoeol(view->win);
1295         }
1297         return view->ops->draw(view, line, lineno, selected);
1300 static void
1301 redraw_view_from(struct view *view, int lineno)
1303         assert(0 <= lineno && lineno < view->height);
1305         for (; lineno < view->height; lineno++) {
1306                 if (!draw_view_line(view, lineno))
1307                         break;
1308         }
1310         redrawwin(view->win);
1311         wrefresh(view->win);
1314 static void
1315 redraw_view(struct view *view)
1317         wclear(view->win);
1318         redraw_view_from(view, 0);
1322 static void
1323 update_view_title(struct view *view)
1325         assert(view_is_displayed(view));
1327         if (view == display[current_view])
1328                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1329         else
1330                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1332         werase(view->title);
1333         wmove(view->title, 0, 0);
1335         if (*view->ref)
1336                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1337         else
1338                 wprintw(view->title, "[%s]", view->name);
1340         if (view->lines || view->pipe) {
1341                 unsigned int view_lines = view->offset + view->height;
1342                 unsigned int lines = view->lines
1343                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1344                                    : 0;
1346                 wprintw(view->title, " - %s %d of %d (%d%%)",
1347                         view->ops->type,
1348                         view->lineno + 1,
1349                         view->lines,
1350                         lines);
1351         }
1353         if (view->pipe) {
1354                 time_t secs = time(NULL) - view->start_time;
1356                 /* Three git seconds are a long time ... */
1357                 if (secs > 2)
1358                         wprintw(view->title, " %lds", secs);
1359         }
1361         wmove(view->title, 0, view->width - 1);
1362         wrefresh(view->title);
1365 static void
1366 resize_display(void)
1368         int offset, i;
1369         struct view *base = display[0];
1370         struct view *view = display[1] ? display[1] : display[0];
1372         /* Setup window dimensions */
1374         getmaxyx(stdscr, base->height, base->width);
1376         /* Make room for the status window. */
1377         base->height -= 1;
1379         if (view != base) {
1380                 /* Horizontal split. */
1381                 view->width   = base->width;
1382                 view->height  = SCALE_SPLIT_VIEW(base->height);
1383                 base->height -= view->height;
1385                 /* Make room for the title bar. */
1386                 view->height -= 1;
1387         }
1389         /* Make room for the title bar. */
1390         base->height -= 1;
1392         offset = 0;
1394         foreach_displayed_view (view, i) {
1395                 if (!view->win) {
1396                         view->win = newwin(view->height, 0, offset, 0);
1397                         if (!view->win)
1398                                 die("Failed to create %s view", view->name);
1400                         scrollok(view->win, TRUE);
1402                         view->title = newwin(1, 0, offset + view->height, 0);
1403                         if (!view->title)
1404                                 die("Failed to create title window");
1406                 } else {
1407                         wresize(view->win, view->height, view->width);
1408                         mvwin(view->win,   offset, 0);
1409                         mvwin(view->title, offset + view->height, 0);
1410                 }
1412                 offset += view->height + 1;
1413         }
1416 static void
1417 redraw_display(void)
1419         struct view *view;
1420         int i;
1422         foreach_displayed_view (view, i) {
1423                 redraw_view(view);
1424                 update_view_title(view);
1425         }
1428 static void
1429 update_display_cursor(void)
1431         struct view *view = display[current_view];
1433         /* Move the cursor to the right-most column of the cursor line.
1434          *
1435          * XXX: This could turn out to be a bit expensive, but it ensures that
1436          * the cursor does not jump around. */
1437         if (view->lines) {
1438                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1439                 wrefresh(view->win);
1440         }
1443 /*
1444  * Navigation
1445  */
1447 /* Scrolling backend */
1448 static void
1449 do_scroll_view(struct view *view, int lines)
1451         bool redraw_current_line = FALSE;
1453         /* The rendering expects the new offset. */
1454         view->offset += lines;
1456         assert(0 <= view->offset && view->offset < view->lines);
1457         assert(lines);
1459         /* Move current line into the view. */
1460         if (view->lineno < view->offset) {
1461                 view->lineno = view->offset;
1462                 redraw_current_line = TRUE;
1463         } else if (view->lineno >= view->offset + view->height) {
1464                 view->lineno = view->offset + view->height - 1;
1465                 redraw_current_line = TRUE;
1466         }
1468         assert(view->offset <= view->lineno && view->lineno < view->lines);
1470         /* Redraw the whole screen if scrolling is pointless. */
1471         if (view->height < ABS(lines)) {
1472                 redraw_view(view);
1474         } else {
1475                 int line = lines > 0 ? view->height - lines : 0;
1476                 int end = line + ABS(lines);
1478                 wscrl(view->win, lines);
1480                 for (; line < end; line++) {
1481                         if (!draw_view_line(view, line))
1482                                 break;
1483                 }
1485                 if (redraw_current_line)
1486                         draw_view_line(view, view->lineno - view->offset);
1487         }
1489         redrawwin(view->win);
1490         wrefresh(view->win);
1491         report("");
1494 /* Scroll frontend */
1495 static void
1496 scroll_view(struct view *view, enum request request)
1498         int lines = 1;
1500         assert(view_is_displayed(view));
1502         switch (request) {
1503         case REQ_SCROLL_PAGE_DOWN:
1504                 lines = view->height;
1505         case REQ_SCROLL_LINE_DOWN:
1506                 if (view->offset + lines > view->lines)
1507                         lines = view->lines - view->offset;
1509                 if (lines == 0 || view->offset + view->height >= view->lines) {
1510                         report("Cannot scroll beyond the last line");
1511                         return;
1512                 }
1513                 break;
1515         case REQ_SCROLL_PAGE_UP:
1516                 lines = view->height;
1517         case REQ_SCROLL_LINE_UP:
1518                 if (lines > view->offset)
1519                         lines = view->offset;
1521                 if (lines == 0) {
1522                         report("Cannot scroll beyond the first line");
1523                         return;
1524                 }
1526                 lines = -lines;
1527                 break;
1529         default:
1530                 die("request %d not handled in switch", request);
1531         }
1533         do_scroll_view(view, lines);
1536 /* Cursor moving */
1537 static void
1538 move_view(struct view *view, enum request request)
1540         int scroll_steps = 0;
1541         int steps;
1543         switch (request) {
1544         case REQ_MOVE_FIRST_LINE:
1545                 steps = -view->lineno;
1546                 break;
1548         case REQ_MOVE_LAST_LINE:
1549                 steps = view->lines - view->lineno - 1;
1550                 break;
1552         case REQ_MOVE_PAGE_UP:
1553                 steps = view->height > view->lineno
1554                       ? -view->lineno : -view->height;
1555                 break;
1557         case REQ_MOVE_PAGE_DOWN:
1558                 steps = view->lineno + view->height >= view->lines
1559                       ? view->lines - view->lineno - 1 : view->height;
1560                 break;
1562         case REQ_MOVE_UP:
1563                 steps = -1;
1564                 break;
1566         case REQ_MOVE_DOWN:
1567                 steps = 1;
1568                 break;
1570         default:
1571                 die("request %d not handled in switch", request);
1572         }
1574         if (steps <= 0 && view->lineno == 0) {
1575                 report("Cannot move beyond the first line");
1576                 return;
1578         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1579                 report("Cannot move beyond the last line");
1580                 return;
1581         }
1583         /* Move the current line */
1584         view->lineno += steps;
1585         assert(0 <= view->lineno && view->lineno < view->lines);
1587         /* Check whether the view needs to be scrolled */
1588         if (view->lineno < view->offset ||
1589             view->lineno >= view->offset + view->height) {
1590                 scroll_steps = steps;
1591                 if (steps < 0 && -steps > view->offset) {
1592                         scroll_steps = -view->offset;
1594                 } else if (steps > 0) {
1595                         if (view->lineno == view->lines - 1 &&
1596                             view->lines > view->height) {
1597                                 scroll_steps = view->lines - view->offset - 1;
1598                                 if (scroll_steps >= view->height)
1599                                         scroll_steps -= view->height - 1;
1600                         }
1601                 }
1602         }
1604         if (!view_is_displayed(view)) {
1605                 view->offset += steps;
1606                 view->ops->select(view, &view->line[view->lineno]);
1607                 return;
1608         }
1610         /* Repaint the old "current" line if we be scrolling */
1611         if (ABS(steps) < view->height)
1612                 draw_view_line(view, view->lineno - steps - view->offset);
1614         if (scroll_steps) {
1615                 do_scroll_view(view, scroll_steps);
1616                 return;
1617         }
1619         /* Draw the current line */
1620         draw_view_line(view, view->lineno - view->offset);
1622         redrawwin(view->win);
1623         wrefresh(view->win);
1624         report("");
1628 /*
1629  * Searching
1630  */
1632 static void search_view(struct view *view, enum request request);
1634 static bool
1635 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1637         assert(view_is_displayed(view));
1639         if (!view->ops->grep(view, line))
1640                 return FALSE;
1642         if (lineno - view->offset >= view->height) {
1643                 view->offset = lineno;
1644                 view->lineno = lineno;
1645                 redraw_view(view);
1647         } else {
1648                 unsigned long old_lineno = view->lineno - view->offset;
1650                 view->lineno = lineno;
1651                 draw_view_line(view, old_lineno);
1653                 draw_view_line(view, view->lineno - view->offset);
1654                 redrawwin(view->win);
1655                 wrefresh(view->win);
1656         }
1658         report("Line %ld matches '%s'", lineno + 1, view->grep);
1659         return TRUE;
1662 static void
1663 find_next(struct view *view, enum request request)
1665         unsigned long lineno = view->lineno;
1666         int direction;
1668         if (!*view->grep) {
1669                 if (!*opt_search)
1670                         report("No previous search");
1671                 else
1672                         search_view(view, request);
1673                 return;
1674         }
1676         switch (request) {
1677         case REQ_SEARCH:
1678         case REQ_FIND_NEXT:
1679                 direction = 1;
1680                 break;
1682         case REQ_SEARCH_BACK:
1683         case REQ_FIND_PREV:
1684                 direction = -1;
1685                 break;
1687         default:
1688                 return;
1689         }
1691         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1692                 lineno += direction;
1694         /* Note, lineno is unsigned long so will wrap around in which case it
1695          * will become bigger than view->lines. */
1696         for (; lineno < view->lines; lineno += direction) {
1697                 struct line *line = &view->line[lineno];
1699                 if (find_next_line(view, lineno, line))
1700                         return;
1701         }
1703         report("No match found for '%s'", view->grep);
1706 static void
1707 search_view(struct view *view, enum request request)
1709         int regex_err;
1711         if (view->regex) {
1712                 regfree(view->regex);
1713                 *view->grep = 0;
1714         } else {
1715                 view->regex = calloc(1, sizeof(*view->regex));
1716                 if (!view->regex)
1717                         return;
1718         }
1720         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1721         if (regex_err != 0) {
1722                 char buf[SIZEOF_STR] = "unknown error";
1724                 regerror(regex_err, view->regex, buf, sizeof(buf));
1725                 report("Search failed: %s", buf);
1726                 return;
1727         }
1729         string_copy(view->grep, opt_search);
1731         find_next(view, request);
1734 /*
1735  * Incremental updating
1736  */
1738 static void
1739 end_update(struct view *view)
1741         if (!view->pipe)
1742                 return;
1743         set_nonblocking_input(FALSE);
1744         if (view->pipe == stdin)
1745                 fclose(view->pipe);
1746         else
1747                 pclose(view->pipe);
1748         view->pipe = NULL;
1751 static bool
1752 begin_update(struct view *view)
1754         const char *id = view->id;
1756         if (view->pipe)
1757                 end_update(view);
1759         if (opt_cmd[0]) {
1760                 string_copy(view->cmd, opt_cmd);
1761                 opt_cmd[0] = 0;
1762                 /* When running random commands, the view ref could have become
1763                  * invalid so clear it. */
1764                 view->ref[0] = 0;
1766         } else if (view == VIEW(REQ_VIEW_TREE)) {
1767                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1769                 if (strcmp(view->vid, view->id))
1770                         opt_path[0] = 0;
1772                 if (!string_format(view->cmd, format, id, opt_path))
1773                         return FALSE;
1775         } else {
1776                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1778                 if (!string_format(view->cmd, format, id, id, id, id, id))
1779                         return FALSE;
1780         }
1782         /* Special case for the pager view. */
1783         if (opt_pipe) {
1784                 view->pipe = opt_pipe;
1785                 opt_pipe = NULL;
1786         } else {
1787                 view->pipe = popen(view->cmd, "r");
1788         }
1790         if (!view->pipe)
1791                 return FALSE;
1793         set_nonblocking_input(TRUE);
1795         view->offset = 0;
1796         view->lines  = 0;
1797         view->lineno = 0;
1798         string_copy(view->vid, id);
1800         if (view->line) {
1801                 int i;
1803                 for (i = 0; i < view->lines; i++)
1804                         if (view->line[i].data)
1805                                 free(view->line[i].data);
1807                 free(view->line);
1808                 view->line = NULL;
1809         }
1811         view->start_time = time(NULL);
1813         return TRUE;
1816 static struct line *
1817 realloc_lines(struct view *view, size_t line_size)
1819         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1821         if (!tmp)
1822                 return NULL;
1824         view->line = tmp;
1825         view->line_size = line_size;
1826         return view->line;
1829 static bool
1830 update_view(struct view *view)
1832         char in_buffer[BUFSIZ];
1833         char out_buffer[BUFSIZ * 2];
1834         char *line;
1835         /* The number of lines to read. If too low it will cause too much
1836          * redrawing (and possible flickering), if too high responsiveness
1837          * will suffer. */
1838         unsigned long lines = view->height;
1839         int redraw_from = -1;
1841         if (!view->pipe)
1842                 return TRUE;
1844         /* Only redraw if lines are visible. */
1845         if (view->offset + view->height >= view->lines)
1846                 redraw_from = view->lines - view->offset;
1848         /* FIXME: This is probably not perfect for backgrounded views. */
1849         if (!realloc_lines(view, view->lines + lines))
1850                 goto alloc_error;
1852         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1853                 size_t linelen = strlen(line);
1855                 if (linelen)
1856                         line[linelen - 1] = 0;
1858                 if (opt_iconv != ICONV_NONE) {
1859                         char *inbuf = line;
1860                         size_t inlen = linelen;
1862                         char *outbuf = out_buffer;
1863                         size_t outlen = sizeof(out_buffer);
1865                         size_t ret;
1867                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1868                         if (ret != (size_t) -1) {
1869                                 line = out_buffer;
1870                                 linelen = strlen(out_buffer);
1871                         }
1872                 }
1874                 if (!view->ops->read(view, line))
1875                         goto alloc_error;
1877                 if (lines-- == 1)
1878                         break;
1879         }
1881         {
1882                 int digits;
1884                 lines = view->lines;
1885                 for (digits = 0; lines; digits++)
1886                         lines /= 10;
1888                 /* Keep the displayed view in sync with line number scaling. */
1889                 if (digits != view->digits) {
1890                         view->digits = digits;
1891                         redraw_from = 0;
1892                 }
1893         }
1895         if (!view_is_displayed(view))
1896                 goto check_pipe;
1898         if (view == VIEW(REQ_VIEW_TREE)) {
1899                 /* Clear the view and redraw everything since the tree sorting
1900                  * might have rearranged things. */
1901                 redraw_view(view);
1903         } else if (redraw_from >= 0) {
1904                 /* If this is an incremental update, redraw the previous line
1905                  * since for commits some members could have changed when
1906                  * loading the main view. */
1907                 if (redraw_from > 0)
1908                         redraw_from--;
1910                 /* Incrementally draw avoids flickering. */
1911                 redraw_view_from(view, redraw_from);
1912         }
1914         /* Update the title _after_ the redraw so that if the redraw picks up a
1915          * commit reference in view->ref it'll be available here. */
1916         update_view_title(view);
1918 check_pipe:
1919         if (ferror(view->pipe)) {
1920                 report("Failed to read: %s", strerror(errno));
1921                 goto end;
1923         } else if (feof(view->pipe)) {
1924                 report("");
1925                 goto end;
1926         }
1928         return TRUE;
1930 alloc_error:
1931         report("Allocation failure");
1933 end:
1934         end_update(view);
1935         return FALSE;
1939 /*
1940  * View opening
1941  */
1943 static void open_help_view(struct view *view)
1945         char buf[BUFSIZ];
1946         int lines = ARRAY_SIZE(req_info) + 2;
1947         int i;
1949         if (view->lines > 0)
1950                 return;
1952         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1953                 if (!req_info[i].request)
1954                         lines++;
1956         view->line = calloc(lines, sizeof(*view->line));
1957         if (!view->line) {
1958                 report("Allocation failure");
1959                 return;
1960         }
1962         view->ops->read(view, "Quick reference for tig keybindings:");
1964         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1965                 char *key;
1967                 if (!req_info[i].request) {
1968                         view->ops->read(view, "");
1969                         view->ops->read(view, req_info[i].help);
1970                         continue;
1971                 }
1973                 key = get_key(req_info[i].request);
1974                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1975                         continue;
1977                 view->ops->read(view, buf);
1978         }
1981 enum open_flags {
1982         OPEN_DEFAULT = 0,       /* Use default view switching. */
1983         OPEN_SPLIT = 1,         /* Split current view. */
1984         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1985         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1986 };
1988 static void
1989 open_view(struct view *prev, enum request request, enum open_flags flags)
1991         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1992         bool split = !!(flags & OPEN_SPLIT);
1993         bool reload = !!(flags & OPEN_RELOAD);
1994         struct view *view = VIEW(request);
1995         int nviews = displayed_views();
1996         struct view *base_view = display[0];
1998         if (view == prev && nviews == 1 && !reload) {
1999                 report("Already in %s view", view->name);
2000                 return;
2001         }
2003         if (view == VIEW(REQ_VIEW_HELP)) {
2004                 open_help_view(view);
2006         } else if ((reload || strcmp(view->vid, view->id)) &&
2007                    !begin_update(view)) {
2008                 report("Failed to load %s view", view->name);
2009                 return;
2010         }
2012         if (split) {
2013                 display[1] = view;
2014                 if (!backgrounded)
2015                         current_view = 1;
2016         } else {
2017                 /* Maximize the current view. */
2018                 memset(display, 0, sizeof(display));
2019                 current_view = 0;
2020                 display[current_view] = view;
2021         }
2023         /* Resize the view when switching between split- and full-screen,
2024          * or when switching between two different full-screen views. */
2025         if (nviews != displayed_views() ||
2026             (nviews == 1 && base_view != display[0]))
2027                 resize_display();
2029         if (split && prev->lineno - prev->offset >= prev->height) {
2030                 /* Take the title line into account. */
2031                 int lines = prev->lineno - prev->offset - prev->height + 1;
2033                 /* Scroll the view that was split if the current line is
2034                  * outside the new limited view. */
2035                 do_scroll_view(prev, lines);
2036         }
2038         if (prev && view != prev) {
2039                 if (split && !backgrounded) {
2040                         /* "Blur" the previous view. */
2041                         update_view_title(prev);
2042                 }
2044                 view->parent = prev;
2045         }
2047         if (view->pipe && view->lines == 0) {
2048                 /* Clear the old view and let the incremental updating refill
2049                  * the screen. */
2050                 wclear(view->win);
2051                 report("");
2052         } else {
2053                 redraw_view(view);
2054                 report("");
2055         }
2057         /* If the view is backgrounded the above calls to report()
2058          * won't redraw the view title. */
2059         if (backgrounded)
2060                 update_view_title(view);
2064 /*
2065  * User request switch noodle
2066  */
2068 static int
2069 view_driver(struct view *view, enum request request)
2071         int i;
2073         switch (request) {
2074         case REQ_MOVE_UP:
2075         case REQ_MOVE_DOWN:
2076         case REQ_MOVE_PAGE_UP:
2077         case REQ_MOVE_PAGE_DOWN:
2078         case REQ_MOVE_FIRST_LINE:
2079         case REQ_MOVE_LAST_LINE:
2080                 move_view(view, request);
2081                 break;
2083         case REQ_SCROLL_LINE_DOWN:
2084         case REQ_SCROLL_LINE_UP:
2085         case REQ_SCROLL_PAGE_DOWN:
2086         case REQ_SCROLL_PAGE_UP:
2087                 scroll_view(view, request);
2088                 break;
2090         case REQ_VIEW_BLOB:
2091                 if (!ref_blob[0]) {
2092                         report("No file chosen, press 't' to open tree view");
2093                         break;
2094                 }
2095                 /* Fall-through */
2096         case REQ_VIEW_MAIN:
2097         case REQ_VIEW_DIFF:
2098         case REQ_VIEW_LOG:
2099         case REQ_VIEW_TREE:
2100         case REQ_VIEW_HELP:
2101         case REQ_VIEW_PAGER:
2102                 open_view(view, request, OPEN_DEFAULT);
2103                 break;
2105         case REQ_NEXT:
2106         case REQ_PREVIOUS:
2107                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2109                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2110                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2111                    (view == VIEW(REQ_VIEW_BLOB) &&
2112                      view->parent == VIEW(REQ_VIEW_TREE))) {
2113                         view = view->parent;
2114                         move_view(view, request);
2115                         if (view_is_displayed(view))
2116                                 update_view_title(view);
2117                 } else {
2118                         move_view(view, request);
2119                         break;
2120                 }
2121                 /* Fall-through */
2123         case REQ_ENTER:
2124                 if (!view->lines) {
2125                         report("Nothing to enter");
2126                         break;
2127                 }
2128                 return view->ops->enter(view, &view->line[view->lineno]);
2130         case REQ_VIEW_NEXT:
2131         {
2132                 int nviews = displayed_views();
2133                 int next_view = (current_view + 1) % nviews;
2135                 if (next_view == current_view) {
2136                         report("Only one view is displayed");
2137                         break;
2138                 }
2140                 current_view = next_view;
2141                 /* Blur out the title of the previous view. */
2142                 update_view_title(view);
2143                 report("");
2144                 break;
2145         }
2146         case REQ_TOGGLE_LINENO:
2147                 opt_line_number = !opt_line_number;
2148                 redraw_display();
2149                 break;
2151         case REQ_TOGGLE_REV_GRAPH:
2152                 opt_rev_graph = !opt_rev_graph;
2153                 redraw_display();
2154                 break;
2156         case REQ_PROMPT:
2157                 /* Always reload^Wrerun commands from the prompt. */
2158                 open_view(view, opt_request, OPEN_RELOAD);
2159                 break;
2161         case REQ_SEARCH:
2162         case REQ_SEARCH_BACK:
2163                 search_view(view, request);
2164                 break;
2166         case REQ_FIND_NEXT:
2167         case REQ_FIND_PREV:
2168                 find_next(view, request);
2169                 break;
2171         case REQ_STOP_LOADING:
2172                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2173                         view = &views[i];
2174                         if (view->pipe)
2175                                 report("Stopped loading the %s view", view->name),
2176                         end_update(view);
2177                 }
2178                 break;
2180         case REQ_SHOW_VERSION:
2181                 report("%s (built %s)", VERSION, __DATE__);
2182                 return TRUE;
2184         case REQ_SCREEN_RESIZE:
2185                 resize_display();
2186                 /* Fall-through */
2187         case REQ_SCREEN_REDRAW:
2188                 redraw_display();
2189                 break;
2191         case REQ_NONE:
2192                 doupdate();
2193                 return TRUE;
2195         case REQ_VIEW_CLOSE:
2196                 /* XXX: Mark closed views by letting view->parent point to the
2197                  * view itself. Parents to closed view should never be
2198                  * followed. */
2199                 if (view->parent &&
2200                     view->parent->parent != view->parent) {
2201                         memset(display, 0, sizeof(display));
2202                         current_view = 0;
2203                         display[current_view] = view->parent;
2204                         view->parent = view;
2205                         resize_display();
2206                         redraw_display();
2207                         break;
2208                 }
2209                 /* Fall-through */
2210         case REQ_QUIT:
2211                 return FALSE;
2213         default:
2214                 /* An unknown key will show most commonly used commands. */
2215                 report("Unknown key, press 'h' for help");
2216                 return TRUE;
2217         }
2219         return TRUE;
2223 /*
2224  * Pager backend
2225  */
2227 static bool
2228 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2230         char *text = line->data;
2231         enum line_type type = line->type;
2232         int textlen = strlen(text);
2233         int attr;
2235         wmove(view->win, lineno, 0);
2237         if (selected) {
2238                 type = LINE_CURSOR;
2239                 wchgat(view->win, -1, 0, type, NULL);
2240         }
2242         attr = get_line_attr(type);
2243         wattrset(view->win, attr);
2245         if (opt_line_number || opt_tab_size < TABSIZE) {
2246                 static char spaces[] = "                    ";
2247                 int col_offset = 0, col = 0;
2249                 if (opt_line_number) {
2250                         unsigned long real_lineno = view->offset + lineno + 1;
2252                         if (real_lineno == 1 ||
2253                             (real_lineno % opt_num_interval) == 0) {
2254                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2256                         } else {
2257                                 waddnstr(view->win, spaces,
2258                                          MIN(view->digits, STRING_SIZE(spaces)));
2259                         }
2260                         waddstr(view->win, ": ");
2261                         col_offset = view->digits + 2;
2262                 }
2264                 while (text && col_offset + col < view->width) {
2265                         int cols_max = view->width - col_offset - col;
2266                         char *pos = text;
2267                         int cols;
2269                         if (*text == '\t') {
2270                                 text++;
2271                                 assert(sizeof(spaces) > TABSIZE);
2272                                 pos = spaces;
2273                                 cols = opt_tab_size - (col % opt_tab_size);
2275                         } else {
2276                                 text = strchr(text, '\t');
2277                                 cols = line ? text - pos : strlen(pos);
2278                         }
2280                         waddnstr(view->win, pos, MIN(cols, cols_max));
2281                         col += cols;
2282                 }
2284         } else {
2285                 int col = 0, pos = 0;
2287                 for (; pos < textlen && col < view->width; pos++, col++)
2288                         if (text[pos] == '\t')
2289                                 col += TABSIZE - (col % TABSIZE) - 1;
2291                 waddnstr(view->win, text, pos);
2292         }
2294         return TRUE;
2297 static bool
2298 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2300         char refbuf[SIZEOF_STR];
2301         char *ref = NULL;
2302         FILE *pipe;
2304         if (!string_format(refbuf, "git describe %s", commit_id))
2305                 return TRUE;
2307         pipe = popen(refbuf, "r");
2308         if (!pipe)
2309                 return TRUE;
2311         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2312                 ref = chomp_string(ref);
2313         pclose(pipe);
2315         if (!ref || !*ref)
2316                 return TRUE;
2318         /* This is the only fatal call, since it can "corrupt" the buffer. */
2319         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2320                 return FALSE;
2322         return TRUE;
2325 static void
2326 add_pager_refs(struct view *view, struct line *line)
2328         char buf[SIZEOF_STR];
2329         char *commit_id = line->data + STRING_SIZE("commit ");
2330         struct ref **refs;
2331         size_t bufpos = 0, refpos = 0;
2332         const char *sep = "Refs: ";
2333         bool is_tag = FALSE;
2335         assert(line->type == LINE_COMMIT);
2337         refs = get_refs(commit_id);
2338         if (!refs) {
2339                 if (view == VIEW(REQ_VIEW_DIFF))
2340                         goto try_add_describe_ref;
2341                 return;
2342         }
2344         do {
2345                 struct ref *ref = refs[refpos];
2346                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2348                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2349                         return;
2350                 sep = ", ";
2351                 if (ref->tag)
2352                         is_tag = TRUE;
2353         } while (refs[refpos++]->next);
2355         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2356 try_add_describe_ref:
2357                 /* Add <tag>-g<commit_id> "fake" reference. */
2358                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2359                         return;
2360         }
2362         if (bufpos == 0)
2363                 return;
2365         if (!realloc_lines(view, view->line_size + 1))
2366                 return;
2368         line = &view->line[view->lines];
2369         line->data = strdup(buf);
2370         if (!line->data)
2371                 return;
2373         line->type = LINE_PP_REFS;
2374         view->lines++;
2377 static bool
2378 pager_read(struct view *view, char *data)
2380         struct line *line = &view->line[view->lines];
2382         line->data = strdup(data);
2383         if (!line->data)
2384                 return FALSE;
2386         line->type = get_line_type(line->data);
2387         view->lines++;
2389         if (line->type == LINE_COMMIT &&
2390             (view == VIEW(REQ_VIEW_DIFF) ||
2391              view == VIEW(REQ_VIEW_LOG)))
2392                 add_pager_refs(view, line);
2394         return TRUE;
2397 static bool
2398 pager_enter(struct view *view, struct line *line)
2400         int split = 0;
2402         if (line->type == LINE_COMMIT &&
2403            (view == VIEW(REQ_VIEW_LOG) ||
2404             view == VIEW(REQ_VIEW_PAGER))) {
2405                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2406                 split = 1;
2407         }
2409         /* Always scroll the view even if it was split. That way
2410          * you can use Enter to scroll through the log view and
2411          * split open each commit diff. */
2412         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2414         /* FIXME: A minor workaround. Scrolling the view will call report("")
2415          * but if we are scrolling a non-current view this won't properly
2416          * update the view title. */
2417         if (split)
2418                 update_view_title(view);
2420         return TRUE;
2423 static bool
2424 pager_grep(struct view *view, struct line *line)
2426         regmatch_t pmatch;
2427         char *text = line->data;
2429         if (!*text)
2430                 return FALSE;
2432         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2433                 return FALSE;
2435         return TRUE;
2438 static void
2439 pager_select(struct view *view, struct line *line)
2441         if (line->type == LINE_COMMIT) {
2442                 char *text = line->data;
2444                 string_copy(view->ref, text + STRING_SIZE("commit "));
2445                 string_copy(ref_commit, view->ref);
2446         }
2449 static struct view_ops pager_ops = {
2450         "line",
2451         pager_draw,
2452         pager_read,
2453         pager_enter,
2454         pager_grep,
2455         pager_select,
2456 };
2459 /*
2460  * Tree backend
2461  */
2463 /* Parse output from git-ls-tree(1):
2464  *
2465  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2466  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2467  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2468  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2469  */
2471 #define SIZEOF_TREE_ATTR \
2472         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2474 #define TREE_UP_FORMAT "040000 tree %s\t.."
2476 static int
2477 tree_compare_entry(enum line_type type1, char *name1,
2478                    enum line_type type2, char *name2)
2480         if (type1 != type2) {
2481                 if (type1 == LINE_TREE_DIR)
2482                         return -1;
2483                 return 1;
2484         }
2486         return strcmp(name1, name2);
2489 static bool
2490 tree_read(struct view *view, char *text)
2492         size_t textlen = strlen(text);
2493         char buf[SIZEOF_STR];
2494         unsigned long pos;
2495         enum line_type type;
2496         bool first_read = view->lines == 0;
2498         if (textlen <= SIZEOF_TREE_ATTR)
2499                 return FALSE;
2501         type = text[STRING_SIZE("100644 ")] == 't'
2502              ? LINE_TREE_DIR : LINE_TREE_FILE;
2504         if (first_read) {
2505                 /* Add path info line */
2506                 if (string_format(buf, "Directory path /%s", opt_path) &&
2507                     realloc_lines(view, view->line_size + 1) &&
2508                     pager_read(view, buf))
2509                         view->line[view->lines - 1].type = LINE_DEFAULT;
2510                 else
2511                         return FALSE;
2513                 /* Insert "link" to parent directory. */
2514                 if (*opt_path &&
2515                     string_format(buf, TREE_UP_FORMAT, view->ref) &&
2516                     realloc_lines(view, view->line_size + 1) &&
2517                     pager_read(view, buf))
2518                         view->line[view->lines - 1].type = LINE_TREE_DIR;
2519                 else if (*opt_path)
2520                         return FALSE;
2521         }
2523         /* Strip the path part ... */
2524         if (*opt_path) {
2525                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2526                 size_t striplen = strlen(opt_path);
2527                 char *path = text + SIZEOF_TREE_ATTR;
2529                 if (pathlen > striplen)
2530                         memmove(path, path + striplen,
2531                                 pathlen - striplen + 1);
2532         }
2534         /* Skip "Directory ..." and ".." line. */
2535         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2536                 struct line *line = &view->line[pos];
2537                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2538                 char *path2 = text + SIZEOF_TREE_ATTR;
2539                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2541                 if (cmp <= 0)
2542                         continue;
2544                 text = strdup(text);
2545                 if (!text)
2546                         return FALSE;
2548                 if (view->lines > pos)
2549                         memmove(&view->line[pos + 1], &view->line[pos],
2550                                 (view->lines - pos) * sizeof(*line));
2552                 line = &view->line[pos];
2553                 line->data = text;
2554                 line->type = type;
2555                 view->lines++;
2556                 return TRUE;
2557         }
2559         if (!pager_read(view, text))
2560                 return FALSE;
2562         /* Move the current line to the first tree entry. */
2563         if (first_read)
2564                 view->lineno++;
2566         view->line[view->lines - 1].type = type;
2567         return TRUE;
2570 static bool
2571 tree_enter(struct view *view, struct line *line)
2573         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2574         enum request request;
2576         switch (line->type) {
2577         case LINE_TREE_DIR:
2578                 /* Depending on whether it is a subdir or parent (updir?) link
2579                  * mangle the path buffer. */
2580                 if (line == &view->line[1] && *opt_path) {
2581                         size_t path_len = strlen(opt_path);
2582                         char *dirsep = opt_path + path_len - 1;
2584                         while (dirsep > opt_path && dirsep[-1] != '/')
2585                                 dirsep--;
2587                         dirsep[0] = 0;
2589                 } else {
2590                         size_t pathlen = strlen(opt_path);
2591                         size_t origlen = pathlen;
2592                         char *data = line->data;
2593                         char *basename = data + SIZEOF_TREE_ATTR;
2595                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2596                                 opt_path[origlen] = 0;
2597                                 return TRUE;
2598                         }
2599                 }
2601                 /* Trees and subtrees share the same ID, so they are not not
2602                  * unique like blobs. */
2603                 flags |= OPEN_RELOAD;
2604                 request = REQ_VIEW_TREE;
2605                 break;
2607         case LINE_TREE_FILE:
2608                 request = REQ_VIEW_BLOB;
2609                 break;
2611         default:
2612                 return TRUE;
2613         }
2615         open_view(view, request, flags);
2617         return TRUE;
2620 static void
2621 tree_select(struct view *view, struct line *line)
2623         char *text = line->data;
2625         text += STRING_SIZE("100644 blob ");
2627         if (line->type == LINE_TREE_FILE) {
2628                 string_ncopy(ref_blob, text, 40);
2629                 /* Also update the blob view's ref, since all there must always
2630                  * be in sync. */
2631                 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2633         } else if (line->type != LINE_TREE_DIR) {
2634                 return;
2635         }
2637         string_ncopy(view->ref, text, 40);
2640 static struct view_ops tree_ops = {
2641         "file",
2642         pager_draw,
2643         tree_read,
2644         tree_enter,
2645         pager_grep,
2646         tree_select,
2647 };
2649 static bool
2650 blob_read(struct view *view, char *line)
2652         bool state = pager_read(view, line);
2654         if (state == TRUE)
2655                 view->line[view->lines - 1].type = LINE_DEFAULT;
2657         return state;
2660 static struct view_ops blob_ops = {
2661         "line",
2662         pager_draw,
2663         blob_read,
2664         pager_enter,
2665         pager_grep,
2666         pager_select,
2667 };
2670 /*
2671  * Main view backend
2672  */
2674 struct commit {
2675         char id[SIZEOF_REV];            /* SHA1 ID. */
2676         char title[75];                 /* First line of the commit message. */
2677         char author[75];                /* Author of the commit. */
2678         struct tm time;                 /* Date from the author ident. */
2679         struct ref **refs;              /* Repository references. */
2680         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2681         size_t graph_size;              /* The width of the graph array. */
2682 };
2684 static bool
2685 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2687         char buf[DATE_COLS + 1];
2688         struct commit *commit = line->data;
2689         enum line_type type;
2690         int col = 0;
2691         size_t timelen;
2692         size_t authorlen;
2693         int trimmed = 1;
2695         if (!*commit->author)
2696                 return FALSE;
2698         wmove(view->win, lineno, col);
2700         if (selected) {
2701                 type = LINE_CURSOR;
2702                 wattrset(view->win, get_line_attr(type));
2703                 wchgat(view->win, -1, 0, type, NULL);
2705         } else {
2706                 type = LINE_MAIN_COMMIT;
2707                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2708         }
2710         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2711         waddnstr(view->win, buf, timelen);
2712         waddstr(view->win, " ");
2714         col += DATE_COLS;
2715         wmove(view->win, lineno, col);
2716         if (type != LINE_CURSOR)
2717                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2719         if (opt_utf8) {
2720                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2721         } else {
2722                 authorlen = strlen(commit->author);
2723                 if (authorlen > AUTHOR_COLS - 2) {
2724                         authorlen = AUTHOR_COLS - 2;
2725                         trimmed = 1;
2726                 }
2727         }
2729         if (trimmed) {
2730                 waddnstr(view->win, commit->author, authorlen);
2731                 if (type != LINE_CURSOR)
2732                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2733                 waddch(view->win, '~');
2734         } else {
2735                 waddstr(view->win, commit->author);
2736         }
2738         col += AUTHOR_COLS;
2739         if (type != LINE_CURSOR)
2740                 wattrset(view->win, A_NORMAL);
2742         if (opt_rev_graph && commit->graph_size) {
2743                 size_t i;
2745                 wmove(view->win, lineno, col);
2746                 /* Using waddch() instead of waddnstr() ensures that
2747                  * they'll be rendered correctly for the cursor line. */
2748                 for (i = 0; i < commit->graph_size; i++)
2749                         waddch(view->win, commit->graph[i]);
2751                 col += commit->graph_size + 1;
2752         }
2754         wmove(view->win, lineno, col);
2756         if (commit->refs) {
2757                 size_t i = 0;
2759                 do {
2760                         if (type == LINE_CURSOR)
2761                                 ;
2762                         else if (commit->refs[i]->tag)
2763                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2764                         else
2765                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2766                         waddstr(view->win, "[");
2767                         waddstr(view->win, commit->refs[i]->name);
2768                         waddstr(view->win, "]");
2769                         if (type != LINE_CURSOR)
2770                                 wattrset(view->win, A_NORMAL);
2771                         waddstr(view->win, " ");
2772                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2773                 } while (commit->refs[i++]->next);
2774         }
2776         if (type != LINE_CURSOR)
2777                 wattrset(view->win, get_line_attr(type));
2779         {
2780                 int titlelen = strlen(commit->title);
2782                 if (col + titlelen > view->width)
2783                         titlelen = view->width - col;
2785                 waddnstr(view->win, commit->title, titlelen);
2786         }
2788         return TRUE;
2792 struct rev_stack {
2793         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
2794         size_t size;
2795 };
2797 /* The current stack of revisions on the graph. */
2798 static struct rev_stack graph_stacks[3];
2799 static size_t graph_stack_no;
2801 /* Parents of the commit being visualized. */
2802 static struct rev_stack graph_parents[2];
2804 static size_t graph_last_rev;
2806 static void
2807 push_rev_stack(struct rev_stack *stack, char *parent)
2809         fprintf(stderr, " (%s)", parent);
2811         /* Combine duplicate parents lines. */
2812         if (stack->size > 0 &&
2813             !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV))
2814                 return;
2816         if (stack->size < SIZEOF_REVITEMS) {
2817                 string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV);
2818         }
2821 void
2822 update_rev_graph(struct commit *commit)
2824         struct rev_stack *parents = &graph_parents[graph_stack_no & 1];
2825         struct rev_stack *stack = &graph_stacks[graph_stack_no++ & 1];
2826         struct rev_stack *prev_parents = &graph_parents[graph_stack_no & 1];
2827         struct rev_stack *graph = &graph_stacks[graph_stack_no & 1];
2828         chtype symbol, separator, line;
2829         size_t stackpos = 0;
2830         size_t i;
2832         fprintf(stderr, "\n%p <%s> ", graph, commit->id);
2834         /* First traverse all lines of revisions up to the active one. */
2835         for (stackpos = 0; stackpos < stack->size; stackpos++) {
2836                 if (!strcmp(stack->rev[stackpos], commit->id)) {
2837                         break;
2838                 }
2840                 push_rev_stack(graph, stack->rev[stackpos]);
2841         }
2843         assert(commit->graph_size < ARRAY_SIZE(commit->graph));
2845         for (i = 0; i < parents->size; i++)
2846                 push_rev_stack(graph, parents->rev[i]);
2848         /* Place the symbol for this commit. */
2849         if (parents->size == 0)
2850                 symbol = REVGRAPH_INIT;
2851         else if (parents->size > 1)
2852                 symbol = REVGRAPH_MERGE;
2853         else if (stackpos >= stack->size)
2854                 symbol = REVGRAPH_BRANCH;
2855         else
2856                 symbol = REVGRAPH_COMMIT;
2858         i = stackpos + 1;
2860         /* FIXME: Moving branches left and right when collapsing a branch. */
2861         while (i < stack->size)
2862                 push_rev_stack(graph, stack->rev[i++]);
2864         separator = ' ';
2865         line = REVGRAPH_LINE;
2867         for (i = 0; i < stackpos; i++) {
2868                 commit->graph[commit->graph_size++] = line;
2869                 if (prev_parents->size > 1 &&
2870                     i == graph_last_rev) {
2871                         separator = '`';
2872                         line = '.';
2873                 }
2874                 commit->graph[commit->graph_size++] = separator;
2875         }
2877         commit->graph[commit->graph_size++] = symbol;
2879         separator = ' ';
2880         line = REVGRAPH_LINE;
2881         i++;
2883         for (; i < stack->size; i++) {
2884                 commit->graph[commit->graph_size++] = separator;
2885                 commit->graph[commit->graph_size++] = line;
2886                 if (prev_parents->size > 1) {
2887                         if (i < graph_last_rev + prev_parents->size) {
2888                                 separator = '`';
2889                                 line = '.';
2890                         }
2891                 }
2892         }
2894         graph_last_rev = stackpos;
2895         stack->size = prev_parents->size = 0;
2898 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2899 static bool
2900 main_read(struct view *view, char *line)
2902         enum line_type type = get_line_type(line);
2903         struct commit *commit = view->lines
2904                               ? view->line[view->lines - 1].data : NULL;
2906         switch (type) {
2907         case LINE_COMMIT:
2908                 commit = calloc(1, sizeof(struct commit));
2909                 if (!commit)
2910                         return FALSE;
2912                 line += STRING_SIZE("commit ");
2914                 view->line[view->lines++].data = commit;
2915                 string_copy(commit->id, line);
2916                 commit->refs = get_refs(commit->id);
2917                 fprintf(stderr, "\n%p [%s]", &graph_stacks[graph_stack_no & 1], commit->id);
2918                 break;
2920         case LINE_PARENT:
2921                 if (commit) {
2922                         line += STRING_SIZE("parent ");
2923                         push_rev_stack(&graph_parents[graph_stack_no & 1], line);
2924                 }
2925                 break;
2927         case LINE_AUTHOR:
2928         {
2929                 char *ident = line + STRING_SIZE("author ");
2930                 char *end = strchr(ident, '<');
2932                 if (!commit)
2933                         break;
2935                 update_rev_graph(commit);
2937                 if (end) {
2938                         char *email = end + 1;
2940                         for (; end > ident && isspace(end[-1]); end--) ;
2942                         if (end == ident && *email) {
2943                                 ident = email;
2944                                 end = strchr(ident, '>');
2945                                 for (; end > ident && isspace(end[-1]); end--) ;
2946                         }
2947                         *end = 0;
2948                 }
2950                 /* End is NULL or ident meaning there's no author. */
2951                 if (end <= ident)
2952                         ident = "Unknown";
2954                 string_copy(commit->author, ident);
2956                 /* Parse epoch and timezone */
2957                 if (end) {
2958                         char *secs = strchr(end + 1, '>');
2959                         char *zone;
2960                         time_t time;
2962                         if (!secs || secs[1] != ' ')
2963                                 break;
2965                         secs += 2;
2966                         time = (time_t) atol(secs);
2967                         zone = strchr(secs, ' ');
2968                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2969                                 long tz;
2971                                 zone++;
2972                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2973                                 tz += ('0' - zone[2]) * 60 * 60;
2974                                 tz += ('0' - zone[3]) * 60;
2975                                 tz += ('0' - zone[4]) * 60;
2977                                 if (zone[0] == '-')
2978                                         tz = -tz;
2980                                 time -= tz;
2981                         }
2982                         gmtime_r(&time, &commit->time);
2983                 }
2984                 break;
2985         }
2986         default:
2987                 if (!commit)
2988                         break;
2990                 /* Fill in the commit title if it has not already been set. */
2991                 if (commit->title[0])
2992                         break;
2994                 /* Require titles to start with a non-space character at the
2995                  * offset used by git log. */
2996                 /* FIXME: More gracefull handling of titles; append "..." to
2997                  * shortened titles, etc. */
2998                 if (strncmp(line, "    ", 4) ||
2999                     isspace(line[4]))
3000                         break;
3002                 string_copy(commit->title, line + 4);
3003         }
3005         return TRUE;
3008 static bool
3009 main_enter(struct view *view, struct line *line)
3011         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3013         open_view(view, REQ_VIEW_DIFF, flags);
3014         return TRUE;
3017 static bool
3018 main_grep(struct view *view, struct line *line)
3020         struct commit *commit = line->data;
3021         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
3022         char buf[DATE_COLS + 1];
3023         regmatch_t pmatch;
3025         for (state = S_TITLE; state < S_END; state++) {
3026                 char *text;
3028                 switch (state) {
3029                 case S_TITLE:   text = commit->title;   break;
3030                 case S_AUTHOR:  text = commit->author;  break;
3031                 case S_DATE:
3032                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
3033                                 continue;
3034                         text = buf;
3035                         break;
3037                 default:
3038                         return FALSE;
3039                 }
3041                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3042                         return TRUE;
3043         }
3045         return FALSE;
3048 static void
3049 main_select(struct view *view, struct line *line)
3051         struct commit *commit = line->data;
3053         string_copy(view->ref, commit->id);
3054         string_copy(ref_commit, view->ref);
3057 static struct view_ops main_ops = {
3058         "commit",
3059         main_draw,
3060         main_read,
3061         main_enter,
3062         main_grep,
3063         main_select,
3064 };
3067 /*
3068  * Unicode / UTF-8 handling
3069  *
3070  * NOTE: Much of the following code for dealing with unicode is derived from
3071  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
3072  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
3073  */
3075 /* I've (over)annotated a lot of code snippets because I am not entirely
3076  * confident that the approach taken by this small UTF-8 interface is correct.
3077  * --jonas */
3079 static inline int
3080 unicode_width(unsigned long c)
3082         if (c >= 0x1100 &&
3083            (c <= 0x115f                         /* Hangul Jamo */
3084             || c == 0x2329
3085             || c == 0x232a
3086             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
3087                                                 /* CJK ... Yi */
3088             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
3089             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
3090             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
3091             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
3092             || (c >= 0xffe0  && c <= 0xffe6)
3093             || (c >= 0x20000 && c <= 0x2fffd)
3094             || (c >= 0x30000 && c <= 0x3fffd)))
3095                 return 2;
3097         return 1;
3100 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
3101  * Illegal bytes are set one. */
3102 static const unsigned char utf8_bytes[256] = {
3103         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,
3104         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,
3105         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,
3106         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,
3107         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,
3108         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,
3109         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,
3110         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,
3111 };
3113 /* Decode UTF-8 multi-byte representation into a unicode character. */
3114 static inline unsigned long
3115 utf8_to_unicode(const char *string, size_t length)
3117         unsigned long unicode;
3119         switch (length) {
3120         case 1:
3121                 unicode  =   string[0];
3122                 break;
3123         case 2:
3124                 unicode  =  (string[0] & 0x1f) << 6;
3125                 unicode +=  (string[1] & 0x3f);
3126                 break;
3127         case 3:
3128                 unicode  =  (string[0] & 0x0f) << 12;
3129                 unicode += ((string[1] & 0x3f) << 6);
3130                 unicode +=  (string[2] & 0x3f);
3131                 break;
3132         case 4:
3133                 unicode  =  (string[0] & 0x0f) << 18;
3134                 unicode += ((string[1] & 0x3f) << 12);
3135                 unicode += ((string[2] & 0x3f) << 6);
3136                 unicode +=  (string[3] & 0x3f);
3137                 break;
3138         case 5:
3139                 unicode  =  (string[0] & 0x0f) << 24;
3140                 unicode += ((string[1] & 0x3f) << 18);
3141                 unicode += ((string[2] & 0x3f) << 12);
3142                 unicode += ((string[3] & 0x3f) << 6);
3143                 unicode +=  (string[4] & 0x3f);
3144                 break;
3145         case 6:
3146                 unicode  =  (string[0] & 0x01) << 30;
3147                 unicode += ((string[1] & 0x3f) << 24);
3148                 unicode += ((string[2] & 0x3f) << 18);
3149                 unicode += ((string[3] & 0x3f) << 12);
3150                 unicode += ((string[4] & 0x3f) << 6);
3151                 unicode +=  (string[5] & 0x3f);
3152                 break;
3153         default:
3154                 die("Invalid unicode length");
3155         }
3157         /* Invalid characters could return the special 0xfffd value but NUL
3158          * should be just as good. */
3159         return unicode > 0xffff ? 0 : unicode;
3162 /* Calculates how much of string can be shown within the given maximum width
3163  * and sets trimmed parameter to non-zero value if all of string could not be
3164  * shown.
3165  *
3166  * Additionally, adds to coloffset how many many columns to move to align with
3167  * the expected position. Takes into account how multi-byte and double-width
3168  * characters will effect the cursor position.
3169  *
3170  * Returns the number of bytes to output from string to satisfy max_width. */
3171 static size_t
3172 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3174         const char *start = string;
3175         const char *end = strchr(string, '\0');
3176         size_t mbwidth = 0;
3177         size_t width = 0;
3179         *trimmed = 0;
3181         while (string < end) {
3182                 int c = *(unsigned char *) string;
3183                 unsigned char bytes = utf8_bytes[c];
3184                 size_t ucwidth;
3185                 unsigned long unicode;
3187                 if (string + bytes > end)
3188                         break;
3190                 /* Change representation to figure out whether
3191                  * it is a single- or double-width character. */
3193                 unicode = utf8_to_unicode(string, bytes);
3194                 /* FIXME: Graceful handling of invalid unicode character. */
3195                 if (!unicode)
3196                         break;
3198                 ucwidth = unicode_width(unicode);
3199                 width  += ucwidth;
3200                 if (width > max_width) {
3201                         *trimmed = 1;
3202                         break;
3203                 }
3205                 /* The column offset collects the differences between the
3206                  * number of bytes encoding a character and the number of
3207                  * columns will be used for rendering said character.
3208                  *
3209                  * So if some character A is encoded in 2 bytes, but will be
3210                  * represented on the screen using only 1 byte this will and up
3211                  * adding 1 to the multi-byte column offset.
3212                  *
3213                  * Assumes that no double-width character can be encoding in
3214                  * less than two bytes. */
3215                 if (bytes > ucwidth)
3216                         mbwidth += bytes - ucwidth;
3218                 string  += bytes;
3219         }
3221         *coloffset += mbwidth;
3223         return string - start;
3227 /*
3228  * Status management
3229  */
3231 /* Whether or not the curses interface has been initialized. */
3232 static bool cursed = FALSE;
3234 /* The status window is used for polling keystrokes. */
3235 static WINDOW *status_win;
3237 /* Update status and title window. */
3238 static void
3239 report(const char *msg, ...)
3241         static bool empty = TRUE;
3242         struct view *view = display[current_view];
3244         if (!empty || *msg) {
3245                 va_list args;
3247                 va_start(args, msg);
3249                 werase(status_win);
3250                 wmove(status_win, 0, 0);
3251                 if (*msg) {
3252                         vwprintw(status_win, msg, args);
3253                         empty = FALSE;
3254                 } else {
3255                         empty = TRUE;
3256                 }
3257                 wrefresh(status_win);
3259                 va_end(args);
3260         }
3262         update_view_title(view);
3263         update_display_cursor();
3266 /* Controls when nodelay should be in effect when polling user input. */
3267 static void
3268 set_nonblocking_input(bool loading)
3270         static unsigned int loading_views;
3272         if ((loading == FALSE && loading_views-- == 1) ||
3273             (loading == TRUE  && loading_views++ == 0))
3274                 nodelay(status_win, loading);
3277 static void
3278 init_display(void)
3280         int x, y;
3282         /* Initialize the curses library */
3283         if (isatty(STDIN_FILENO)) {
3284                 cursed = !!initscr();
3285         } else {
3286                 /* Leave stdin and stdout alone when acting as a pager. */
3287                 FILE *io = fopen("/dev/tty", "r+");
3289                 if (!io)
3290                         die("Failed to open /dev/tty");
3291                 cursed = !!newterm(NULL, io, io);
3292         }
3294         if (!cursed)
3295                 die("Failed to initialize curses");
3297         nonl();         /* Tell curses not to do NL->CR/NL on output */
3298         cbreak();       /* Take input chars one at a time, no wait for \n */
3299         noecho();       /* Don't echo input */
3300         leaveok(stdscr, TRUE);
3302         if (has_colors())
3303                 init_colors();
3305         getmaxyx(stdscr, y, x);
3306         status_win = newwin(1, 0, y - 1, 0);
3307         if (!status_win)
3308                 die("Failed to create status window");
3310         /* Enable keyboard mapping */
3311         keypad(status_win, TRUE);
3312         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3315 static char *
3316 read_prompt(const char *prompt)
3318         enum { READING, STOP, CANCEL } status = READING;
3319         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3320         int pos = 0;
3322         while (status == READING) {
3323                 struct view *view;
3324                 int i, key;
3326                 foreach_view (view, i)
3327                         update_view(view);
3329                 report("%s%.*s", prompt, pos, buf);
3330                 /* Refresh, accept single keystroke of input */
3331                 key = wgetch(status_win);
3332                 switch (key) {
3333                 case KEY_RETURN:
3334                 case KEY_ENTER:
3335                 case '\n':
3336                         status = pos ? STOP : CANCEL;
3337                         break;
3339                 case KEY_BACKSPACE:
3340                         if (pos > 0)
3341                                 pos--;
3342                         else
3343                                 status = CANCEL;
3344                         break;
3346                 case KEY_ESC:
3347                         status = CANCEL;
3348                         break;
3350                 case ERR:
3351                         break;
3353                 default:
3354                         if (pos >= sizeof(buf)) {
3355                                 report("Input string too long");
3356                                 return NULL;
3357                         }
3359                         if (isprint(key))
3360                                 buf[pos++] = (char) key;
3361                 }
3362         }
3364         if (status == CANCEL) {
3365                 /* Clear the status window */
3366                 report("");
3367                 return NULL;
3368         }
3370         buf[pos++] = 0;
3372         return buf;
3375 /*
3376  * Repository references
3377  */
3379 static struct ref *refs;
3380 static size_t refs_size;
3382 /* Id <-> ref store */
3383 static struct ref ***id_refs;
3384 static size_t id_refs_size;
3386 static struct ref **
3387 get_refs(char *id)
3389         struct ref ***tmp_id_refs;
3390         struct ref **ref_list = NULL;
3391         size_t ref_list_size = 0;
3392         size_t i;
3394         for (i = 0; i < id_refs_size; i++)
3395                 if (!strcmp(id, id_refs[i][0]->id))
3396                         return id_refs[i];
3398         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3399         if (!tmp_id_refs)
3400                 return NULL;
3402         id_refs = tmp_id_refs;
3404         for (i = 0; i < refs_size; i++) {
3405                 struct ref **tmp;
3407                 if (strcmp(id, refs[i].id))
3408                         continue;
3410                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3411                 if (!tmp) {
3412                         if (ref_list)
3413                                 free(ref_list);
3414                         return NULL;
3415                 }
3417                 ref_list = tmp;
3418                 if (ref_list_size > 0)
3419                         ref_list[ref_list_size - 1]->next = 1;
3420                 ref_list[ref_list_size] = &refs[i];
3422                 /* XXX: The properties of the commit chains ensures that we can
3423                  * safely modify the shared ref. The repo references will
3424                  * always be similar for the same id. */
3425                 ref_list[ref_list_size]->next = 0;
3426                 ref_list_size++;
3427         }
3429         if (ref_list)
3430                 id_refs[id_refs_size++] = ref_list;
3432         return ref_list;
3435 static int
3436 read_ref(char *id, int idlen, char *name, int namelen)
3438         struct ref *ref;
3439         bool tag = FALSE;
3441         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3442                 /* Commits referenced by tags has "^{}" appended. */
3443                 if (name[namelen - 1] != '}')
3444                         return OK;
3446                 while (namelen > 0 && name[namelen] != '^')
3447                         namelen--;
3449                 tag = TRUE;
3450                 namelen -= STRING_SIZE("refs/tags/");
3451                 name    += STRING_SIZE("refs/tags/");
3453         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3454                 namelen -= STRING_SIZE("refs/heads/");
3455                 name    += STRING_SIZE("refs/heads/");
3457         } else if (!strcmp(name, "HEAD")) {
3458                 return OK;
3459         }
3461         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3462         if (!refs)
3463                 return ERR;
3465         ref = &refs[refs_size++];
3466         ref->name = malloc(namelen + 1);
3467         if (!ref->name)
3468                 return ERR;
3470         strncpy(ref->name, name, namelen);
3471         ref->name[namelen] = 0;
3472         ref->tag = tag;
3473         string_copy(ref->id, id);
3475         return OK;
3478 static int
3479 load_refs(void)
3481         const char *cmd_env = getenv("TIG_LS_REMOTE");
3482         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3484         return read_properties(popen(cmd, "r"), "\t", read_ref);
3487 static int
3488 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3490         if (!strcmp(name, "i18n.commitencoding"))
3491                 string_copy(opt_encoding, value);
3493         return OK;
3496 static int
3497 load_repo_config(void)
3499         return read_properties(popen("git repo-config --list", "r"),
3500                                "=", read_repo_config_option);
3503 static int
3504 read_properties(FILE *pipe, const char *separators,
3505                 int (*read_property)(char *, int, char *, int))
3507         char buffer[BUFSIZ];
3508         char *name;
3509         int state = OK;
3511         if (!pipe)
3512                 return ERR;
3514         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3515                 char *value;
3516                 size_t namelen;
3517                 size_t valuelen;
3519                 name = chomp_string(name);
3520                 namelen = strcspn(name, separators);
3522                 if (name[namelen]) {
3523                         name[namelen] = 0;
3524                         value = chomp_string(name + namelen + 1);
3525                         valuelen = strlen(value);
3527                 } else {
3528                         value = "";
3529                         valuelen = 0;
3530                 }
3532                 state = read_property(name, namelen, value, valuelen);
3533         }
3535         if (state != ERR && ferror(pipe))
3536                 state = ERR;
3538         pclose(pipe);
3540         return state;
3544 /*
3545  * Main
3546  */
3548 static void __NORETURN
3549 quit(int sig)
3551         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3552         if (cursed)
3553                 endwin();
3554         exit(0);
3557 static void __NORETURN
3558 die(const char *err, ...)
3560         va_list args;
3562         endwin();
3564         va_start(args, err);
3565         fputs("tig: ", stderr);
3566         vfprintf(stderr, err, args);
3567         fputs("\n", stderr);
3568         va_end(args);
3570         exit(1);
3573 int
3574 main(int argc, char *argv[])
3576         struct view *view;
3577         enum request request;
3578         size_t i;
3580         signal(SIGINT, quit);
3582         if (setlocale(LC_ALL, "")) {
3583                 string_copy(opt_codeset, nl_langinfo(CODESET));
3584         }
3586         if (load_options() == ERR)
3587                 die("Failed to load user config.");
3589         /* Load the repo config file so options can be overwritten from
3590          * the command line.  */
3591         if (load_repo_config() == ERR)
3592                 die("Failed to load repo config.");
3594         if (!parse_options(argc, argv))
3595                 return 0;
3597         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3598                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3599                 if (opt_iconv == ICONV_NONE)
3600                         die("Failed to initialize character set conversion");
3601         }
3603         if (load_refs() == ERR)
3604                 die("Failed to load refs.");
3606         /* Require a git repository unless when running in pager mode. */
3607         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3608                 die("Not a git repository");
3610         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3611                 view->cmd_env = getenv(view->cmd_env);
3613         request = opt_request;
3615         init_display();
3617         while (view_driver(display[current_view], request)) {
3618                 int key;
3619                 int i;
3621                 foreach_view (view, i)
3622                         update_view(view);
3624                 /* Refresh, accept single keystroke of input */
3625                 key = wgetch(status_win);
3627                 request = get_keybinding(display[current_view]->keymap, key);
3629                 /* Some low-level request handling. This keeps access to
3630                  * status_win restricted. */
3631                 switch (request) {
3632                 case REQ_PROMPT:
3633                 {
3634                         char *cmd = read_prompt(":");
3636                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3637                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3638                                         opt_request = REQ_VIEW_DIFF;
3639                                 } else {
3640                                         opt_request = REQ_VIEW_PAGER;
3641                                 }
3642                                 break;
3643                         }
3645                         request = REQ_NONE;
3646                         break;
3647                 }
3648                 case REQ_SEARCH:
3649                 case REQ_SEARCH_BACK:
3650                 {
3651                         const char *prompt = request == REQ_SEARCH
3652                                            ? "/" : "?";
3653                         char *search = read_prompt(prompt);
3655                         if (search)
3656                                 string_copy(opt_search, search);
3657                         else
3658                                 request = REQ_NONE;
3659                         break;
3660                 }
3661                 case REQ_SCREEN_RESIZE:
3662                 {
3663                         int height, width;
3665                         getmaxyx(stdscr, height, width);
3667                         /* Resize the status view and let the view driver take
3668                          * care of resizing the displayed views. */
3669                         wresize(status_win, 1, width);
3670                         mvwin(status_win, height - 1, 0);
3671                         wrefresh(status_win);
3672                         break;
3673                 }
3674                 default:
3675                         break;
3676                 }
3677         }
3679         quit(0);
3681         return 0;