Code

626fcd5fd1ca49e8dfc89d1ae29c09f5a0e10edd
[tig.git] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifndef VERSION
15 #define VERSION "tig-0.3"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <curses.h>
35 #if __GNUC__ >= 3
36 #define __NORETURN __attribute__((__noreturn__))
37 #else
38 #define __NORETURN
39 #endif
41 static void __NORETURN die(const char *err, ...);
42 static void report(const char *msg, ...);
43 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
44 static void set_nonblocking_input(bool loading);
45 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
47 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
48 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
50 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
51 #define STRING_SIZE(x)  (sizeof(x) - 1)
53 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
54 #define SIZEOF_CMD      1024    /* Size of command buffer. */
55 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
57 /* This color name can be used to refer to the default term colors. */
58 #define COLOR_DEFAULT   (-1)
60 /* The format and size of the date column in the main view. */
61 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
62 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
64 #define AUTHOR_COLS     20
66 /* The default interval between line numbers. */
67 #define NUMBER_INTERVAL 1
69 #define TABSIZE         8
71 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
73 #define TIG_LS_REMOTE \
74         "git ls-remote . 2>/dev/null"
76 #define TIG_DIFF_CMD \
77         "git show --patch-with-stat --find-copies-harder -B -C %s"
79 #define TIG_LOG_CMD     \
80         "git log --cc --stat -n100 %s"
82 #define TIG_MAIN_CMD \
83         "git log --topo-order --stat --pretty=raw %s"
85 /* XXX: Needs to be defined to the empty string. */
86 #define TIG_HELP_CMD    ""
87 #define TIG_PAGER_CMD   ""
89 /* Some ascii-shorthands fitted into the ncurses namespace. */
90 #define KEY_TAB         '\t'
91 #define KEY_RETURN      '\r'
92 #define KEY_ESC         27
95 struct ref {
96         char *name;             /* Ref name; tag or head names are shortened. */
97         char id[41];            /* Commit SHA1 ID */
98         unsigned int tag:1;     /* Is it a tag? */
99         unsigned int next:1;    /* For ref lists: are there more refs? */
100 };
102 static struct ref **get_refs(char *id);
104 struct int_map {
105         const char *name;
106         int namelen;
107         int value;
108 };
110 static int
111 set_from_int_map(struct int_map *map, size_t map_size,
112                  int *value, const char *name, int namelen)
115         int i;
117         for (i = 0; i < map_size; i++)
118                 if (namelen == map[i].namelen &&
119                     !strncasecmp(name, map[i].name, namelen)) {
120                         *value = map[i].value;
121                         return OK;
122                 }
124         return ERR;
128 /*
129  * String helpers
130  */
132 static inline void
133 string_ncopy(char *dst, const char *src, int dstlen)
135         strncpy(dst, src, dstlen - 1);
136         dst[dstlen - 1] = 0;
140 /* Shorthand for safely copying into a fixed buffer. */
141 #define string_copy(dst, src) \
142         string_ncopy(dst, src, sizeof(dst))
144 static char *
145 chomp_string(char *name)
147         int namelen;
149         while (isspace(*name))
150                 name++;
152         namelen = strlen(name) - 1;
153         while (namelen > 0 && isspace(name[namelen]))
154                 name[namelen--] = 0;
156         return name;
159 static bool
160 string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
162         va_list args;
163         int pos = bufpos ? *bufpos : 0;
165         va_start(args, fmt);
166         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
167         va_end(args);
169         if (bufpos)
170                 *bufpos = pos;
172         return pos >= bufsize ? FALSE : TRUE;
175 #define string_format(buf, fmt, args...) \
176         string_nformat(buf, sizeof(buf), NULL, fmt, args)
178 #define string_format_from(buf, from, fmt, args...) \
179         string_nformat(buf, sizeof(buf), from, fmt, args)
181 static int
182 string_enum_compare(const char *str1, const char *str2, int len)
184         size_t i;
186 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
188         /* Diff-Header == DIFF_HEADER */
189         for (i = 0; i < len; i++) {
190                 if (toupper(str1[i]) == toupper(str2[i]))
191                         continue;
193                 if (string_enum_sep(str1[i]) &&
194                     string_enum_sep(str2[i]))
195                         continue;
197                 return str1[i] - str2[i];
198         }
200         return 0;
203 /* Shell quoting
204  *
205  * NOTE: The following is a slightly modified copy of the git project's shell
206  * quoting routines found in the quote.c file.
207  *
208  * Help to copy the thing properly quoted for the shell safety.  any single
209  * quote is replaced with '\'', any exclamation point is replaced with '\!',
210  * and the whole thing is enclosed in a
211  *
212  * E.g.
213  *  original     sq_quote     result
214  *  name     ==> name      ==> 'name'
215  *  a b      ==> a b       ==> 'a b'
216  *  a'b      ==> a'\''b    ==> 'a'\''b'
217  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
218  */
220 static size_t
221 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
223         char c;
225 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
227         BUFPUT('\'');
228         while ((c = *src++)) {
229                 if (c == '\'' || c == '!') {
230                         BUFPUT('\'');
231                         BUFPUT('\\');
232                         BUFPUT(c);
233                         BUFPUT('\'');
234                 } else {
235                         BUFPUT(c);
236                 }
237         }
238         BUFPUT('\'');
240         return bufsize;
244 /*
245  * User requests
246  */
248 #define REQ_INFO \
249         /* XXX: Keep the view request first and in sync with views[]. */ \
250         REQ_GROUP("View switching") \
251         REQ_(VIEW_MAIN,         "Show main view"), \
252         REQ_(VIEW_DIFF,         "Show diff view"), \
253         REQ_(VIEW_LOG,          "Show log view"), \
254         REQ_(VIEW_HELP,         "Show help page"), \
255         REQ_(VIEW_PAGER,        "Show pager view"), \
256         \
257         REQ_GROUP("View manipulation") \
258         REQ_(ENTER,             "Enter current line and scroll"), \
259         REQ_(NEXT,              "Move to next"), \
260         REQ_(PREVIOUS,          "Move to previous"), \
261         REQ_(VIEW_NEXT,         "Move focus to next view"), \
262         REQ_(VIEW_CLOSE,        "Close the current view"), \
263         REQ_(QUIT,              "Close all views and quit"), \
264         \
265         REQ_GROUP("Cursor navigation") \
266         REQ_(MOVE_UP,           "Move cursor one line up"), \
267         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
268         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
269         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
270         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
271         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
272         \
273         REQ_GROUP("Scrolling") \
274         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
275         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
276         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
277         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
278         \
279         REQ_GROUP("Misc") \
280         REQ_(PROMPT,            "Bring up the prompt"), \
281         REQ_(SCREEN_UPDATE,     "Update the screen"), \
282         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
283         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
284         REQ_(SHOW_VERSION,      "Show version information"), \
285         REQ_(STOP_LOADING,      "Stop all loading views"), \
286         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
287         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"),
290 /* User action requests. */
291 enum request {
292 #define REQ_GROUP(help)
293 #define REQ_(req, help) REQ_##req
295         /* Offset all requests to avoid conflicts with ncurses getch values. */
296         REQ_OFFSET = KEY_MAX + 1,
297         REQ_INFO
299 #undef  REQ_GROUP
300 #undef  REQ_
301 };
303 struct request_info {
304         enum request request;
305         char *help;
306 };
308 static struct request_info req_info[] = {
309 #define REQ_GROUP(help) { 0, (help) },
310 #define REQ_(req, help) { REQ_##req, (help) }
311         REQ_INFO
312 #undef  REQ_GROUP
313 #undef  REQ_
314 };
316 /*
317  * Options
318  */
320 static const char usage[] =
321 VERSION " (" __DATE__ ")\n"
322 "\n"
323 "Usage: tig [options]\n"
324 "   or: tig [options] [--] [git log options]\n"
325 "   or: tig [options] log  [git log options]\n"
326 "   or: tig [options] diff [git diff options]\n"
327 "   or: tig [options] show [git show options]\n"
328 "   or: tig [options] <    [git command output]\n"
329 "\n"
330 "Options:\n"
331 "  -l                          Start up in log view\n"
332 "  -d                          Start up in diff view\n"
333 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
334 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
335 "  --                          Mark end of tig options\n"
336 "  -v, --version               Show version and exit\n"
337 "  -h, --help                  Show help message and exit\n";
339 /* Option and state variables. */
340 static bool opt_line_number     = FALSE;
341 static bool opt_rev_graph       = TRUE;
342 static int opt_num_interval     = NUMBER_INTERVAL;
343 static int opt_tab_size         = TABSIZE;
344 static enum request opt_request = REQ_VIEW_MAIN;
345 static char opt_cmd[SIZEOF_CMD] = "";
346 static char opt_encoding[20]    = "";
347 static bool opt_utf8            = TRUE;
348 static FILE *opt_pipe           = NULL;
350 enum option_type {
351         OPT_NONE,
352         OPT_INT,
353 };
355 static bool
356 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
358         va_list args;
359         char *value = "";
360         int *number;
362         if (opt[0] != '-')
363                 return FALSE;
365         if (opt[1] == '-') {
366                 int namelen = strlen(name);
368                 opt += 2;
370                 if (strncmp(opt, name, namelen))
371                         return FALSE;
373                 if (opt[namelen] == '=')
374                         value = opt + namelen + 1;
376         } else {
377                 if (!short_name || opt[1] != short_name)
378                         return FALSE;
379                 value = opt + 2;
380         }
382         va_start(args, type);
383         if (type == OPT_INT) {
384                 number = va_arg(args, int *);
385                 if (isdigit(*value))
386                         *number = atoi(value);
387         }
388         va_end(args);
390         return TRUE;
393 /* Returns the index of log or diff command or -1 to exit. */
394 static bool
395 parse_options(int argc, char *argv[])
397         int i;
399         for (i = 1; i < argc; i++) {
400                 char *opt = argv[i];
402                 if (!strcmp(opt, "-l")) {
403                         opt_request = REQ_VIEW_LOG;
404                         continue;
405                 }
407                 if (!strcmp(opt, "-d")) {
408                         opt_request = REQ_VIEW_DIFF;
409                         continue;
410                 }
412                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
413                         opt_line_number = TRUE;
414                         continue;
415                 }
417                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
418                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
419                         continue;
420                 }
422                 if (check_option(opt, 'v', "version", OPT_NONE)) {
423                         printf("tig version %s\n", VERSION);
424                         return FALSE;
425                 }
427                 if (check_option(opt, 'h', "help", OPT_NONE)) {
428                         printf(usage);
429                         return FALSE;
430                 }
432                 if (!strcmp(opt, "--")) {
433                         i++;
434                         break;
435                 }
437                 if (!strcmp(opt, "log") ||
438                     !strcmp(opt, "diff") ||
439                     !strcmp(opt, "show")) {
440                         opt_request = opt[0] == 'l'
441                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
442                         break;
443                 }
445                 if (opt[0] && opt[0] != '-')
446                         break;
448                 die("unknown option '%s'\n\n%s", opt, usage);
449         }
451         if (!isatty(STDIN_FILENO)) {
452                 opt_request = REQ_VIEW_PAGER;
453                 opt_pipe = stdin;
455         } else if (i < argc) {
456                 size_t buf_size;
458                 if (opt_request == REQ_VIEW_MAIN)
459                         /* XXX: This is vulnerable to the user overriding
460                          * options required for the main view parser. */
461                         string_copy(opt_cmd, "git log --stat --pretty=raw");
462                 else
463                         string_copy(opt_cmd, "git");
464                 buf_size = strlen(opt_cmd);
466                 while (buf_size < sizeof(opt_cmd) && i < argc) {
467                         opt_cmd[buf_size++] = ' ';
468                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
469                 }
471                 if (buf_size >= sizeof(opt_cmd))
472                         die("command too long");
474                 opt_cmd[buf_size] = 0;
476         }
478         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
479                 opt_utf8 = FALSE;
481         return TRUE;
485 /*
486  * Line-oriented content detection.
487  */
489 #define LINE_INFO \
490 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
491 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
492 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
493 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
494 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
495 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
496 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
497 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
498 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
499 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
500 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
501 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
502 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
503 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
504 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
505 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
506 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
507 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
508 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
509 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
510 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
511 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
512 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
513 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
514 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
515 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
516 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
517 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
518 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
519 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
520 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
521 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
522 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
523 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
524 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
525 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
526 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
527 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
529 enum line_type {
530 #define LINE(type, line, fg, bg, attr) \
531         LINE_##type
532         LINE_INFO
533 #undef  LINE
534 };
536 struct line_info {
537         const char *name;       /* Option name. */
538         int namelen;            /* Size of option name. */
539         const char *line;       /* The start of line to match. */
540         int linelen;            /* Size of string to match. */
541         int fg, bg, attr;       /* Color and text attributes for the lines. */
542 };
544 static struct line_info line_info[] = {
545 #define LINE(type, line, fg, bg, attr) \
546         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
547         LINE_INFO
548 #undef  LINE
549 };
551 static enum line_type
552 get_line_type(char *line)
554         int linelen = strlen(line);
555         enum line_type type;
557         for (type = 0; type < ARRAY_SIZE(line_info); type++)
558                 /* Case insensitive search matches Signed-off-by lines better. */
559                 if (linelen >= line_info[type].linelen &&
560                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
561                         return type;
563         return LINE_DEFAULT;
566 static inline int
567 get_line_attr(enum line_type type)
569         assert(type < ARRAY_SIZE(line_info));
570         return COLOR_PAIR(type) | line_info[type].attr;
573 static struct line_info *
574 get_line_info(char *name, int namelen)
576         enum line_type type;
578         for (type = 0; type < ARRAY_SIZE(line_info); type++)
579                 if (namelen == line_info[type].namelen &&
580                     !string_enum_compare(line_info[type].name, name, namelen))
581                         return &line_info[type];
583         return NULL;
586 static void
587 init_colors(void)
589         int default_bg = COLOR_BLACK;
590         int default_fg = COLOR_WHITE;
591         enum line_type type;
593         start_color();
595         if (use_default_colors() != ERR) {
596                 default_bg = -1;
597                 default_fg = -1;
598         }
600         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
601                 struct line_info *info = &line_info[type];
602                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
603                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
605                 init_pair(type, fg, bg);
606         }
609 struct line {
610         enum line_type type;
611         void *data;             /* User data */
612 };
615 /*
616  * Keys
617  */
619 struct keymap {
620         int alias;
621         int request;
622 };
624 static struct keymap keymap[] = {
625         /* View switching */
626         { 'm',          REQ_VIEW_MAIN },
627         { 'd',          REQ_VIEW_DIFF },
628         { 'l',          REQ_VIEW_LOG },
629         { 'p',          REQ_VIEW_PAGER },
630         { 'h',          REQ_VIEW_HELP },
631         { '?',          REQ_VIEW_HELP },
633         /* View manipulation */
634         { 'q',          REQ_VIEW_CLOSE },
635         { KEY_TAB,      REQ_VIEW_NEXT },
636         { KEY_RETURN,   REQ_ENTER },
637         { KEY_UP,       REQ_PREVIOUS },
638         { KEY_DOWN,     REQ_NEXT },
640         /* Cursor navigation */
641         { 'k',          REQ_MOVE_UP },
642         { 'j',          REQ_MOVE_DOWN },
643         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
644         { KEY_END,      REQ_MOVE_LAST_LINE },
645         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
646         { ' ',          REQ_MOVE_PAGE_DOWN },
647         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
648         { 'b',          REQ_MOVE_PAGE_UP },
649         { '-',          REQ_MOVE_PAGE_UP },
651         /* Scrolling */
652         { KEY_IC,       REQ_SCROLL_LINE_UP },
653         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
654         { 'w',          REQ_SCROLL_PAGE_UP },
655         { 's',          REQ_SCROLL_PAGE_DOWN },
657         /* Misc */
658         { 'Q',          REQ_QUIT },
659         { 'z',          REQ_STOP_LOADING },
660         { 'v',          REQ_SHOW_VERSION },
661         { 'r',          REQ_SCREEN_REDRAW },
662         { 'n',          REQ_TOGGLE_LINENO },
663         { 'g',          REQ_TOGGLE_REV_GRAPH},
664         { ':',          REQ_PROMPT },
666         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
667         { ERR,          REQ_SCREEN_UPDATE },
669         /* Use the ncurses SIGWINCH handler. */
670         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
671 };
673 static enum request
674 get_request(int key)
676         int i;
678         for (i = 0; i < ARRAY_SIZE(keymap); i++)
679                 if (keymap[i].alias == key)
680                         return keymap[i].request;
682         return (enum request) key;
685 struct key {
686         char *name;
687         int value;
688 };
690 static struct key key_table[] = {
691         { "Enter",      KEY_RETURN },
692         { "Space",      ' ' },
693         { "Backspace",  KEY_BACKSPACE },
694         { "Tab",        KEY_TAB },
695         { "Escape",     KEY_ESC },
696         { "Left",       KEY_LEFT },
697         { "Right",      KEY_RIGHT },
698         { "Up",         KEY_UP },
699         { "Down",       KEY_DOWN },
700         { "Insert",     KEY_IC },
701         { "Delete",     KEY_DC },
702         { "Home",       KEY_HOME },
703         { "End",        KEY_END },
704         { "PageUp",     KEY_PPAGE },
705         { "PageDown",   KEY_NPAGE },
706         { "F1",         KEY_F(1) },
707         { "F2",         KEY_F(2) },
708         { "F3",         KEY_F(3) },
709         { "F4",         KEY_F(4) },
710         { "F5",         KEY_F(5) },
711         { "F6",         KEY_F(6) },
712         { "F7",         KEY_F(7) },
713         { "F8",         KEY_F(8) },
714         { "F9",         KEY_F(9) },
715         { "F10",        KEY_F(10) },
716         { "F11",        KEY_F(11) },
717         { "F12",        KEY_F(12) },
718 };
720 static char *
721 get_key(enum request request)
723         static char buf[BUFSIZ];
724         static char key_char[] = "'X'";
725         int pos = 0;
726         char *sep = "    ";
727         int i;
729         buf[pos] = 0;
731         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
732                 char *seq = NULL;
733                 int key;
735                 if (keymap[i].request != request)
736                         continue;
738                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
739                         if (key_table[key].value == keymap[i].alias)
740                                 seq = key_table[key].name;
742                 if (seq == NULL &&
743                     keymap[i].alias < 127 &&
744                     isprint(keymap[i].alias)) {
745                         key_char[1] = (char) keymap[i].alias;
746                         seq = key_char;
747                 }
749                 if (!seq)
750                         seq = "'?'";
752                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
753                         return "Too many keybindings!";
754                 sep = ", ";
755         }
757         return buf;
761 /*
762  * User config file handling.
763  */
765 static struct int_map color_map[] = {
766 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
767         COLOR_MAP(DEFAULT),
768         COLOR_MAP(BLACK),
769         COLOR_MAP(BLUE),
770         COLOR_MAP(CYAN),
771         COLOR_MAP(GREEN),
772         COLOR_MAP(MAGENTA),
773         COLOR_MAP(RED),
774         COLOR_MAP(WHITE),
775         COLOR_MAP(YELLOW),
776 };
778 #define set_color(color, name) \
779         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
781 static struct int_map attr_map[] = {
782 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
783         ATTR_MAP(NORMAL),
784         ATTR_MAP(BLINK),
785         ATTR_MAP(BOLD),
786         ATTR_MAP(DIM),
787         ATTR_MAP(REVERSE),
788         ATTR_MAP(STANDOUT),
789         ATTR_MAP(UNDERLINE),
790 };
792 #define set_attribute(attr, name) \
793         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
795 static int   config_lineno;
796 static bool  config_errors;
797 static char *config_msg;
799 /* Wants: object fgcolor bgcolor [attr] */
800 static int
801 option_color_command(int argc, char *argv[])
803         struct line_info *info;
805         if (argc != 3 && argc != 4) {
806                 config_msg = "Wrong number of arguments given to color command";
807                 return ERR;
808         }
810         info = get_line_info(argv[0], strlen(argv[0]));
811         if (!info) {
812                 config_msg = "Unknown color name";
813                 return ERR;
814         }
816         if (set_color(&info->fg, argv[1]) == ERR) {
817                 config_msg = "Unknown color";
818                 return ERR;
819         }
821         if (set_color(&info->bg, argv[2]) == ERR) {
822                 config_msg = "Unknown color";
823                 return ERR;
824         }
826         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
827                 config_msg = "Unknown attribute";
828                 return ERR;
829         }
831         return OK;
834 /* Wants: name = value */
835 static int
836 option_set_command(int argc, char *argv[])
838         if (argc != 3) {
839                 config_msg = "Wrong number of arguments given to set command";
840                 return ERR;
841         }
843         if (strcmp(argv[1], "=")) {
844                 config_msg = "No value assigned";
845                 return ERR;
846         }
848         if (!strcmp(argv[0], "show-rev-graph")) {
849                 opt_rev_graph = (!strcmp(argv[2], "1") ||
850                                  !strcmp(argv[2], "true") ||
851                                  !strcmp(argv[2], "yes"));
852                 return OK;
853         }
855         if (!strcmp(argv[0], "line-number-interval")) {
856                 opt_num_interval = atoi(argv[2]);
857                 return OK;
858         }
860         if (!strcmp(argv[0], "tab-size")) {
861                 opt_tab_size = atoi(argv[2]);
862                 return OK;
863         }
865         if (!strcmp(argv[0], "encoding")) {
866                 string_copy(opt_encoding, argv[2]);
867                 return OK;
868         }
870         return ERR;
873 static int
874 set_option(char *opt, char *value)
876         char *argv[16];
877         int valuelen;
878         int argc = 0;
880         /* Tokenize */
881         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
882                 argv[argc++] = value;
884                 value += valuelen;
885                 if (!*value)
886                         break;
888                 *value++ = 0;
889                 while (isspace(*value))
890                         value++;
891         }
893         if (!strcmp(opt, "color"))
894                 return option_color_command(argc, argv);
896         if (!strcmp(opt, "set"))
897                 return option_set_command(argc, argv);
899         return ERR;
902 static int
903 read_option(char *opt, int optlen, char *value, int valuelen)
905         config_lineno++;
906         config_msg = "Internal error";
908         optlen = strcspn(opt, "#;");
909         if (optlen == 0) {
910                 /* The whole line is a commend or empty. */
911                 return OK;
913         } else if (opt[optlen] != 0) {
914                 /* Part of the option name is a comment, so the value part
915                  * should be ignored. */
916                 valuelen = 0;
917                 opt[optlen] = value[valuelen] = 0;
918         } else {
919                 /* Else look for comment endings in the value. */
920                 valuelen = strcspn(value, "#;");
921                 value[valuelen] = 0;
922         }
924         if (set_option(opt, value) == ERR) {
925                 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
926                         config_lineno, optlen, opt, config_msg);
927                 config_errors = TRUE;
928         }
930         /* Always keep going if errors are encountered. */
931         return OK;
934 static int
935 load_options(void)
937         char *home = getenv("HOME");
938         char buf[1024];
939         FILE *file;
941         config_lineno = 0;
942         config_errors = FALSE;
944         if (!home || !string_format(buf, "%s/.tigrc", home))
945                 return ERR;
947         /* It's ok that the file doesn't exist. */
948         file = fopen(buf, "r");
949         if (!file)
950                 return OK;
952         if (read_properties(file, " \t", read_option) == ERR ||
953             config_errors == TRUE)
954                 fprintf(stderr, "Errors while loading %s.\n", buf);
956         return OK;
960 /*
961  * The viewer
962  */
964 struct view;
965 struct view_ops;
967 /* The display array of active views and the index of the current view. */
968 static struct view *display[2];
969 static unsigned int current_view;
971 #define foreach_view(view, i) \
972         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
974 #define displayed_views()       (display[1] != NULL ? 2 : 1)
976 /* Current head and commit ID */
977 static char ref_commit[SIZEOF_REF]      = "HEAD";
978 static char ref_head[SIZEOF_REF]        = "HEAD";
980 struct view {
981         const char *name;       /* View name */
982         const char *cmd_fmt;    /* Default command line format */
983         const char *cmd_env;    /* Command line set via environment */
984         const char *id;         /* Points to either of ref_{head,commit} */
986         struct view_ops *ops;   /* View operations */
988         char cmd[SIZEOF_CMD];   /* Command buffer */
989         char ref[SIZEOF_REF];   /* Hovered commit reference */
990         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
992         int height, width;      /* The width and height of the main window */
993         WINDOW *win;            /* The main window */
994         WINDOW *title;          /* The title window living below the main window */
996         /* Navigation */
997         unsigned long offset;   /* Offset of the window top */
998         unsigned long lineno;   /* Current line number */
1000         /* If non-NULL, points to the view that opened this view. If this view
1001          * is closed tig will switch back to the parent view. */
1002         struct view *parent;
1004         /* Buffering */
1005         unsigned long lines;    /* Total number of lines */
1006         struct line *line;      /* Line index */
1007         unsigned long line_size;/* Total number of allocated lines */
1008         unsigned int digits;    /* Number of digits in the lines member. */
1010         /* Loading */
1011         FILE *pipe;
1012         time_t start_time;
1013 };
1015 struct view_ops {
1016         /* What type of content being displayed. Used in the title bar. */
1017         const char *type;
1018         /* Draw one line; @lineno must be < view->height. */
1019         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1020         /* Read one line; updates view->line. */
1021         bool (*read)(struct view *view, char *data);
1022         /* Depending on view, change display based on current line. */
1023         bool (*enter)(struct view *view, struct line *line);
1024 };
1026 static struct view_ops pager_ops;
1027 static struct view_ops main_ops;
1029 #define VIEW_STR(name, cmd, env, ref, ops) \
1030         { name, cmd, #env, ref, ops }
1032 #define VIEW_(id, name, ops, ref) \
1033         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
1036 static struct view views[] = {
1037         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1038         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1039         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1040         VIEW_(HELP,  "help",  &pager_ops, "static"),
1041         VIEW_(PAGER, "pager", &pager_ops, "static"),
1042 };
1044 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1047 static bool
1048 draw_view_line(struct view *view, unsigned int lineno)
1050         if (view->offset + lineno >= view->lines)
1051                 return FALSE;
1053         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1056 static void
1057 redraw_view_from(struct view *view, int lineno)
1059         assert(0 <= lineno && lineno < view->height);
1061         for (; lineno < view->height; lineno++) {
1062                 if (!draw_view_line(view, lineno))
1063                         break;
1064         }
1066         redrawwin(view->win);
1067         wrefresh(view->win);
1070 static void
1071 redraw_view(struct view *view)
1073         wclear(view->win);
1074         redraw_view_from(view, 0);
1078 static void
1079 update_view_title(struct view *view)
1081         if (view == display[current_view])
1082                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1083         else
1084                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1086         werase(view->title);
1087         wmove(view->title, 0, 0);
1089         if (*view->ref)
1090                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1091         else
1092                 wprintw(view->title, "[%s]", view->name);
1094         if (view->lines || view->pipe) {
1095                 unsigned int view_lines = view->offset + view->height;
1096                 unsigned int lines = view->lines
1097                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1098                                    : 0;
1100                 wprintw(view->title, " - %s %d of %d (%d%%)",
1101                         view->ops->type,
1102                         view->lineno + 1,
1103                         view->lines,
1104                         lines);
1105         }
1107         if (view->pipe) {
1108                 time_t secs = time(NULL) - view->start_time;
1110                 /* Three git seconds are a long time ... */
1111                 if (secs > 2)
1112                         wprintw(view->title, " %lds", secs);
1113         }
1115         wmove(view->title, 0, view->width - 1);
1116         wrefresh(view->title);
1119 static void
1120 resize_display(void)
1122         int offset, i;
1123         struct view *base = display[0];
1124         struct view *view = display[1] ? display[1] : display[0];
1126         /* Setup window dimensions */
1128         getmaxyx(stdscr, base->height, base->width);
1130         /* Make room for the status window. */
1131         base->height -= 1;
1133         if (view != base) {
1134                 /* Horizontal split. */
1135                 view->width   = base->width;
1136                 view->height  = SCALE_SPLIT_VIEW(base->height);
1137                 base->height -= view->height;
1139                 /* Make room for the title bar. */
1140                 view->height -= 1;
1141         }
1143         /* Make room for the title bar. */
1144         base->height -= 1;
1146         offset = 0;
1148         foreach_view (view, i) {
1149                 if (!view->win) {
1150                         view->win = newwin(view->height, 0, offset, 0);
1151                         if (!view->win)
1152                                 die("Failed to create %s view", view->name);
1154                         scrollok(view->win, TRUE);
1156                         view->title = newwin(1, 0, offset + view->height, 0);
1157                         if (!view->title)
1158                                 die("Failed to create title window");
1160                 } else {
1161                         wresize(view->win, view->height, view->width);
1162                         mvwin(view->win,   offset, 0);
1163                         mvwin(view->title, offset + view->height, 0);
1164                 }
1166                 offset += view->height + 1;
1167         }
1170 static void
1171 redraw_display(void)
1173         struct view *view;
1174         int i;
1176         foreach_view (view, i) {
1177                 redraw_view(view);
1178                 update_view_title(view);
1179         }
1182 static void
1183 update_display_cursor(void)
1185         struct view *view = display[current_view];
1187         /* Move the cursor to the right-most column of the cursor line.
1188          *
1189          * XXX: This could turn out to be a bit expensive, but it ensures that
1190          * the cursor does not jump around. */
1191         if (view->lines) {
1192                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1193                 wrefresh(view->win);
1194         }
1197 /*
1198  * Navigation
1199  */
1201 /* Scrolling backend */
1202 static void
1203 do_scroll_view(struct view *view, int lines, bool redraw)
1205         /* The rendering expects the new offset. */
1206         view->offset += lines;
1208         assert(0 <= view->offset && view->offset < view->lines);
1209         assert(lines);
1211         /* Redraw the whole screen if scrolling is pointless. */
1212         if (view->height < ABS(lines)) {
1213                 redraw_view(view);
1215         } else {
1216                 int line = lines > 0 ? view->height - lines : 0;
1217                 int end = line + ABS(lines);
1219                 wscrl(view->win, lines);
1221                 for (; line < end; line++) {
1222                         if (!draw_view_line(view, line))
1223                                 break;
1224                 }
1225         }
1227         /* Move current line into the view. */
1228         if (view->lineno < view->offset) {
1229                 view->lineno = view->offset;
1230                 draw_view_line(view, 0);
1232         } else if (view->lineno >= view->offset + view->height) {
1233                 if (view->lineno == view->offset + view->height) {
1234                         /* Clear the hidden line so it doesn't show if the view
1235                          * is scrolled up. */
1236                         wmove(view->win, view->height, 0);
1237                         wclrtoeol(view->win);
1238                 }
1239                 view->lineno = view->offset + view->height - 1;
1240                 draw_view_line(view, view->lineno - view->offset);
1241         }
1243         assert(view->offset <= view->lineno && view->lineno < view->lines);
1245         if (!redraw)
1246                 return;
1248         redrawwin(view->win);
1249         wrefresh(view->win);
1250         report("");
1253 /* Scroll frontend */
1254 static void
1255 scroll_view(struct view *view, enum request request)
1257         int lines = 1;
1259         switch (request) {
1260         case REQ_SCROLL_PAGE_DOWN:
1261                 lines = view->height;
1262         case REQ_SCROLL_LINE_DOWN:
1263                 if (view->offset + lines > view->lines)
1264                         lines = view->lines - view->offset;
1266                 if (lines == 0 || view->offset + view->height >= view->lines) {
1267                         report("Cannot scroll beyond the last line");
1268                         return;
1269                 }
1270                 break;
1272         case REQ_SCROLL_PAGE_UP:
1273                 lines = view->height;
1274         case REQ_SCROLL_LINE_UP:
1275                 if (lines > view->offset)
1276                         lines = view->offset;
1278                 if (lines == 0) {
1279                         report("Cannot scroll beyond the first line");
1280                         return;
1281                 }
1283                 lines = -lines;
1284                 break;
1286         default:
1287                 die("request %d not handled in switch", request);
1288         }
1290         do_scroll_view(view, lines, TRUE);
1293 /* Cursor moving */
1294 static void
1295 move_view(struct view *view, enum request request, bool redraw)
1297         int steps;
1299         switch (request) {
1300         case REQ_MOVE_FIRST_LINE:
1301                 steps = -view->lineno;
1302                 break;
1304         case REQ_MOVE_LAST_LINE:
1305                 steps = view->lines - view->lineno - 1;
1306                 break;
1308         case REQ_MOVE_PAGE_UP:
1309                 steps = view->height > view->lineno
1310                       ? -view->lineno : -view->height;
1311                 break;
1313         case REQ_MOVE_PAGE_DOWN:
1314                 steps = view->lineno + view->height >= view->lines
1315                       ? view->lines - view->lineno - 1 : view->height;
1316                 break;
1318         case REQ_MOVE_UP:
1319                 steps = -1;
1320                 break;
1322         case REQ_MOVE_DOWN:
1323                 steps = 1;
1324                 break;
1326         default:
1327                 die("request %d not handled in switch", request);
1328         }
1330         if (steps <= 0 && view->lineno == 0) {
1331                 report("Cannot move beyond the first line");
1332                 return;
1334         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1335                 report("Cannot move beyond the last line");
1336                 return;
1337         }
1339         /* Move the current line */
1340         view->lineno += steps;
1341         assert(0 <= view->lineno && view->lineno < view->lines);
1343         /* Repaint the old "current" line if we be scrolling */
1344         if (ABS(steps) < view->height) {
1345                 int prev_lineno = view->lineno - steps - view->offset;
1347                 wmove(view->win, prev_lineno, 0);
1348                 wclrtoeol(view->win);
1349                 draw_view_line(view,  prev_lineno);
1350         }
1352         /* Check whether the view needs to be scrolled */
1353         if (view->lineno < view->offset ||
1354             view->lineno >= view->offset + view->height) {
1355                 if (steps < 0 && -steps > view->offset) {
1356                         steps = -view->offset;
1358                 } else if (steps > 0) {
1359                         if (view->lineno == view->lines - 1 &&
1360                             view->lines > view->height) {
1361                                 steps = view->lines - view->offset - 1;
1362                                 if (steps >= view->height)
1363                                         steps -= view->height - 1;
1364                         }
1365                 }
1367                 do_scroll_view(view, steps, redraw);
1368                 return;
1369         }
1371         /* Draw the current line */
1372         draw_view_line(view, view->lineno - view->offset);
1374         if (!redraw)
1375                 return;
1377         redrawwin(view->win);
1378         wrefresh(view->win);
1379         report("");
1383 /*
1384  * Incremental updating
1385  */
1387 static void
1388 end_update(struct view *view)
1390         if (!view->pipe)
1391                 return;
1392         set_nonblocking_input(FALSE);
1393         if (view->pipe == stdin)
1394                 fclose(view->pipe);
1395         else
1396                 pclose(view->pipe);
1397         view->pipe = NULL;
1400 static bool
1401 begin_update(struct view *view)
1403         const char *id = view->id;
1405         if (view->pipe)
1406                 end_update(view);
1408         if (opt_cmd[0]) {
1409                 string_copy(view->cmd, opt_cmd);
1410                 opt_cmd[0] = 0;
1411                 /* When running random commands, the view ref could have become
1412                  * invalid so clear it. */
1413                 view->ref[0] = 0;
1414         } else {
1415                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1417                 if (!string_format(view->cmd, format, id, id, id, id, id))
1418                         return FALSE;
1419         }
1421         /* Special case for the pager view. */
1422         if (opt_pipe) {
1423                 view->pipe = opt_pipe;
1424                 opt_pipe = NULL;
1425         } else {
1426                 view->pipe = popen(view->cmd, "r");
1427         }
1429         if (!view->pipe)
1430                 return FALSE;
1432         set_nonblocking_input(TRUE);
1434         view->offset = 0;
1435         view->lines  = 0;
1436         view->lineno = 0;
1437         string_copy(view->vid, id);
1439         if (view->line) {
1440                 int i;
1442                 for (i = 0; i < view->lines; i++)
1443                         if (view->line[i].data)
1444                                 free(view->line[i].data);
1446                 free(view->line);
1447                 view->line = NULL;
1448         }
1450         view->start_time = time(NULL);
1452         return TRUE;
1455 static struct line *
1456 realloc_lines(struct view *view, size_t line_size)
1458         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1460         if (!tmp)
1461                 return NULL;
1463         view->line = tmp;
1464         view->line_size = line_size;
1465         return view->line;
1468 static bool
1469 update_view(struct view *view)
1471         char buffer[BUFSIZ];
1472         char *line;
1473         /* The number of lines to read. If too low it will cause too much
1474          * redrawing (and possible flickering), if too high responsiveness
1475          * will suffer. */
1476         unsigned long lines = view->height;
1477         int redraw_from = -1;
1479         if (!view->pipe)
1480                 return TRUE;
1482         /* Only redraw if lines are visible. */
1483         if (view->offset + view->height >= view->lines)
1484                 redraw_from = view->lines - view->offset;
1486         if (!realloc_lines(view, view->lines + lines))
1487                 goto alloc_error;
1489         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1490                 int linelen = strlen(line);
1492                 if (linelen)
1493                         line[linelen - 1] = 0;
1495                 if (!view->ops->read(view, line))
1496                         goto alloc_error;
1498                 if (lines-- == 1)
1499                         break;
1500         }
1502         {
1503                 int digits;
1505                 lines = view->lines;
1506                 for (digits = 0; lines; digits++)
1507                         lines /= 10;
1509                 /* Keep the displayed view in sync with line number scaling. */
1510                 if (digits != view->digits) {
1511                         view->digits = digits;
1512                         redraw_from = 0;
1513                 }
1514         }
1516         if (redraw_from >= 0) {
1517                 /* If this is an incremental update, redraw the previous line
1518                  * since for commits some members could have changed when
1519                  * loading the main view. */
1520                 if (redraw_from > 0)
1521                         redraw_from--;
1523                 /* Incrementally draw avoids flickering. */
1524                 redraw_view_from(view, redraw_from);
1525         }
1527         /* Update the title _after_ the redraw so that if the redraw picks up a
1528          * commit reference in view->ref it'll be available here. */
1529         update_view_title(view);
1531         if (ferror(view->pipe)) {
1532                 report("Failed to read: %s", strerror(errno));
1533                 goto end;
1535         } else if (feof(view->pipe)) {
1536                 report("");
1537                 goto end;
1538         }
1540         return TRUE;
1542 alloc_error:
1543         report("Allocation failure");
1545 end:
1546         end_update(view);
1547         return FALSE;
1551 /*
1552  * View opening
1553  */
1555 static void open_help_view(struct view *view)
1557         char buf[BUFSIZ];
1558         int lines = ARRAY_SIZE(req_info) + 2;
1559         int i;
1561         if (view->lines > 0)
1562                 return;
1564         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1565                 if (!req_info[i].request)
1566                         lines++;
1568         view->line = calloc(lines, sizeof(*view->line));
1569         if (!view->line) {
1570                 report("Allocation failure");
1571                 return;
1572         }
1574         view->ops->read(view, "Quick reference for tig keybindings:");
1576         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1577                 char *key;
1579                 if (!req_info[i].request) {
1580                         view->ops->read(view, "");
1581                         view->ops->read(view, req_info[i].help);
1582                         continue;
1583                 }
1585                 key = get_key(req_info[i].request);
1586                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1587                         continue;
1589                 view->ops->read(view, buf);
1590         }
1593 enum open_flags {
1594         OPEN_DEFAULT = 0,       /* Use default view switching. */
1595         OPEN_SPLIT = 1,         /* Split current view. */
1596         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1597         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1598 };
1600 static void
1601 open_view(struct view *prev, enum request request, enum open_flags flags)
1603         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1604         bool split = !!(flags & OPEN_SPLIT);
1605         bool reload = !!(flags & OPEN_RELOAD);
1606         struct view *view = VIEW(request);
1607         int nviews = displayed_views();
1608         struct view *base_view = display[0];
1610         if (view == prev && nviews == 1 && !reload) {
1611                 report("Already in %s view", view->name);
1612                 return;
1613         }
1615         if (view == VIEW(REQ_VIEW_HELP)) {
1616                 open_help_view(view);
1618         } else if ((reload || strcmp(view->vid, view->id)) &&
1619                    !begin_update(view)) {
1620                 report("Failed to load %s view", view->name);
1621                 return;
1622         }
1624         if (split) {
1625                 display[1] = view;
1626                 if (!backgrounded)
1627                         current_view = 1;
1628         } else {
1629                 /* Maximize the current view. */
1630                 memset(display, 0, sizeof(display));
1631                 current_view = 0;
1632                 display[current_view] = view;
1633         }
1635         /* Resize the view when switching between split- and full-screen,
1636          * or when switching between two different full-screen views. */
1637         if (nviews != displayed_views() ||
1638             (nviews == 1 && base_view != display[0]))
1639                 resize_display();
1641         if (split && prev->lineno - prev->offset >= prev->height) {
1642                 /* Take the title line into account. */
1643                 int lines = prev->lineno - prev->offset - prev->height + 1;
1645                 /* Scroll the view that was split if the current line is
1646                  * outside the new limited view. */
1647                 do_scroll_view(prev, lines, TRUE);
1648         }
1650         if (prev && view != prev) {
1651                 if (split && !backgrounded) {
1652                         /* "Blur" the previous view. */
1653                         update_view_title(prev);
1654                 }
1656                 view->parent = prev;
1657         }
1659         if (view->pipe && view->lines == 0) {
1660                 /* Clear the old view and let the incremental updating refill
1661                  * the screen. */
1662                 wclear(view->win);
1663                 report("");
1664         } else {
1665                 redraw_view(view);
1666                 report("");
1667         }
1669         /* If the view is backgrounded the above calls to report()
1670          * won't redraw the view title. */
1671         if (backgrounded)
1672                 update_view_title(view);
1676 /*
1677  * User request switch noodle
1678  */
1680 static int
1681 view_driver(struct view *view, enum request request)
1683         int i;
1685         switch (request) {
1686         case REQ_MOVE_UP:
1687         case REQ_MOVE_DOWN:
1688         case REQ_MOVE_PAGE_UP:
1689         case REQ_MOVE_PAGE_DOWN:
1690         case REQ_MOVE_FIRST_LINE:
1691         case REQ_MOVE_LAST_LINE:
1692                 move_view(view, request, TRUE);
1693                 break;
1695         case REQ_SCROLL_LINE_DOWN:
1696         case REQ_SCROLL_LINE_UP:
1697         case REQ_SCROLL_PAGE_DOWN:
1698         case REQ_SCROLL_PAGE_UP:
1699                 scroll_view(view, request);
1700                 break;
1702         case REQ_VIEW_MAIN:
1703         case REQ_VIEW_DIFF:
1704         case REQ_VIEW_LOG:
1705         case REQ_VIEW_HELP:
1706         case REQ_VIEW_PAGER:
1707                 open_view(view, request, OPEN_DEFAULT);
1708                 break;
1710         case REQ_NEXT:
1711         case REQ_PREVIOUS:
1712                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1714                 if (view == VIEW(REQ_VIEW_DIFF) &&
1715                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1716                         bool redraw = display[1] == view;
1718                         view = view->parent;
1719                         move_view(view, request, redraw);
1720                         if (redraw)
1721                                 update_view_title(view);
1722                 } else {
1723                         move_view(view, request, TRUE);
1724                         break;
1725                 }
1726                 /* Fall-through */
1728         case REQ_ENTER:
1729                 if (!view->lines) {
1730                         report("Nothing to enter");
1731                         break;
1732                 }
1733                 return view->ops->enter(view, &view->line[view->lineno]);
1735         case REQ_VIEW_NEXT:
1736         {
1737                 int nviews = displayed_views();
1738                 int next_view = (current_view + 1) % nviews;
1740                 if (next_view == current_view) {
1741                         report("Only one view is displayed");
1742                         break;
1743                 }
1745                 current_view = next_view;
1746                 /* Blur out the title of the previous view. */
1747                 update_view_title(view);
1748                 report("");
1749                 break;
1750         }
1751         case REQ_TOGGLE_LINENO:
1752                 opt_line_number = !opt_line_number;
1753                 redraw_display();
1754                 break;
1756         case REQ_TOGGLE_REV_GRAPH:
1757                 opt_rev_graph = !opt_rev_graph;
1758                 redraw_display();
1759                 break;
1761         case REQ_PROMPT:
1762                 /* Always reload^Wrerun commands from the prompt. */
1763                 open_view(view, opt_request, OPEN_RELOAD);
1764                 break;
1766         case REQ_STOP_LOADING:
1767                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1768                         view = &views[i];
1769                         if (view->pipe)
1770                                 report("Stopped loading the %s view", view->name),
1771                         end_update(view);
1772                 }
1773                 break;
1775         case REQ_SHOW_VERSION:
1776                 report("%s (built %s)", VERSION, __DATE__);
1777                 return TRUE;
1779         case REQ_SCREEN_RESIZE:
1780                 resize_display();
1781                 /* Fall-through */
1782         case REQ_SCREEN_REDRAW:
1783                 redraw_display();
1784                 break;
1786         case REQ_SCREEN_UPDATE:
1787                 doupdate();
1788                 return TRUE;
1790         case REQ_VIEW_CLOSE:
1791                 /* XXX: Mark closed views by letting view->parent point to the
1792                  * view itself. Parents to closed view should never be
1793                  * followed. */
1794                 if (view->parent &&
1795                     view->parent->parent != view->parent) {
1796                         memset(display, 0, sizeof(display));
1797                         current_view = 0;
1798                         display[current_view] = view->parent;
1799                         view->parent = view;
1800                         resize_display();
1801                         redraw_display();
1802                         break;
1803                 }
1804                 /* Fall-through */
1805         case REQ_QUIT:
1806                 return FALSE;
1808         default:
1809                 /* An unknown key will show most commonly used commands. */
1810                 report("Unknown key, press 'h' for help");
1811                 return TRUE;
1812         }
1814         return TRUE;
1818 /*
1819  * Pager backend
1820  */
1822 static bool
1823 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1825         char *text = line->data;
1826         enum line_type type = line->type;
1827         int textlen = strlen(text);
1828         int attr;
1830         wmove(view->win, lineno, 0);
1832         if (view->offset + lineno == view->lineno) {
1833                 if (type == LINE_COMMIT) {
1834                         string_copy(view->ref, text + 7);
1835                         string_copy(ref_commit, view->ref);
1836                 }
1838                 type = LINE_CURSOR;
1839                 wchgat(view->win, -1, 0, type, NULL);
1840         }
1842         attr = get_line_attr(type);
1843         wattrset(view->win, attr);
1845         if (opt_line_number || opt_tab_size < TABSIZE) {
1846                 static char spaces[] = "                    ";
1847                 int col_offset = 0, col = 0;
1849                 if (opt_line_number) {
1850                         unsigned long real_lineno = view->offset + lineno + 1;
1852                         if (real_lineno == 1 ||
1853                             (real_lineno % opt_num_interval) == 0) {
1854                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1856                         } else {
1857                                 waddnstr(view->win, spaces,
1858                                          MIN(view->digits, STRING_SIZE(spaces)));
1859                         }
1860                         waddstr(view->win, ": ");
1861                         col_offset = view->digits + 2;
1862                 }
1864                 while (text && col_offset + col < view->width) {
1865                         int cols_max = view->width - col_offset - col;
1866                         char *pos = text;
1867                         int cols;
1869                         if (*text == '\t') {
1870                                 text++;
1871                                 assert(sizeof(spaces) > TABSIZE);
1872                                 pos = spaces;
1873                                 cols = opt_tab_size - (col % opt_tab_size);
1875                         } else {
1876                                 text = strchr(text, '\t');
1877                                 cols = line ? text - pos : strlen(pos);
1878                         }
1880                         waddnstr(view->win, pos, MIN(cols, cols_max));
1881                         col += cols;
1882                 }
1884         } else {
1885                 int col = 0, pos = 0;
1887                 for (; pos < textlen && col < view->width; pos++, col++)
1888                         if (text[pos] == '\t')
1889                                 col += TABSIZE - (col % TABSIZE) - 1;
1891                 waddnstr(view->win, text, pos);
1892         }
1894         return TRUE;
1897 static void
1898 add_pager_refs(struct view *view, struct line *line)
1900         char buf[1024];
1901         char *data = line->data;
1902         struct ref **refs;
1903         int bufpos = 0, refpos = 0;
1904         const char *sep = "Refs: ";
1906         assert(line->type == LINE_COMMIT);
1908         refs = get_refs(data + STRING_SIZE("commit "));
1909         if (!refs)
1910                 return;
1912         do {
1913                 struct ref *ref = refs[refpos];
1914                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1916                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1917                         return;
1918                 sep = ", ";
1919         } while (refs[refpos++]->next);
1921         if (!realloc_lines(view, view->line_size + 1))
1922                 return;
1924         line = &view->line[view->lines];
1925         line->data = strdup(buf);
1926         if (!line->data)
1927                 return;
1929         line->type = LINE_PP_REFS;
1930         view->lines++;
1933 static bool
1934 pager_read(struct view *view, char *data)
1936         struct line *line = &view->line[view->lines];
1938         line->data = strdup(data);
1939         if (!line->data)
1940                 return FALSE;
1942         line->type = get_line_type(line->data);
1943         view->lines++;
1945         if (line->type == LINE_COMMIT &&
1946             (view == VIEW(REQ_VIEW_DIFF) ||
1947              view == VIEW(REQ_VIEW_LOG)))
1948                 add_pager_refs(view, line);
1950         return TRUE;
1953 static bool
1954 pager_enter(struct view *view, struct line *line)
1956         int split = 0;
1958         if (line->type == LINE_COMMIT &&
1959            (view == VIEW(REQ_VIEW_LOG) ||
1960             view == VIEW(REQ_VIEW_PAGER))) {
1961                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1962                 split = 1;
1963         }
1965         /* Always scroll the view even if it was split. That way
1966          * you can use Enter to scroll through the log view and
1967          * split open each commit diff. */
1968         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1970         /* FIXME: A minor workaround. Scrolling the view will call report("")
1971          * but if we are scrolling a non-current view this won't properly
1972          * update the view title. */
1973         if (split)
1974                 update_view_title(view);
1976         return TRUE;
1979 static struct view_ops pager_ops = {
1980         "line",
1981         pager_draw,
1982         pager_read,
1983         pager_enter,
1984 };
1987 /*
1988  * Main view backend
1989  */
1991 struct commit {
1992         char id[41];                    /* SHA1 ID. */
1993         char title[75];                 /* First line of the commit message. */
1994         char author[75];                /* Author of the commit. */
1995         struct tm time;                 /* Date from the author ident. */
1996         struct ref **refs;              /* Repository references. */
1997         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
1998         size_t graph_size;              /* The width of the graph array. */
1999 };
2001 static bool
2002 main_draw(struct view *view, struct line *line, unsigned int lineno)
2004         char buf[DATE_COLS + 1];
2005         struct commit *commit = line->data;
2006         enum line_type type;
2007         int col = 0;
2008         size_t timelen;
2009         size_t authorlen;
2010         int trimmed = 1;
2012         if (!*commit->author)
2013                 return FALSE;
2015         wmove(view->win, lineno, col);
2017         if (view->offset + lineno == view->lineno) {
2018                 string_copy(view->ref, commit->id);
2019                 string_copy(ref_commit, view->ref);
2020                 type = LINE_CURSOR;
2021                 wattrset(view->win, get_line_attr(type));
2022                 wchgat(view->win, -1, 0, type, NULL);
2024         } else {
2025                 type = LINE_MAIN_COMMIT;
2026                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2027         }
2029         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2030         waddnstr(view->win, buf, timelen);
2031         waddstr(view->win, " ");
2033         col += DATE_COLS;
2034         wmove(view->win, lineno, col);
2035         if (type != LINE_CURSOR)
2036                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2038         if (opt_utf8) {
2039                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2040         } else {
2041                 authorlen = strlen(commit->author);
2042                 if (authorlen > AUTHOR_COLS - 2) {
2043                         authorlen = AUTHOR_COLS - 2;
2044                         trimmed = 1;
2045                 }
2046         }
2048         if (trimmed) {
2049                 waddnstr(view->win, commit->author, authorlen);
2050                 if (type != LINE_CURSOR)
2051                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2052                 waddch(view->win, '~');
2053         } else {
2054                 waddstr(view->win, commit->author);
2055         }
2057         col += AUTHOR_COLS;
2058         if (type != LINE_CURSOR)
2059                 wattrset(view->win, A_NORMAL);
2061         if (opt_rev_graph && commit->graph_size) {
2062                 size_t i;
2064                 wmove(view->win, lineno, col);
2065                 /* Using waddch() instead of waddnstr() ensures that
2066                  * they'll be rendered correctly for the cursor line. */
2067                 for (i = 0; i < commit->graph_size; i++)
2068                         waddch(view->win, commit->graph[i]);
2070                 col += commit->graph_size + 1;
2071         }
2073         wmove(view->win, lineno, col);
2075         if (commit->refs) {
2076                 size_t i = 0;
2078                 do {
2079                         if (type == LINE_CURSOR)
2080                                 ;
2081                         else if (commit->refs[i]->tag)
2082                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2083                         else
2084                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2085                         waddstr(view->win, "[");
2086                         waddstr(view->win, commit->refs[i]->name);
2087                         waddstr(view->win, "]");
2088                         if (type != LINE_CURSOR)
2089                                 wattrset(view->win, A_NORMAL);
2090                         waddstr(view->win, " ");
2091                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2092                 } while (commit->refs[i++]->next);
2093         }
2095         if (type != LINE_CURSOR)
2096                 wattrset(view->win, get_line_attr(type));
2098         {
2099                 int titlelen = strlen(commit->title);
2101                 if (col + titlelen > view->width)
2102                         titlelen = view->width - col;
2104                 waddnstr(view->win, commit->title, titlelen);
2105         }
2107         return TRUE;
2110 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2111 static bool
2112 main_read(struct view *view, char *line)
2114         enum line_type type = get_line_type(line);
2115         struct commit *commit = view->lines
2116                               ? view->line[view->lines - 1].data : NULL;
2118         switch (type) {
2119         case LINE_COMMIT:
2120                 commit = calloc(1, sizeof(struct commit));
2121                 if (!commit)
2122                         return FALSE;
2124                 line += STRING_SIZE("commit ");
2126                 view->line[view->lines++].data = commit;
2127                 string_copy(commit->id, line);
2128                 commit->refs = get_refs(commit->id);
2129                 commit->graph[commit->graph_size++] = ACS_LTEE;
2130                 break;
2132         case LINE_AUTHOR:
2133         {
2134                 char *ident = line + STRING_SIZE("author ");
2135                 char *end = strchr(ident, '<');
2137                 if (!commit)
2138                         break;
2140                 if (end) {
2141                         for (; end > ident && isspace(end[-1]); end--) ;
2142                         *end = 0;
2143                 }
2145                 string_copy(commit->author, ident);
2147                 /* Parse epoch and timezone */
2148                 if (end) {
2149                         char *secs = strchr(end + 1, '>');
2150                         char *zone;
2151                         time_t time;
2153                         if (!secs || secs[1] != ' ')
2154                                 break;
2156                         secs += 2;
2157                         time = (time_t) atol(secs);
2158                         zone = strchr(secs, ' ');
2159                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2160                                 long tz;
2162                                 zone++;
2163                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2164                                 tz += ('0' - zone[2]) * 60 * 60;
2165                                 tz += ('0' - zone[3]) * 60;
2166                                 tz += ('0' - zone[4]) * 60;
2168                                 if (zone[0] == '-')
2169                                         tz = -tz;
2171                                 time -= tz;
2172                         }
2173                         gmtime_r(&time, &commit->time);
2174                 }
2175                 break;
2176         }
2177         default:
2178                 if (!commit)
2179                         break;
2181                 /* Fill in the commit title if it has not already been set. */
2182                 if (commit->title[0])
2183                         break;
2185                 /* Require titles to start with a non-space character at the
2186                  * offset used by git log. */
2187                 /* FIXME: More gracefull handling of titles; append "..." to
2188                  * shortened titles, etc. */
2189                 if (strncmp(line, "    ", 4) ||
2190                     isspace(line[4]))
2191                         break;
2193                 string_copy(commit->title, line + 4);
2194         }
2196         return TRUE;
2199 static bool
2200 main_enter(struct view *view, struct line *line)
2202         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2204         open_view(view, REQ_VIEW_DIFF, flags);
2205         return TRUE;
2208 static struct view_ops main_ops = {
2209         "commit",
2210         main_draw,
2211         main_read,
2212         main_enter,
2213 };
2216 /*
2217  * Unicode / UTF-8 handling
2218  *
2219  * NOTE: Much of the following code for dealing with unicode is derived from
2220  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2221  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2222  */
2224 /* I've (over)annotated a lot of code snippets because I am not entirely
2225  * confident that the approach taken by this small UTF-8 interface is correct.
2226  * --jonas */
2228 static inline int
2229 unicode_width(unsigned long c)
2231         if (c >= 0x1100 &&
2232            (c <= 0x115f                         /* Hangul Jamo */
2233             || c == 0x2329
2234             || c == 0x232a
2235             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2236                                                 /* CJK ... Yi */
2237             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2238             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2239             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2240             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2241             || (c >= 0xffe0  && c <= 0xffe6)
2242             || (c >= 0x20000 && c <= 0x2fffd)
2243             || (c >= 0x30000 && c <= 0x3fffd)))
2244                 return 2;
2246         return 1;
2249 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2250  * Illegal bytes are set one. */
2251 static const unsigned char utf8_bytes[256] = {
2252         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,
2253         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,
2254         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,
2255         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,
2256         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,
2257         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,
2258         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,
2259         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,
2260 };
2262 /* Decode UTF-8 multi-byte representation into a unicode character. */
2263 static inline unsigned long
2264 utf8_to_unicode(const char *string, size_t length)
2266         unsigned long unicode;
2268         switch (length) {
2269         case 1:
2270                 unicode  =   string[0];
2271                 break;
2272         case 2:
2273                 unicode  =  (string[0] & 0x1f) << 6;
2274                 unicode +=  (string[1] & 0x3f);
2275                 break;
2276         case 3:
2277                 unicode  =  (string[0] & 0x0f) << 12;
2278                 unicode += ((string[1] & 0x3f) << 6);
2279                 unicode +=  (string[2] & 0x3f);
2280                 break;
2281         case 4:
2282                 unicode  =  (string[0] & 0x0f) << 18;
2283                 unicode += ((string[1] & 0x3f) << 12);
2284                 unicode += ((string[2] & 0x3f) << 6);
2285                 unicode +=  (string[3] & 0x3f);
2286                 break;
2287         case 5:
2288                 unicode  =  (string[0] & 0x0f) << 24;
2289                 unicode += ((string[1] & 0x3f) << 18);
2290                 unicode += ((string[2] & 0x3f) << 12);
2291                 unicode += ((string[3] & 0x3f) << 6);
2292                 unicode +=  (string[4] & 0x3f);
2293                 break;
2294         case 6:
2295                 unicode  =  (string[0] & 0x01) << 30;
2296                 unicode += ((string[1] & 0x3f) << 24);
2297                 unicode += ((string[2] & 0x3f) << 18);
2298                 unicode += ((string[3] & 0x3f) << 12);
2299                 unicode += ((string[4] & 0x3f) << 6);
2300                 unicode +=  (string[5] & 0x3f);
2301                 break;
2302         default:
2303                 die("Invalid unicode length");
2304         }
2306         /* Invalid characters could return the special 0xfffd value but NUL
2307          * should be just as good. */
2308         return unicode > 0xffff ? 0 : unicode;
2311 /* Calculates how much of string can be shown within the given maximum width
2312  * and sets trimmed parameter to non-zero value if all of string could not be
2313  * shown.
2314  *
2315  * Additionally, adds to coloffset how many many columns to move to align with
2316  * the expected position. Takes into account how multi-byte and double-width
2317  * characters will effect the cursor position.
2318  *
2319  * Returns the number of bytes to output from string to satisfy max_width. */
2320 static size_t
2321 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2323         const char *start = string;
2324         const char *end = strchr(string, '\0');
2325         size_t mbwidth = 0;
2326         size_t width = 0;
2328         *trimmed = 0;
2330         while (string < end) {
2331                 int c = *(unsigned char *) string;
2332                 unsigned char bytes = utf8_bytes[c];
2333                 size_t ucwidth;
2334                 unsigned long unicode;
2336                 if (string + bytes > end)
2337                         break;
2339                 /* Change representation to figure out whether
2340                  * it is a single- or double-width character. */
2342                 unicode = utf8_to_unicode(string, bytes);
2343                 /* FIXME: Graceful handling of invalid unicode character. */
2344                 if (!unicode)
2345                         break;
2347                 ucwidth = unicode_width(unicode);
2348                 width  += ucwidth;
2349                 if (width > max_width) {
2350                         *trimmed = 1;
2351                         break;
2352                 }
2354                 /* The column offset collects the differences between the
2355                  * number of bytes encoding a character and the number of
2356                  * columns will be used for rendering said character.
2357                  *
2358                  * So if some character A is encoded in 2 bytes, but will be
2359                  * represented on the screen using only 1 byte this will and up
2360                  * adding 1 to the multi-byte column offset.
2361                  *
2362                  * Assumes that no double-width character can be encoding in
2363                  * less than two bytes. */
2364                 if (bytes > ucwidth)
2365                         mbwidth += bytes - ucwidth;
2367                 string  += bytes;
2368         }
2370         *coloffset += mbwidth;
2372         return string - start;
2376 /*
2377  * Status management
2378  */
2380 /* Whether or not the curses interface has been initialized. */
2381 static bool cursed = FALSE;
2383 /* The status window is used for polling keystrokes. */
2384 static WINDOW *status_win;
2386 /* Update status and title window. */
2387 static void
2388 report(const char *msg, ...)
2390         static bool empty = TRUE;
2391         struct view *view = display[current_view];
2393         if (!empty || *msg) {
2394                 va_list args;
2396                 va_start(args, msg);
2398                 werase(status_win);
2399                 wmove(status_win, 0, 0);
2400                 if (*msg) {
2401                         vwprintw(status_win, msg, args);
2402                         empty = FALSE;
2403                 } else {
2404                         empty = TRUE;
2405                 }
2406                 wrefresh(status_win);
2408                 va_end(args);
2409         }
2411         update_view_title(view);
2412         update_display_cursor();
2415 /* Controls when nodelay should be in effect when polling user input. */
2416 static void
2417 set_nonblocking_input(bool loading)
2419         static unsigned int loading_views;
2421         if ((loading == FALSE && loading_views-- == 1) ||
2422             (loading == TRUE  && loading_views++ == 0))
2423                 nodelay(status_win, loading);
2426 static void
2427 init_display(void)
2429         int x, y;
2431         /* Initialize the curses library */
2432         if (isatty(STDIN_FILENO)) {
2433                 cursed = !!initscr();
2434         } else {
2435                 /* Leave stdin and stdout alone when acting as a pager. */
2436                 FILE *io = fopen("/dev/tty", "r+");
2438                 cursed = !!newterm(NULL, io, io);
2439         }
2441         if (!cursed)
2442                 die("Failed to initialize curses");
2444         nonl();         /* Tell curses not to do NL->CR/NL on output */
2445         cbreak();       /* Take input chars one at a time, no wait for \n */
2446         noecho();       /* Don't echo input */
2447         leaveok(stdscr, TRUE);
2449         if (has_colors())
2450                 init_colors();
2452         getmaxyx(stdscr, y, x);
2453         status_win = newwin(1, 0, y - 1, 0);
2454         if (!status_win)
2455                 die("Failed to create status window");
2457         /* Enable keyboard mapping */
2458         keypad(status_win, TRUE);
2459         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2463 /*
2464  * Repository references
2465  */
2467 static struct ref *refs;
2468 static size_t refs_size;
2470 /* Id <-> ref store */
2471 static struct ref ***id_refs;
2472 static size_t id_refs_size;
2474 static struct ref **
2475 get_refs(char *id)
2477         struct ref ***tmp_id_refs;
2478         struct ref **ref_list = NULL;
2479         size_t ref_list_size = 0;
2480         size_t i;
2482         for (i = 0; i < id_refs_size; i++)
2483                 if (!strcmp(id, id_refs[i][0]->id))
2484                         return id_refs[i];
2486         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2487         if (!tmp_id_refs)
2488                 return NULL;
2490         id_refs = tmp_id_refs;
2492         for (i = 0; i < refs_size; i++) {
2493                 struct ref **tmp;
2495                 if (strcmp(id, refs[i].id))
2496                         continue;
2498                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2499                 if (!tmp) {
2500                         if (ref_list)
2501                                 free(ref_list);
2502                         return NULL;
2503                 }
2505                 ref_list = tmp;
2506                 if (ref_list_size > 0)
2507                         ref_list[ref_list_size - 1]->next = 1;
2508                 ref_list[ref_list_size] = &refs[i];
2510                 /* XXX: The properties of the commit chains ensures that we can
2511                  * safely modify the shared ref. The repo references will
2512                  * always be similar for the same id. */
2513                 ref_list[ref_list_size]->next = 0;
2514                 ref_list_size++;
2515         }
2517         if (ref_list)
2518                 id_refs[id_refs_size++] = ref_list;
2520         return ref_list;
2523 static int
2524 read_ref(char *id, int idlen, char *name, int namelen)
2526         struct ref *ref;
2527         bool tag = FALSE;
2529         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2530                 /* Commits referenced by tags has "^{}" appended. */
2531                 if (name[namelen - 1] != '}')
2532                         return OK;
2534                 while (namelen > 0 && name[namelen] != '^')
2535                         namelen--;
2537                 tag = TRUE;
2538                 namelen -= STRING_SIZE("refs/tags/");
2539                 name    += STRING_SIZE("refs/tags/");
2541         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2542                 namelen -= STRING_SIZE("refs/heads/");
2543                 name    += STRING_SIZE("refs/heads/");
2545         } else if (!strcmp(name, "HEAD")) {
2546                 return OK;
2547         }
2549         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2550         if (!refs)
2551                 return ERR;
2553         ref = &refs[refs_size++];
2554         ref->name = malloc(namelen + 1);
2555         if (!ref->name)
2556                 return ERR;
2558         strncpy(ref->name, name, namelen);
2559         ref->name[namelen] = 0;
2560         ref->tag = tag;
2561         string_copy(ref->id, id);
2563         return OK;
2566 static int
2567 load_refs(void)
2569         const char *cmd_env = getenv("TIG_LS_REMOTE");
2570         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2572         return read_properties(popen(cmd, "r"), "\t", read_ref);
2575 static int
2576 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2578         if (!strcmp(name, "i18n.commitencoding"))
2579                 string_copy(opt_encoding, value);
2581         return OK;
2584 static int
2585 load_repo_config(void)
2587         return read_properties(popen("git repo-config --list", "r"),
2588                                "=", read_repo_config_option);
2591 static int
2592 read_properties(FILE *pipe, const char *separators,
2593                 int (*read_property)(char *, int, char *, int))
2595         char buffer[BUFSIZ];
2596         char *name;
2597         int state = OK;
2599         if (!pipe)
2600                 return ERR;
2602         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2603                 char *value;
2604                 size_t namelen;
2605                 size_t valuelen;
2607                 name = chomp_string(name);
2608                 namelen = strcspn(name, separators);
2610                 if (name[namelen]) {
2611                         name[namelen] = 0;
2612                         value = chomp_string(name + namelen + 1);
2613                         valuelen = strlen(value);
2615                 } else {
2616                         value = "";
2617                         valuelen = 0;
2618                 }
2620                 state = read_property(name, namelen, value, valuelen);
2621         }
2623         if (state != ERR && ferror(pipe))
2624                 state = ERR;
2626         pclose(pipe);
2628         return state;
2632 /*
2633  * Main
2634  */
2636 static void __NORETURN
2637 quit(int sig)
2639         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2640         if (cursed)
2641                 endwin();
2642         exit(0);
2645 static void __NORETURN
2646 die(const char *err, ...)
2648         va_list args;
2650         endwin();
2652         va_start(args, err);
2653         fputs("tig: ", stderr);
2654         vfprintf(stderr, err, args);
2655         fputs("\n", stderr);
2656         va_end(args);
2658         exit(1);
2661 int
2662 main(int argc, char *argv[])
2664         struct view *view;
2665         enum request request;
2666         size_t i;
2668         signal(SIGINT, quit);
2670         if (load_options() == ERR)
2671                 die("Failed to load user config.");
2673         /* Load the repo config file so options can be overwritten from
2674          * the command line.  */
2675         if (load_repo_config() == ERR)
2676                 die("Failed to load repo config.");
2678         if (!parse_options(argc, argv))
2679                 return 0;
2681         if (load_refs() == ERR)
2682                 die("Failed to load refs.");
2684         /* Require a git repository unless when running in pager mode. */
2685         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2686                 die("Not a git repository");
2688         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2689                 view->cmd_env = getenv(view->cmd_env);
2691         request = opt_request;
2693         init_display();
2695         while (view_driver(display[current_view], request)) {
2696                 int key;
2697                 int i;
2699                 foreach_view (view, i)
2700                         update_view(view);
2702                 /* Refresh, accept single keystroke of input */
2703                 key = wgetch(status_win);
2704                 request = get_request(key);
2706                 /* Some low-level request handling. This keeps access to
2707                  * status_win restricted. */
2708                 switch (request) {
2709                 case REQ_PROMPT:
2710                         report(":");
2711                         /* Temporarily switch to line-oriented and echoed
2712                          * input. */
2713                         nocbreak();
2714                         echo();
2716                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2717                                 memcpy(opt_cmd, "git ", 4);
2718                                 opt_request = REQ_VIEW_PAGER;
2719                         } else {
2720                                 report("Prompt interrupted by loading view, "
2721                                        "press 'z' to stop loading views");
2722                                 request = REQ_SCREEN_UPDATE;
2723                         }
2725                         noecho();
2726                         cbreak();
2727                         break;
2729                 case REQ_SCREEN_RESIZE:
2730                 {
2731                         int height, width;
2733                         getmaxyx(stdscr, height, width);
2735                         /* Resize the status view and let the view driver take
2736                          * care of resizing the displayed views. */
2737                         wresize(status_win, 1, width);
2738                         mvwin(status_win, height - 1, 0);
2739                         wrefresh(status_win);
2740                         break;
2741                 }
2742                 default:
2743                         break;
2744                 }
2745         }
2747         quit(0);
2749         return 0;