Code

3bf5a25313d89a5a60075486147b5a6ddd78868b
[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 /* Shell quoting
182  *
183  * NOTE: The following is a slightly modified copy of the git project's shell
184  * quoting routines found in the quote.c file.
185  *
186  * Help to copy the thing properly quoted for the shell safety.  any single
187  * quote is replaced with '\'', any exclamation point is replaced with '\!',
188  * and the whole thing is enclosed in a
189  *
190  * E.g.
191  *  original     sq_quote     result
192  *  name     ==> name      ==> 'name'
193  *  a b      ==> a b       ==> 'a b'
194  *  a'b      ==> a'\''b    ==> 'a'\''b'
195  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
196  */
198 static size_t
199 sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
201         char c;
203 #define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
205         BUFPUT('\'');
206         while ((c = *src++)) {
207                 if (c == '\'' || c == '!') {
208                         BUFPUT('\'');
209                         BUFPUT('\\');
210                         BUFPUT(c);
211                         BUFPUT('\'');
212                 } else {
213                         BUFPUT(c);
214                 }
215         }
216         BUFPUT('\'');
218         return bufsize;
222 /*
223  * User requests
224  */
226 #define REQ_INFO \
227         /* XXX: Keep the view request first and in sync with views[]. */ \
228         REQ_GROUP("View switching") \
229         REQ_(VIEW_MAIN,         "Show main view"), \
230         REQ_(VIEW_DIFF,         "Show diff view"), \
231         REQ_(VIEW_LOG,          "Show log view"), \
232         REQ_(VIEW_HELP,         "Show help page"), \
233         REQ_(VIEW_PAGER,        "Show pager view"), \
234         \
235         REQ_GROUP("View manipulation") \
236         REQ_(ENTER,             "Enter current line and scroll"), \
237         REQ_(NEXT,              "Move to next"), \
238         REQ_(PREVIOUS,          "Move to previous"), \
239         REQ_(VIEW_NEXT,         "Move focus to next view"), \
240         REQ_(VIEW_CLOSE,        "Close the current view"), \
241         REQ_(QUIT,              "Close all views and quit"), \
242         \
243         REQ_GROUP("Cursor navigation") \
244         REQ_(MOVE_UP,           "Move cursor one line up"), \
245         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
246         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
247         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
248         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
249         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
250         \
251         REQ_GROUP("Scrolling") \
252         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
253         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
254         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
255         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
256         \
257         REQ_GROUP("Misc") \
258         REQ_(PROMPT,            "Bring up the prompt"), \
259         REQ_(SCREEN_UPDATE,     "Update the screen"), \
260         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
261         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
262         REQ_(SHOW_VERSION,      "Show version information"), \
263         REQ_(STOP_LOADING,      "Stop all loading views"), \
264         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
265         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"),
268 /* User action requests. */
269 enum request {
270 #define REQ_GROUP(help)
271 #define REQ_(req, help) REQ_##req
273         /* Offset all requests to avoid conflicts with ncurses getch values. */
274         REQ_OFFSET = KEY_MAX + 1,
275         REQ_INFO
277 #undef  REQ_GROUP
278 #undef  REQ_
279 };
281 struct request_info {
282         enum request request;
283         char *help;
284 };
286 static struct request_info req_info[] = {
287 #define REQ_GROUP(help) { 0, (help) },
288 #define REQ_(req, help) { REQ_##req, (help) }
289         REQ_INFO
290 #undef  REQ_GROUP
291 #undef  REQ_
292 };
294 /*
295  * Options
296  */
298 static const char usage[] =
299 VERSION " (" __DATE__ ")\n"
300 "\n"
301 "Usage: tig [options]\n"
302 "   or: tig [options] [--] [git log options]\n"
303 "   or: tig [options] log  [git log options]\n"
304 "   or: tig [options] diff [git diff options]\n"
305 "   or: tig [options] show [git show options]\n"
306 "   or: tig [options] <    [git command output]\n"
307 "\n"
308 "Options:\n"
309 "  -l                          Start up in log view\n"
310 "  -d                          Start up in diff view\n"
311 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
312 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
313 "  --                          Mark end of tig options\n"
314 "  -v, --version               Show version and exit\n"
315 "  -h, --help                  Show help message and exit\n";
317 /* Option and state variables. */
318 static bool opt_line_number     = FALSE;
319 static bool opt_rev_graph       = TRUE;
320 static int opt_num_interval     = NUMBER_INTERVAL;
321 static int opt_tab_size         = TABSIZE;
322 static enum request opt_request = REQ_VIEW_MAIN;
323 static char opt_cmd[SIZEOF_CMD] = "";
324 static char opt_encoding[20]    = "";
325 static bool opt_utf8            = TRUE;
326 static FILE *opt_pipe           = NULL;
328 enum option_type {
329         OPT_NONE,
330         OPT_INT,
331 };
333 static bool
334 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
336         va_list args;
337         char *value = "";
338         int *number;
340         if (opt[0] != '-')
341                 return FALSE;
343         if (opt[1] == '-') {
344                 int namelen = strlen(name);
346                 opt += 2;
348                 if (strncmp(opt, name, namelen))
349                         return FALSE;
351                 if (opt[namelen] == '=')
352                         value = opt + namelen + 1;
354         } else {
355                 if (!short_name || opt[1] != short_name)
356                         return FALSE;
357                 value = opt + 2;
358         }
360         va_start(args, type);
361         if (type == OPT_INT) {
362                 number = va_arg(args, int *);
363                 if (isdigit(*value))
364                         *number = atoi(value);
365         }
366         va_end(args);
368         return TRUE;
371 /* Returns the index of log or diff command or -1 to exit. */
372 static bool
373 parse_options(int argc, char *argv[])
375         int i;
377         for (i = 1; i < argc; i++) {
378                 char *opt = argv[i];
380                 if (!strcmp(opt, "-l")) {
381                         opt_request = REQ_VIEW_LOG;
382                         continue;
383                 }
385                 if (!strcmp(opt, "-d")) {
386                         opt_request = REQ_VIEW_DIFF;
387                         continue;
388                 }
390                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
391                         opt_line_number = TRUE;
392                         continue;
393                 }
395                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
396                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
397                         continue;
398                 }
400                 if (check_option(opt, 'v', "version", OPT_NONE)) {
401                         printf("tig version %s\n", VERSION);
402                         return FALSE;
403                 }
405                 if (check_option(opt, 'h', "help", OPT_NONE)) {
406                         printf(usage);
407                         return FALSE;
408                 }
410                 if (!strcmp(opt, "--")) {
411                         i++;
412                         break;
413                 }
415                 if (!strcmp(opt, "log") ||
416                     !strcmp(opt, "diff") ||
417                     !strcmp(opt, "show")) {
418                         opt_request = opt[0] == 'l'
419                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
420                         break;
421                 }
423                 if (opt[0] && opt[0] != '-')
424                         break;
426                 die("unknown option '%s'\n\n%s", opt, usage);
427         }
429         if (!isatty(STDIN_FILENO)) {
430                 opt_request = REQ_VIEW_PAGER;
431                 opt_pipe = stdin;
433         } else if (i < argc) {
434                 size_t buf_size;
436                 if (opt_request == REQ_VIEW_MAIN)
437                         /* XXX: This is vulnerable to the user overriding
438                          * options required for the main view parser. */
439                         string_copy(opt_cmd, "git log --stat --pretty=raw");
440                 else
441                         string_copy(opt_cmd, "git");
442                 buf_size = strlen(opt_cmd);
444                 while (buf_size < sizeof(opt_cmd) && i < argc) {
445                         opt_cmd[buf_size++] = ' ';
446                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
447                 }
449                 if (buf_size >= sizeof(opt_cmd))
450                         die("command too long");
452                 opt_cmd[buf_size] = 0;
454         }
456         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
457                 opt_utf8 = FALSE;
459         return TRUE;
463 /*
464  * Line-oriented content detection.
465  */
467 #define LINE_INFO \
468 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
469 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
470 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
471 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
472 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
473 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
474 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
475 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
476 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
477 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
478 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
479 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
480 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
481 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
482 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
483 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
484 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
485 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
486 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
487 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
488 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
489 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
490 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
491 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
492 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
493 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
494 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
495 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
496 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
497 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
498 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
499 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
500 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
501 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
502 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
503 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
504 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
505 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
507 enum line_type {
508 #define LINE(type, line, fg, bg, attr) \
509         LINE_##type
510         LINE_INFO
511 #undef  LINE
512 };
514 struct line_info {
515         const char *name;       /* Option name. */
516         int namelen;            /* Size of option name. */
517         const char *line;       /* The start of line to match. */
518         int linelen;            /* Size of string to match. */
519         int fg, bg, attr;       /* Color and text attributes for the lines. */
520 };
522 static struct line_info line_info[] = {
523 #define LINE(type, line, fg, bg, attr) \
524         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
525         LINE_INFO
526 #undef  LINE
527 };
529 static enum line_type
530 get_line_type(char *line)
532         int linelen = strlen(line);
533         enum line_type type;
535         for (type = 0; type < ARRAY_SIZE(line_info); type++)
536                 /* Case insensitive search matches Signed-off-by lines better. */
537                 if (linelen >= line_info[type].linelen &&
538                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
539                         return type;
541         return LINE_DEFAULT;
544 static inline int
545 get_line_attr(enum line_type type)
547         assert(type < ARRAY_SIZE(line_info));
548         return COLOR_PAIR(type) | line_info[type].attr;
551 static struct line_info *
552 get_line_info(char *name, int namelen)
554         enum line_type type;
555         int i;
557         /* Diff-Header -> DIFF_HEADER */
558         for (i = 0; i < namelen; i++) {
559                 if (name[i] == '-')
560                         name[i] = '_';
561                 else if (name[i] == '.')
562                         name[i] = '_';
563         }
565         for (type = 0; type < ARRAY_SIZE(line_info); type++)
566                 if (namelen == line_info[type].namelen &&
567                     !strncasecmp(line_info[type].name, name, namelen))
568                         return &line_info[type];
570         return NULL;
573 static void
574 init_colors(void)
576         int default_bg = COLOR_BLACK;
577         int default_fg = COLOR_WHITE;
578         enum line_type type;
580         start_color();
582         if (use_default_colors() != ERR) {
583                 default_bg = -1;
584                 default_fg = -1;
585         }
587         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
588                 struct line_info *info = &line_info[type];
589                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
590                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
592                 init_pair(type, fg, bg);
593         }
596 struct line {
597         enum line_type type;
598         void *data;             /* User data */
599 };
602 /*
603  * Keys
604  */
606 struct keymap {
607         int alias;
608         int request;
609 };
611 static struct keymap keymap[] = {
612         /* View switching */
613         { 'm',          REQ_VIEW_MAIN },
614         { 'd',          REQ_VIEW_DIFF },
615         { 'l',          REQ_VIEW_LOG },
616         { 'p',          REQ_VIEW_PAGER },
617         { 'h',          REQ_VIEW_HELP },
618         { '?',          REQ_VIEW_HELP },
620         /* View manipulation */
621         { 'q',          REQ_VIEW_CLOSE },
622         { KEY_TAB,      REQ_VIEW_NEXT },
623         { KEY_RETURN,   REQ_ENTER },
624         { KEY_UP,       REQ_PREVIOUS },
625         { KEY_DOWN,     REQ_NEXT },
627         /* Cursor navigation */
628         { 'k',          REQ_MOVE_UP },
629         { 'j',          REQ_MOVE_DOWN },
630         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
631         { KEY_END,      REQ_MOVE_LAST_LINE },
632         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
633         { ' ',          REQ_MOVE_PAGE_DOWN },
634         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
635         { 'b',          REQ_MOVE_PAGE_UP },
636         { '-',          REQ_MOVE_PAGE_UP },
638         /* Scrolling */
639         { KEY_IC,       REQ_SCROLL_LINE_UP },
640         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
641         { 'w',          REQ_SCROLL_PAGE_UP },
642         { 's',          REQ_SCROLL_PAGE_DOWN },
644         /* Misc */
645         { 'Q',          REQ_QUIT },
646         { 'z',          REQ_STOP_LOADING },
647         { 'v',          REQ_SHOW_VERSION },
648         { 'r',          REQ_SCREEN_REDRAW },
649         { 'n',          REQ_TOGGLE_LINENO },
650         { 'g',          REQ_TOGGLE_REV_GRAPH},
651         { ':',          REQ_PROMPT },
653         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
654         { ERR,          REQ_SCREEN_UPDATE },
656         /* Use the ncurses SIGWINCH handler. */
657         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
658 };
660 static enum request
661 get_request(int key)
663         int i;
665         for (i = 0; i < ARRAY_SIZE(keymap); i++)
666                 if (keymap[i].alias == key)
667                         return keymap[i].request;
669         return (enum request) key;
672 struct key {
673         char *name;
674         int value;
675 };
677 static struct key key_table[] = {
678         { "Enter",      KEY_RETURN },
679         { "Space",      ' ' },
680         { "Backspace",  KEY_BACKSPACE },
681         { "Tab",        KEY_TAB },
682         { "Escape",     KEY_ESC },
683         { "Left",       KEY_LEFT },
684         { "Right",      KEY_RIGHT },
685         { "Up",         KEY_UP },
686         { "Down",       KEY_DOWN },
687         { "Insert",     KEY_IC },
688         { "Delete",     KEY_DC },
689         { "Home",       KEY_HOME },
690         { "End",        KEY_END },
691         { "PageUp",     KEY_PPAGE },
692         { "PageDown",   KEY_NPAGE },
693         { "F1",         KEY_F(1) },
694         { "F2",         KEY_F(2) },
695         { "F3",         KEY_F(3) },
696         { "F4",         KEY_F(4) },
697         { "F5",         KEY_F(5) },
698         { "F6",         KEY_F(6) },
699         { "F7",         KEY_F(7) },
700         { "F8",         KEY_F(8) },
701         { "F9",         KEY_F(9) },
702         { "F10",        KEY_F(10) },
703         { "F11",        KEY_F(11) },
704         { "F12",        KEY_F(12) },
705 };
707 static char *
708 get_key(enum request request)
710         static char buf[BUFSIZ];
711         static char key_char[] = "'X'";
712         int pos = 0;
713         char *sep = "    ";
714         int i;
716         buf[pos] = 0;
718         for (i = 0; i < ARRAY_SIZE(keymap); i++) {
719                 char *seq = NULL;
720                 int key;
722                 if (keymap[i].request != request)
723                         continue;
725                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
726                         if (key_table[key].value == keymap[i].alias)
727                                 seq = key_table[key].name;
729                 if (seq == NULL &&
730                     keymap[i].alias < 127 &&
731                     isprint(keymap[i].alias)) {
732                         key_char[1] = (char) keymap[i].alias;
733                         seq = key_char;
734                 }
736                 if (!seq)
737                         seq = "'?'";
739                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
740                         return "Too many keybindings!";
741                 sep = ", ";
742         }
744         return buf;
748 /*
749  * User config file handling.
750  */
752 static struct int_map color_map[] = {
753 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
754         COLOR_MAP(DEFAULT),
755         COLOR_MAP(BLACK),
756         COLOR_MAP(BLUE),
757         COLOR_MAP(CYAN),
758         COLOR_MAP(GREEN),
759         COLOR_MAP(MAGENTA),
760         COLOR_MAP(RED),
761         COLOR_MAP(WHITE),
762         COLOR_MAP(YELLOW),
763 };
765 #define set_color(color, name) \
766         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
768 static struct int_map attr_map[] = {
769 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
770         ATTR_MAP(NORMAL),
771         ATTR_MAP(BLINK),
772         ATTR_MAP(BOLD),
773         ATTR_MAP(DIM),
774         ATTR_MAP(REVERSE),
775         ATTR_MAP(STANDOUT),
776         ATTR_MAP(UNDERLINE),
777 };
779 #define set_attribute(attr, name) \
780         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
782 static int   config_lineno;
783 static bool  config_errors;
784 static char *config_msg;
786 /* Wants: object fgcolor bgcolor [attr] */
787 static int
788 option_color_command(int argc, char *argv[])
790         struct line_info *info;
792         if (argc != 3 && argc != 4) {
793                 config_msg = "Wrong number of arguments given to color command";
794                 return ERR;
795         }
797         info = get_line_info(argv[0], strlen(argv[0]));
798         if (!info) {
799                 config_msg = "Unknown color name";
800                 return ERR;
801         }
803         if (set_color(&info->fg, argv[1]) == ERR) {
804                 config_msg = "Unknown color";
805                 return ERR;
806         }
808         if (set_color(&info->bg, argv[2]) == ERR) {
809                 config_msg = "Unknown color";
810                 return ERR;
811         }
813         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
814                 config_msg = "Unknown attribute";
815                 return ERR;
816         }
818         return OK;
821 /* Wants: name = value */
822 static int
823 option_set_command(int argc, char *argv[])
825         if (argc != 3) {
826                 config_msg = "Wrong number of arguments given to set command";
827                 return ERR;
828         }
830         if (strcmp(argv[1], "=")) {
831                 config_msg = "No value assigned";
832                 return ERR;
833         }
835         if (!strcmp(argv[0], "show-rev-graph")) {
836                 opt_rev_graph = (!strcmp(argv[2], "1") ||
837                                  !strcmp(argv[2], "true") ||
838                                  !strcmp(argv[2], "yes"));
839                 return OK;
840         }
842         if (!strcmp(argv[0], "line-number-interval")) {
843                 opt_num_interval = atoi(argv[2]);
844                 return OK;
845         }
847         if (!strcmp(argv[0], "tab-size")) {
848                 opt_tab_size = atoi(argv[2]);
849                 return OK;
850         }
852         if (!strcmp(argv[0], "encoding")) {
853                 string_copy(opt_encoding, argv[2]);
854                 return OK;
855         }
857         return ERR;
860 static int
861 set_option(char *opt, char *value)
863         char *argv[16];
864         int valuelen;
865         int argc = 0;
867         /* Tokenize */
868         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
869                 argv[argc++] = value;
871                 value += valuelen;
872                 if (!*value)
873                         break;
875                 *value++ = 0;
876                 while (isspace(*value))
877                         value++;
878         }
880         if (!strcmp(opt, "color"))
881                 return option_color_command(argc, argv);
883         if (!strcmp(opt, "set"))
884                 return option_set_command(argc, argv);
886         return ERR;
889 static int
890 read_option(char *opt, int optlen, char *value, int valuelen)
892         config_lineno++;
893         config_msg = "Internal error";
895         optlen = strcspn(opt, "#;");
896         if (optlen == 0) {
897                 /* The whole line is a commend or empty. */
898                 return OK;
900         } else if (opt[optlen] != 0) {
901                 /* Part of the option name is a comment, so the value part
902                  * should be ignored. */
903                 valuelen = 0;
904                 opt[optlen] = value[valuelen] = 0;
905         } else {
906                 /* Else look for comment endings in the value. */
907                 valuelen = strcspn(value, "#;");
908                 value[valuelen] = 0;
909         }
911         if (set_option(opt, value) == ERR) {
912                 fprintf(stderr, "Error on line %d, near '%.*s' option: %s\n",
913                         config_lineno, optlen, opt, config_msg);
914                 config_errors = TRUE;
915         }
917         /* Always keep going if errors are encountered. */
918         return OK;
921 static int
922 load_options(void)
924         char *home = getenv("HOME");
925         char buf[1024];
926         FILE *file;
928         config_lineno = 0;
929         config_errors = FALSE;
931         if (!home || !string_format(buf, "%s/.tigrc", home))
932                 return ERR;
934         /* It's ok that the file doesn't exist. */
935         file = fopen(buf, "r");
936         if (!file)
937                 return OK;
939         if (read_properties(file, " \t", read_option) == ERR ||
940             config_errors == TRUE)
941                 fprintf(stderr, "Errors while loading %s.\n", buf);
943         return OK;
947 /*
948  * The viewer
949  */
951 struct view;
952 struct view_ops;
954 /* The display array of active views and the index of the current view. */
955 static struct view *display[2];
956 static unsigned int current_view;
958 #define foreach_view(view, i) \
959         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
961 #define displayed_views()       (display[1] != NULL ? 2 : 1)
963 /* Current head and commit ID */
964 static char ref_commit[SIZEOF_REF]      = "HEAD";
965 static char ref_head[SIZEOF_REF]        = "HEAD";
967 struct view {
968         const char *name;       /* View name */
969         const char *cmd_fmt;    /* Default command line format */
970         const char *cmd_env;    /* Command line set via environment */
971         const char *id;         /* Points to either of ref_{head,commit} */
973         struct view_ops *ops;   /* View operations */
975         char cmd[SIZEOF_CMD];   /* Command buffer */
976         char ref[SIZEOF_REF];   /* Hovered commit reference */
977         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
979         int height, width;      /* The width and height of the main window */
980         WINDOW *win;            /* The main window */
981         WINDOW *title;          /* The title window living below the main window */
983         /* Navigation */
984         unsigned long offset;   /* Offset of the window top */
985         unsigned long lineno;   /* Current line number */
987         /* If non-NULL, points to the view that opened this view. If this view
988          * is closed tig will switch back to the parent view. */
989         struct view *parent;
991         /* Buffering */
992         unsigned long lines;    /* Total number of lines */
993         struct line *line;      /* Line index */
994         unsigned long line_size;/* Total number of allocated lines */
995         unsigned int digits;    /* Number of digits in the lines member. */
997         /* Loading */
998         FILE *pipe;
999         time_t start_time;
1000 };
1002 struct view_ops {
1003         /* What type of content being displayed. Used in the title bar. */
1004         const char *type;
1005         /* Draw one line; @lineno must be < view->height. */
1006         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1007         /* Read one line; updates view->line. */
1008         bool (*read)(struct view *view, char *data);
1009         /* Depending on view, change display based on current line. */
1010         bool (*enter)(struct view *view, struct line *line);
1011 };
1013 static struct view_ops pager_ops;
1014 static struct view_ops main_ops;
1016 #define VIEW_STR(name, cmd, env, ref, ops) \
1017         { name, cmd, #env, ref, ops }
1019 #define VIEW_(id, name, ops, ref) \
1020         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops)
1023 static struct view views[] = {
1024         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1025         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1026         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1027         VIEW_(HELP,  "help",  &pager_ops, "static"),
1028         VIEW_(PAGER, "pager", &pager_ops, "static"),
1029 };
1031 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1034 static bool
1035 draw_view_line(struct view *view, unsigned int lineno)
1037         if (view->offset + lineno >= view->lines)
1038                 return FALSE;
1040         return view->ops->draw(view, &view->line[view->offset + lineno], lineno);
1043 static void
1044 redraw_view_from(struct view *view, int lineno)
1046         assert(0 <= lineno && lineno < view->height);
1048         for (; lineno < view->height; lineno++) {
1049                 if (!draw_view_line(view, lineno))
1050                         break;
1051         }
1053         redrawwin(view->win);
1054         wrefresh(view->win);
1057 static void
1058 redraw_view(struct view *view)
1060         wclear(view->win);
1061         redraw_view_from(view, 0);
1065 static void
1066 update_view_title(struct view *view)
1068         if (view == display[current_view])
1069                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1070         else
1071                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1073         werase(view->title);
1074         wmove(view->title, 0, 0);
1076         if (*view->ref)
1077                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1078         else
1079                 wprintw(view->title, "[%s]", view->name);
1081         if (view->lines || view->pipe) {
1082                 unsigned int view_lines = view->offset + view->height;
1083                 unsigned int lines = view->lines
1084                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1085                                    : 0;
1087                 wprintw(view->title, " - %s %d of %d (%d%%)",
1088                         view->ops->type,
1089                         view->lineno + 1,
1090                         view->lines,
1091                         lines);
1092         }
1094         if (view->pipe) {
1095                 time_t secs = time(NULL) - view->start_time;
1097                 /* Three git seconds are a long time ... */
1098                 if (secs > 2)
1099                         wprintw(view->title, " %lds", secs);
1100         }
1102         wmove(view->title, 0, view->width - 1);
1103         wrefresh(view->title);
1106 static void
1107 resize_display(void)
1109         int offset, i;
1110         struct view *base = display[0];
1111         struct view *view = display[1] ? display[1] : display[0];
1113         /* Setup window dimensions */
1115         getmaxyx(stdscr, base->height, base->width);
1117         /* Make room for the status window. */
1118         base->height -= 1;
1120         if (view != base) {
1121                 /* Horizontal split. */
1122                 view->width   = base->width;
1123                 view->height  = SCALE_SPLIT_VIEW(base->height);
1124                 base->height -= view->height;
1126                 /* Make room for the title bar. */
1127                 view->height -= 1;
1128         }
1130         /* Make room for the title bar. */
1131         base->height -= 1;
1133         offset = 0;
1135         foreach_view (view, i) {
1136                 if (!view->win) {
1137                         view->win = newwin(view->height, 0, offset, 0);
1138                         if (!view->win)
1139                                 die("Failed to create %s view", view->name);
1141                         scrollok(view->win, TRUE);
1143                         view->title = newwin(1, 0, offset + view->height, 0);
1144                         if (!view->title)
1145                                 die("Failed to create title window");
1147                 } else {
1148                         wresize(view->win, view->height, view->width);
1149                         mvwin(view->win,   offset, 0);
1150                         mvwin(view->title, offset + view->height, 0);
1151                 }
1153                 offset += view->height + 1;
1154         }
1157 static void
1158 redraw_display(void)
1160         struct view *view;
1161         int i;
1163         foreach_view (view, i) {
1164                 redraw_view(view);
1165                 update_view_title(view);
1166         }
1169 static void
1170 update_display_cursor(void)
1172         struct view *view = display[current_view];
1174         /* Move the cursor to the right-most column of the cursor line.
1175          *
1176          * XXX: This could turn out to be a bit expensive, but it ensures that
1177          * the cursor does not jump around. */
1178         if (view->lines) {
1179                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1180                 wrefresh(view->win);
1181         }
1184 /*
1185  * Navigation
1186  */
1188 /* Scrolling backend */
1189 static void
1190 do_scroll_view(struct view *view, int lines, bool redraw)
1192         /* The rendering expects the new offset. */
1193         view->offset += lines;
1195         assert(0 <= view->offset && view->offset < view->lines);
1196         assert(lines);
1198         /* Redraw the whole screen if scrolling is pointless. */
1199         if (view->height < ABS(lines)) {
1200                 redraw_view(view);
1202         } else {
1203                 int line = lines > 0 ? view->height - lines : 0;
1204                 int end = line + ABS(lines);
1206                 wscrl(view->win, lines);
1208                 for (; line < end; line++) {
1209                         if (!draw_view_line(view, line))
1210                                 break;
1211                 }
1212         }
1214         /* Move current line into the view. */
1215         if (view->lineno < view->offset) {
1216                 view->lineno = view->offset;
1217                 draw_view_line(view, 0);
1219         } else if (view->lineno >= view->offset + view->height) {
1220                 if (view->lineno == view->offset + view->height) {
1221                         /* Clear the hidden line so it doesn't show if the view
1222                          * is scrolled up. */
1223                         wmove(view->win, view->height, 0);
1224                         wclrtoeol(view->win);
1225                 }
1226                 view->lineno = view->offset + view->height - 1;
1227                 draw_view_line(view, view->lineno - view->offset);
1228         }
1230         assert(view->offset <= view->lineno && view->lineno < view->lines);
1232         if (!redraw)
1233                 return;
1235         redrawwin(view->win);
1236         wrefresh(view->win);
1237         report("");
1240 /* Scroll frontend */
1241 static void
1242 scroll_view(struct view *view, enum request request)
1244         int lines = 1;
1246         switch (request) {
1247         case REQ_SCROLL_PAGE_DOWN:
1248                 lines = view->height;
1249         case REQ_SCROLL_LINE_DOWN:
1250                 if (view->offset + lines > view->lines)
1251                         lines = view->lines - view->offset;
1253                 if (lines == 0 || view->offset + view->height >= view->lines) {
1254                         report("Cannot scroll beyond the last line");
1255                         return;
1256                 }
1257                 break;
1259         case REQ_SCROLL_PAGE_UP:
1260                 lines = view->height;
1261         case REQ_SCROLL_LINE_UP:
1262                 if (lines > view->offset)
1263                         lines = view->offset;
1265                 if (lines == 0) {
1266                         report("Cannot scroll beyond the first line");
1267                         return;
1268                 }
1270                 lines = -lines;
1271                 break;
1273         default:
1274                 die("request %d not handled in switch", request);
1275         }
1277         do_scroll_view(view, lines, TRUE);
1280 /* Cursor moving */
1281 static void
1282 move_view(struct view *view, enum request request, bool redraw)
1284         int steps;
1286         switch (request) {
1287         case REQ_MOVE_FIRST_LINE:
1288                 steps = -view->lineno;
1289                 break;
1291         case REQ_MOVE_LAST_LINE:
1292                 steps = view->lines - view->lineno - 1;
1293                 break;
1295         case REQ_MOVE_PAGE_UP:
1296                 steps = view->height > view->lineno
1297                       ? -view->lineno : -view->height;
1298                 break;
1300         case REQ_MOVE_PAGE_DOWN:
1301                 steps = view->lineno + view->height >= view->lines
1302                       ? view->lines - view->lineno - 1 : view->height;
1303                 break;
1305         case REQ_MOVE_UP:
1306                 steps = -1;
1307                 break;
1309         case REQ_MOVE_DOWN:
1310                 steps = 1;
1311                 break;
1313         default:
1314                 die("request %d not handled in switch", request);
1315         }
1317         if (steps <= 0 && view->lineno == 0) {
1318                 report("Cannot move beyond the first line");
1319                 return;
1321         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1322                 report("Cannot move beyond the last line");
1323                 return;
1324         }
1326         /* Move the current line */
1327         view->lineno += steps;
1328         assert(0 <= view->lineno && view->lineno < view->lines);
1330         /* Repaint the old "current" line if we be scrolling */
1331         if (ABS(steps) < view->height) {
1332                 int prev_lineno = view->lineno - steps - view->offset;
1334                 wmove(view->win, prev_lineno, 0);
1335                 wclrtoeol(view->win);
1336                 draw_view_line(view,  prev_lineno);
1337         }
1339         /* Check whether the view needs to be scrolled */
1340         if (view->lineno < view->offset ||
1341             view->lineno >= view->offset + view->height) {
1342                 if (steps < 0 && -steps > view->offset) {
1343                         steps = -view->offset;
1345                 } else if (steps > 0) {
1346                         if (view->lineno == view->lines - 1 &&
1347                             view->lines > view->height) {
1348                                 steps = view->lines - view->offset - 1;
1349                                 if (steps >= view->height)
1350                                         steps -= view->height - 1;
1351                         }
1352                 }
1354                 do_scroll_view(view, steps, redraw);
1355                 return;
1356         }
1358         /* Draw the current line */
1359         draw_view_line(view, view->lineno - view->offset);
1361         if (!redraw)
1362                 return;
1364         redrawwin(view->win);
1365         wrefresh(view->win);
1366         report("");
1370 /*
1371  * Incremental updating
1372  */
1374 static void
1375 end_update(struct view *view)
1377         if (!view->pipe)
1378                 return;
1379         set_nonblocking_input(FALSE);
1380         if (view->pipe == stdin)
1381                 fclose(view->pipe);
1382         else
1383                 pclose(view->pipe);
1384         view->pipe = NULL;
1387 static bool
1388 begin_update(struct view *view)
1390         const char *id = view->id;
1392         if (view->pipe)
1393                 end_update(view);
1395         if (opt_cmd[0]) {
1396                 string_copy(view->cmd, opt_cmd);
1397                 opt_cmd[0] = 0;
1398                 /* When running random commands, the view ref could have become
1399                  * invalid so clear it. */
1400                 view->ref[0] = 0;
1401         } else {
1402                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1404                 if (!string_format(view->cmd, format, id, id, id, id, id))
1405                         return FALSE;
1406         }
1408         /* Special case for the pager view. */
1409         if (opt_pipe) {
1410                 view->pipe = opt_pipe;
1411                 opt_pipe = NULL;
1412         } else {
1413                 view->pipe = popen(view->cmd, "r");
1414         }
1416         if (!view->pipe)
1417                 return FALSE;
1419         set_nonblocking_input(TRUE);
1421         view->offset = 0;
1422         view->lines  = 0;
1423         view->lineno = 0;
1424         string_copy(view->vid, id);
1426         if (view->line) {
1427                 int i;
1429                 for (i = 0; i < view->lines; i++)
1430                         if (view->line[i].data)
1431                                 free(view->line[i].data);
1433                 free(view->line);
1434                 view->line = NULL;
1435         }
1437         view->start_time = time(NULL);
1439         return TRUE;
1442 static struct line *
1443 realloc_lines(struct view *view, size_t line_size)
1445         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1447         if (!tmp)
1448                 return NULL;
1450         view->line = tmp;
1451         view->line_size = line_size;
1452         return view->line;
1455 static bool
1456 update_view(struct view *view)
1458         char buffer[BUFSIZ];
1459         char *line;
1460         /* The number of lines to read. If too low it will cause too much
1461          * redrawing (and possible flickering), if too high responsiveness
1462          * will suffer. */
1463         unsigned long lines = view->height;
1464         int redraw_from = -1;
1466         if (!view->pipe)
1467                 return TRUE;
1469         /* Only redraw if lines are visible. */
1470         if (view->offset + view->height >= view->lines)
1471                 redraw_from = view->lines - view->offset;
1473         if (!realloc_lines(view, view->lines + lines))
1474                 goto alloc_error;
1476         while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
1477                 int linelen = strlen(line);
1479                 if (linelen)
1480                         line[linelen - 1] = 0;
1482                 if (!view->ops->read(view, line))
1483                         goto alloc_error;
1485                 if (lines-- == 1)
1486                         break;
1487         }
1489         {
1490                 int digits;
1492                 lines = view->lines;
1493                 for (digits = 0; lines; digits++)
1494                         lines /= 10;
1496                 /* Keep the displayed view in sync with line number scaling. */
1497                 if (digits != view->digits) {
1498                         view->digits = digits;
1499                         redraw_from = 0;
1500                 }
1501         }
1503         if (redraw_from >= 0) {
1504                 /* If this is an incremental update, redraw the previous line
1505                  * since for commits some members could have changed when
1506                  * loading the main view. */
1507                 if (redraw_from > 0)
1508                         redraw_from--;
1510                 /* Incrementally draw avoids flickering. */
1511                 redraw_view_from(view, redraw_from);
1512         }
1514         /* Update the title _after_ the redraw so that if the redraw picks up a
1515          * commit reference in view->ref it'll be available here. */
1516         update_view_title(view);
1518         if (ferror(view->pipe)) {
1519                 report("Failed to read: %s", strerror(errno));
1520                 goto end;
1522         } else if (feof(view->pipe)) {
1523                 report("");
1524                 goto end;
1525         }
1527         return TRUE;
1529 alloc_error:
1530         report("Allocation failure");
1532 end:
1533         end_update(view);
1534         return FALSE;
1538 /*
1539  * View opening
1540  */
1542 static void open_help_view(struct view *view)
1544         char buf[BUFSIZ];
1545         int lines = ARRAY_SIZE(req_info) + 2;
1546         int i;
1548         if (view->lines > 0)
1549                 return;
1551         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1552                 if (!req_info[i].request)
1553                         lines++;
1555         view->line = calloc(lines, sizeof(*view->line));
1556         if (!view->line) {
1557                 report("Allocation failure");
1558                 return;
1559         }
1561         view->ops->read(view, "Quick reference for tig keybindings:");
1563         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1564                 char *key;
1566                 if (!req_info[i].request) {
1567                         view->ops->read(view, "");
1568                         view->ops->read(view, req_info[i].help);
1569                         continue;
1570                 }
1572                 key = get_key(req_info[i].request);
1573                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1574                         continue;
1576                 view->ops->read(view, buf);
1577         }
1580 enum open_flags {
1581         OPEN_DEFAULT = 0,       /* Use default view switching. */
1582         OPEN_SPLIT = 1,         /* Split current view. */
1583         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1584         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1585 };
1587 static void
1588 open_view(struct view *prev, enum request request, enum open_flags flags)
1590         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1591         bool split = !!(flags & OPEN_SPLIT);
1592         bool reload = !!(flags & OPEN_RELOAD);
1593         struct view *view = VIEW(request);
1594         int nviews = displayed_views();
1595         struct view *base_view = display[0];
1597         if (view == prev && nviews == 1 && !reload) {
1598                 report("Already in %s view", view->name);
1599                 return;
1600         }
1602         if (view == VIEW(REQ_VIEW_HELP)) {
1603                 open_help_view(view);
1605         } else if ((reload || strcmp(view->vid, view->id)) &&
1606                    !begin_update(view)) {
1607                 report("Failed to load %s view", view->name);
1608                 return;
1609         }
1611         if (split) {
1612                 display[1] = view;
1613                 if (!backgrounded)
1614                         current_view = 1;
1615         } else {
1616                 /* Maximize the current view. */
1617                 memset(display, 0, sizeof(display));
1618                 current_view = 0;
1619                 display[current_view] = view;
1620         }
1622         /* Resize the view when switching between split- and full-screen,
1623          * or when switching between two different full-screen views. */
1624         if (nviews != displayed_views() ||
1625             (nviews == 1 && base_view != display[0]))
1626                 resize_display();
1628         if (split && prev->lineno - prev->offset >= prev->height) {
1629                 /* Take the title line into account. */
1630                 int lines = prev->lineno - prev->offset - prev->height + 1;
1632                 /* Scroll the view that was split if the current line is
1633                  * outside the new limited view. */
1634                 do_scroll_view(prev, lines, TRUE);
1635         }
1637         if (prev && view != prev) {
1638                 if (split && !backgrounded) {
1639                         /* "Blur" the previous view. */
1640                         update_view_title(prev);
1641                 }
1643                 view->parent = prev;
1644         }
1646         if (view->pipe && view->lines == 0) {
1647                 /* Clear the old view and let the incremental updating refill
1648                  * the screen. */
1649                 wclear(view->win);
1650                 report("");
1651         } else {
1652                 redraw_view(view);
1653                 report("");
1654         }
1656         /* If the view is backgrounded the above calls to report()
1657          * won't redraw the view title. */
1658         if (backgrounded)
1659                 update_view_title(view);
1663 /*
1664  * User request switch noodle
1665  */
1667 static int
1668 view_driver(struct view *view, enum request request)
1670         int i;
1672         switch (request) {
1673         case REQ_MOVE_UP:
1674         case REQ_MOVE_DOWN:
1675         case REQ_MOVE_PAGE_UP:
1676         case REQ_MOVE_PAGE_DOWN:
1677         case REQ_MOVE_FIRST_LINE:
1678         case REQ_MOVE_LAST_LINE:
1679                 move_view(view, request, TRUE);
1680                 break;
1682         case REQ_SCROLL_LINE_DOWN:
1683         case REQ_SCROLL_LINE_UP:
1684         case REQ_SCROLL_PAGE_DOWN:
1685         case REQ_SCROLL_PAGE_UP:
1686                 scroll_view(view, request);
1687                 break;
1689         case REQ_VIEW_MAIN:
1690         case REQ_VIEW_DIFF:
1691         case REQ_VIEW_LOG:
1692         case REQ_VIEW_HELP:
1693         case REQ_VIEW_PAGER:
1694                 open_view(view, request, OPEN_DEFAULT);
1695                 break;
1697         case REQ_NEXT:
1698         case REQ_PREVIOUS:
1699                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
1701                 if (view == VIEW(REQ_VIEW_DIFF) &&
1702                     view->parent == VIEW(REQ_VIEW_MAIN)) {
1703                         bool redraw = display[1] == view;
1705                         view = view->parent;
1706                         move_view(view, request, redraw);
1707                         if (redraw)
1708                                 update_view_title(view);
1709                 } else {
1710                         move_view(view, request, TRUE);
1711                         break;
1712                 }
1713                 /* Fall-through */
1715         case REQ_ENTER:
1716                 if (!view->lines) {
1717                         report("Nothing to enter");
1718                         break;
1719                 }
1720                 return view->ops->enter(view, &view->line[view->lineno]);
1722         case REQ_VIEW_NEXT:
1723         {
1724                 int nviews = displayed_views();
1725                 int next_view = (current_view + 1) % nviews;
1727                 if (next_view == current_view) {
1728                         report("Only one view is displayed");
1729                         break;
1730                 }
1732                 current_view = next_view;
1733                 /* Blur out the title of the previous view. */
1734                 update_view_title(view);
1735                 report("");
1736                 break;
1737         }
1738         case REQ_TOGGLE_LINENO:
1739                 opt_line_number = !opt_line_number;
1740                 redraw_display();
1741                 break;
1743         case REQ_TOGGLE_REV_GRAPH:
1744                 opt_rev_graph = !opt_rev_graph;
1745                 redraw_display();
1746                 break;
1748         case REQ_PROMPT:
1749                 /* Always reload^Wrerun commands from the prompt. */
1750                 open_view(view, opt_request, OPEN_RELOAD);
1751                 break;
1753         case REQ_STOP_LOADING:
1754                 for (i = 0; i < ARRAY_SIZE(views); i++) {
1755                         view = &views[i];
1756                         if (view->pipe)
1757                                 report("Stopped loading the %s view", view->name),
1758                         end_update(view);
1759                 }
1760                 break;
1762         case REQ_SHOW_VERSION:
1763                 report("%s (built %s)", VERSION, __DATE__);
1764                 return TRUE;
1766         case REQ_SCREEN_RESIZE:
1767                 resize_display();
1768                 /* Fall-through */
1769         case REQ_SCREEN_REDRAW:
1770                 redraw_display();
1771                 break;
1773         case REQ_SCREEN_UPDATE:
1774                 doupdate();
1775                 return TRUE;
1777         case REQ_VIEW_CLOSE:
1778                 /* XXX: Mark closed views by letting view->parent point to the
1779                  * view itself. Parents to closed view should never be
1780                  * followed. */
1781                 if (view->parent &&
1782                     view->parent->parent != view->parent) {
1783                         memset(display, 0, sizeof(display));
1784                         current_view = 0;
1785                         display[current_view] = view->parent;
1786                         view->parent = view;
1787                         resize_display();
1788                         redraw_display();
1789                         break;
1790                 }
1791                 /* Fall-through */
1792         case REQ_QUIT:
1793                 return FALSE;
1795         default:
1796                 /* An unknown key will show most commonly used commands. */
1797                 report("Unknown key, press 'h' for help");
1798                 return TRUE;
1799         }
1801         return TRUE;
1805 /*
1806  * Pager backend
1807  */
1809 static bool
1810 pager_draw(struct view *view, struct line *line, unsigned int lineno)
1812         char *text = line->data;
1813         enum line_type type = line->type;
1814         int textlen = strlen(text);
1815         int attr;
1817         wmove(view->win, lineno, 0);
1819         if (view->offset + lineno == view->lineno) {
1820                 if (type == LINE_COMMIT) {
1821                         string_copy(view->ref, text + 7);
1822                         string_copy(ref_commit, view->ref);
1823                 }
1825                 type = LINE_CURSOR;
1826                 wchgat(view->win, -1, 0, type, NULL);
1827         }
1829         attr = get_line_attr(type);
1830         wattrset(view->win, attr);
1832         if (opt_line_number || opt_tab_size < TABSIZE) {
1833                 static char spaces[] = "                    ";
1834                 int col_offset = 0, col = 0;
1836                 if (opt_line_number) {
1837                         unsigned long real_lineno = view->offset + lineno + 1;
1839                         if (real_lineno == 1 ||
1840                             (real_lineno % opt_num_interval) == 0) {
1841                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
1843                         } else {
1844                                 waddnstr(view->win, spaces,
1845                                          MIN(view->digits, STRING_SIZE(spaces)));
1846                         }
1847                         waddstr(view->win, ": ");
1848                         col_offset = view->digits + 2;
1849                 }
1851                 while (text && col_offset + col < view->width) {
1852                         int cols_max = view->width - col_offset - col;
1853                         char *pos = text;
1854                         int cols;
1856                         if (*text == '\t') {
1857                                 text++;
1858                                 assert(sizeof(spaces) > TABSIZE);
1859                                 pos = spaces;
1860                                 cols = opt_tab_size - (col % opt_tab_size);
1862                         } else {
1863                                 text = strchr(text, '\t');
1864                                 cols = line ? text - pos : strlen(pos);
1865                         }
1867                         waddnstr(view->win, pos, MIN(cols, cols_max));
1868                         col += cols;
1869                 }
1871         } else {
1872                 int col = 0, pos = 0;
1874                 for (; pos < textlen && col < view->width; pos++, col++)
1875                         if (text[pos] == '\t')
1876                                 col += TABSIZE - (col % TABSIZE) - 1;
1878                 waddnstr(view->win, text, pos);
1879         }
1881         return TRUE;
1884 static void
1885 add_pager_refs(struct view *view, struct line *line)
1887         char buf[1024];
1888         char *data = line->data;
1889         struct ref **refs;
1890         int bufpos = 0, refpos = 0;
1891         const char *sep = "Refs: ";
1893         assert(line->type == LINE_COMMIT);
1895         refs = get_refs(data + STRING_SIZE("commit "));
1896         if (!refs)
1897                 return;
1899         do {
1900                 struct ref *ref = refs[refpos];
1901                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
1903                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
1904                         return;
1905                 sep = ", ";
1906         } while (refs[refpos++]->next);
1908         if (!realloc_lines(view, view->line_size + 1))
1909                 return;
1911         line = &view->line[view->lines];
1912         line->data = strdup(buf);
1913         if (!line->data)
1914                 return;
1916         line->type = LINE_PP_REFS;
1917         view->lines++;
1920 static bool
1921 pager_read(struct view *view, char *data)
1923         struct line *line = &view->line[view->lines];
1925         line->data = strdup(data);
1926         if (!line->data)
1927                 return FALSE;
1929         line->type = get_line_type(line->data);
1930         view->lines++;
1932         if (line->type == LINE_COMMIT &&
1933             (view == VIEW(REQ_VIEW_DIFF) ||
1934              view == VIEW(REQ_VIEW_LOG)))
1935                 add_pager_refs(view, line);
1937         return TRUE;
1940 static bool
1941 pager_enter(struct view *view, struct line *line)
1943         int split = 0;
1945         if (line->type == LINE_COMMIT &&
1946            (view == VIEW(REQ_VIEW_LOG) ||
1947             view == VIEW(REQ_VIEW_PAGER))) {
1948                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1949                 split = 1;
1950         }
1952         /* Always scroll the view even if it was split. That way
1953          * you can use Enter to scroll through the log view and
1954          * split open each commit diff. */
1955         scroll_view(view, REQ_SCROLL_LINE_DOWN);
1957         /* FIXME: A minor workaround. Scrolling the view will call report("")
1958          * but if we are scrolling a non-current view this won't properly
1959          * update the view title. */
1960         if (split)
1961                 update_view_title(view);
1963         return TRUE;
1966 static struct view_ops pager_ops = {
1967         "line",
1968         pager_draw,
1969         pager_read,
1970         pager_enter,
1971 };
1974 /*
1975  * Main view backend
1976  */
1978 struct commit {
1979         char id[41];                    /* SHA1 ID. */
1980         char title[75];                 /* First line of the commit message. */
1981         char author[75];                /* Author of the commit. */
1982         struct tm time;                 /* Date from the author ident. */
1983         struct ref **refs;              /* Repository references. */
1984         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
1985         size_t graph_size;              /* The width of the graph array. */
1986 };
1988 static bool
1989 main_draw(struct view *view, struct line *line, unsigned int lineno)
1991         char buf[DATE_COLS + 1];
1992         struct commit *commit = line->data;
1993         enum line_type type;
1994         int col = 0;
1995         size_t timelen;
1996         size_t authorlen;
1997         int trimmed = 1;
1999         if (!*commit->author)
2000                 return FALSE;
2002         wmove(view->win, lineno, col);
2004         if (view->offset + lineno == view->lineno) {
2005                 string_copy(view->ref, commit->id);
2006                 string_copy(ref_commit, view->ref);
2007                 type = LINE_CURSOR;
2008                 wattrset(view->win, get_line_attr(type));
2009                 wchgat(view->win, -1, 0, type, NULL);
2011         } else {
2012                 type = LINE_MAIN_COMMIT;
2013                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2014         }
2016         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2017         waddnstr(view->win, buf, timelen);
2018         waddstr(view->win, " ");
2020         col += DATE_COLS;
2021         wmove(view->win, lineno, col);
2022         if (type != LINE_CURSOR)
2023                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2025         if (opt_utf8) {
2026                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2027         } else {
2028                 authorlen = strlen(commit->author);
2029                 if (authorlen > AUTHOR_COLS - 2) {
2030                         authorlen = AUTHOR_COLS - 2;
2031                         trimmed = 1;
2032                 }
2033         }
2035         if (trimmed) {
2036                 waddnstr(view->win, commit->author, authorlen);
2037                 if (type != LINE_CURSOR)
2038                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2039                 waddch(view->win, '~');
2040         } else {
2041                 waddstr(view->win, commit->author);
2042         }
2044         col += AUTHOR_COLS;
2045         if (type != LINE_CURSOR)
2046                 wattrset(view->win, A_NORMAL);
2048         if (opt_rev_graph && commit->graph_size) {
2049                 size_t i;
2051                 wmove(view->win, lineno, col);
2052                 /* Using waddch() instead of waddnstr() ensures that
2053                  * they'll be rendered correctly for the cursor line. */
2054                 for (i = 0; i < commit->graph_size; i++)
2055                         waddch(view->win, commit->graph[i]);
2057                 col += commit->graph_size + 1;
2058         }
2060         wmove(view->win, lineno, col);
2062         if (commit->refs) {
2063                 size_t i = 0;
2065                 do {
2066                         if (type == LINE_CURSOR)
2067                                 ;
2068                         else if (commit->refs[i]->tag)
2069                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2070                         else
2071                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2072                         waddstr(view->win, "[");
2073                         waddstr(view->win, commit->refs[i]->name);
2074                         waddstr(view->win, "]");
2075                         if (type != LINE_CURSOR)
2076                                 wattrset(view->win, A_NORMAL);
2077                         waddstr(view->win, " ");
2078                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2079                 } while (commit->refs[i++]->next);
2080         }
2082         if (type != LINE_CURSOR)
2083                 wattrset(view->win, get_line_attr(type));
2085         {
2086                 int titlelen = strlen(commit->title);
2088                 if (col + titlelen > view->width)
2089                         titlelen = view->width - col;
2091                 waddnstr(view->win, commit->title, titlelen);
2092         }
2094         return TRUE;
2097 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2098 static bool
2099 main_read(struct view *view, char *line)
2101         enum line_type type = get_line_type(line);
2102         struct commit *commit = view->lines
2103                               ? view->line[view->lines - 1].data : NULL;
2105         switch (type) {
2106         case LINE_COMMIT:
2107                 commit = calloc(1, sizeof(struct commit));
2108                 if (!commit)
2109                         return FALSE;
2111                 line += STRING_SIZE("commit ");
2113                 view->line[view->lines++].data = commit;
2114                 string_copy(commit->id, line);
2115                 commit->refs = get_refs(commit->id);
2116                 commit->graph[commit->graph_size++] = ACS_LTEE;
2117                 break;
2119         case LINE_AUTHOR:
2120         {
2121                 char *ident = line + STRING_SIZE("author ");
2122                 char *end = strchr(ident, '<');
2124                 if (!commit)
2125                         break;
2127                 if (end) {
2128                         for (; end > ident && isspace(end[-1]); end--) ;
2129                         *end = 0;
2130                 }
2132                 string_copy(commit->author, ident);
2134                 /* Parse epoch and timezone */
2135                 if (end) {
2136                         char *secs = strchr(end + 1, '>');
2137                         char *zone;
2138                         time_t time;
2140                         if (!secs || secs[1] != ' ')
2141                                 break;
2143                         secs += 2;
2144                         time = (time_t) atol(secs);
2145                         zone = strchr(secs, ' ');
2146                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2147                                 long tz;
2149                                 zone++;
2150                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2151                                 tz += ('0' - zone[2]) * 60 * 60;
2152                                 tz += ('0' - zone[3]) * 60;
2153                                 tz += ('0' - zone[4]) * 60;
2155                                 if (zone[0] == '-')
2156                                         tz = -tz;
2158                                 time -= tz;
2159                         }
2160                         gmtime_r(&time, &commit->time);
2161                 }
2162                 break;
2163         }
2164         default:
2165                 if (!commit)
2166                         break;
2168                 /* Fill in the commit title if it has not already been set. */
2169                 if (commit->title[0])
2170                         break;
2172                 /* Require titles to start with a non-space character at the
2173                  * offset used by git log. */
2174                 /* FIXME: More gracefull handling of titles; append "..." to
2175                  * shortened titles, etc. */
2176                 if (strncmp(line, "    ", 4) ||
2177                     isspace(line[4]))
2178                         break;
2180                 string_copy(commit->title, line + 4);
2181         }
2183         return TRUE;
2186 static bool
2187 main_enter(struct view *view, struct line *line)
2189         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2191         open_view(view, REQ_VIEW_DIFF, flags);
2192         return TRUE;
2195 static struct view_ops main_ops = {
2196         "commit",
2197         main_draw,
2198         main_read,
2199         main_enter,
2200 };
2203 /*
2204  * Unicode / UTF-8 handling
2205  *
2206  * NOTE: Much of the following code for dealing with unicode is derived from
2207  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2208  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2209  */
2211 /* I've (over)annotated a lot of code snippets because I am not entirely
2212  * confident that the approach taken by this small UTF-8 interface is correct.
2213  * --jonas */
2215 static inline int
2216 unicode_width(unsigned long c)
2218         if (c >= 0x1100 &&
2219            (c <= 0x115f                         /* Hangul Jamo */
2220             || c == 0x2329
2221             || c == 0x232a
2222             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2223                                                 /* CJK ... Yi */
2224             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2225             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2226             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2227             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2228             || (c >= 0xffe0  && c <= 0xffe6)
2229             || (c >= 0x20000 && c <= 0x2fffd)
2230             || (c >= 0x30000 && c <= 0x3fffd)))
2231                 return 2;
2233         return 1;
2236 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2237  * Illegal bytes are set one. */
2238 static const unsigned char utf8_bytes[256] = {
2239         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,
2240         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,
2241         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,
2242         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,
2243         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,
2244         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,
2245         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,
2246         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,
2247 };
2249 /* Decode UTF-8 multi-byte representation into a unicode character. */
2250 static inline unsigned long
2251 utf8_to_unicode(const char *string, size_t length)
2253         unsigned long unicode;
2255         switch (length) {
2256         case 1:
2257                 unicode  =   string[0];
2258                 break;
2259         case 2:
2260                 unicode  =  (string[0] & 0x1f) << 6;
2261                 unicode +=  (string[1] & 0x3f);
2262                 break;
2263         case 3:
2264                 unicode  =  (string[0] & 0x0f) << 12;
2265                 unicode += ((string[1] & 0x3f) << 6);
2266                 unicode +=  (string[2] & 0x3f);
2267                 break;
2268         case 4:
2269                 unicode  =  (string[0] & 0x0f) << 18;
2270                 unicode += ((string[1] & 0x3f) << 12);
2271                 unicode += ((string[2] & 0x3f) << 6);
2272                 unicode +=  (string[3] & 0x3f);
2273                 break;
2274         case 5:
2275                 unicode  =  (string[0] & 0x0f) << 24;
2276                 unicode += ((string[1] & 0x3f) << 18);
2277                 unicode += ((string[2] & 0x3f) << 12);
2278                 unicode += ((string[3] & 0x3f) << 6);
2279                 unicode +=  (string[4] & 0x3f);
2280                 break;
2281         case 6:
2282                 unicode  =  (string[0] & 0x01) << 30;
2283                 unicode += ((string[1] & 0x3f) << 24);
2284                 unicode += ((string[2] & 0x3f) << 18);
2285                 unicode += ((string[3] & 0x3f) << 12);
2286                 unicode += ((string[4] & 0x3f) << 6);
2287                 unicode +=  (string[5] & 0x3f);
2288                 break;
2289         default:
2290                 die("Invalid unicode length");
2291         }
2293         /* Invalid characters could return the special 0xfffd value but NUL
2294          * should be just as good. */
2295         return unicode > 0xffff ? 0 : unicode;
2298 /* Calculates how much of string can be shown within the given maximum width
2299  * and sets trimmed parameter to non-zero value if all of string could not be
2300  * shown.
2301  *
2302  * Additionally, adds to coloffset how many many columns to move to align with
2303  * the expected position. Takes into account how multi-byte and double-width
2304  * characters will effect the cursor position.
2305  *
2306  * Returns the number of bytes to output from string to satisfy max_width. */
2307 static size_t
2308 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2310         const char *start = string;
2311         const char *end = strchr(string, '\0');
2312         size_t mbwidth = 0;
2313         size_t width = 0;
2315         *trimmed = 0;
2317         while (string < end) {
2318                 int c = *(unsigned char *) string;
2319                 unsigned char bytes = utf8_bytes[c];
2320                 size_t ucwidth;
2321                 unsigned long unicode;
2323                 if (string + bytes > end)
2324                         break;
2326                 /* Change representation to figure out whether
2327                  * it is a single- or double-width character. */
2329                 unicode = utf8_to_unicode(string, bytes);
2330                 /* FIXME: Graceful handling of invalid unicode character. */
2331                 if (!unicode)
2332                         break;
2334                 ucwidth = unicode_width(unicode);
2335                 width  += ucwidth;
2336                 if (width > max_width) {
2337                         *trimmed = 1;
2338                         break;
2339                 }
2341                 /* The column offset collects the differences between the
2342                  * number of bytes encoding a character and the number of
2343                  * columns will be used for rendering said character.
2344                  *
2345                  * So if some character A is encoded in 2 bytes, but will be
2346                  * represented on the screen using only 1 byte this will and up
2347                  * adding 1 to the multi-byte column offset.
2348                  *
2349                  * Assumes that no double-width character can be encoding in
2350                  * less than two bytes. */
2351                 if (bytes > ucwidth)
2352                         mbwidth += bytes - ucwidth;
2354                 string  += bytes;
2355         }
2357         *coloffset += mbwidth;
2359         return string - start;
2363 /*
2364  * Status management
2365  */
2367 /* Whether or not the curses interface has been initialized. */
2368 static bool cursed = FALSE;
2370 /* The status window is used for polling keystrokes. */
2371 static WINDOW *status_win;
2373 /* Update status and title window. */
2374 static void
2375 report(const char *msg, ...)
2377         static bool empty = TRUE;
2378         struct view *view = display[current_view];
2380         if (!empty || *msg) {
2381                 va_list args;
2383                 va_start(args, msg);
2385                 werase(status_win);
2386                 wmove(status_win, 0, 0);
2387                 if (*msg) {
2388                         vwprintw(status_win, msg, args);
2389                         empty = FALSE;
2390                 } else {
2391                         empty = TRUE;
2392                 }
2393                 wrefresh(status_win);
2395                 va_end(args);
2396         }
2398         update_view_title(view);
2399         update_display_cursor();
2402 /* Controls when nodelay should be in effect when polling user input. */
2403 static void
2404 set_nonblocking_input(bool loading)
2406         static unsigned int loading_views;
2408         if ((loading == FALSE && loading_views-- == 1) ||
2409             (loading == TRUE  && loading_views++ == 0))
2410                 nodelay(status_win, loading);
2413 static void
2414 init_display(void)
2416         int x, y;
2418         /* Initialize the curses library */
2419         if (isatty(STDIN_FILENO)) {
2420                 cursed = !!initscr();
2421         } else {
2422                 /* Leave stdin and stdout alone when acting as a pager. */
2423                 FILE *io = fopen("/dev/tty", "r+");
2425                 cursed = !!newterm(NULL, io, io);
2426         }
2428         if (!cursed)
2429                 die("Failed to initialize curses");
2431         nonl();         /* Tell curses not to do NL->CR/NL on output */
2432         cbreak();       /* Take input chars one at a time, no wait for \n */
2433         noecho();       /* Don't echo input */
2434         leaveok(stdscr, TRUE);
2436         if (has_colors())
2437                 init_colors();
2439         getmaxyx(stdscr, y, x);
2440         status_win = newwin(1, 0, y - 1, 0);
2441         if (!status_win)
2442                 die("Failed to create status window");
2444         /* Enable keyboard mapping */
2445         keypad(status_win, TRUE);
2446         wbkgdset(status_win, get_line_attr(LINE_STATUS));
2450 /*
2451  * Repository references
2452  */
2454 static struct ref *refs;
2455 static size_t refs_size;
2457 /* Id <-> ref store */
2458 static struct ref ***id_refs;
2459 static size_t id_refs_size;
2461 static struct ref **
2462 get_refs(char *id)
2464         struct ref ***tmp_id_refs;
2465         struct ref **ref_list = NULL;
2466         size_t ref_list_size = 0;
2467         size_t i;
2469         for (i = 0; i < id_refs_size; i++)
2470                 if (!strcmp(id, id_refs[i][0]->id))
2471                         return id_refs[i];
2473         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2474         if (!tmp_id_refs)
2475                 return NULL;
2477         id_refs = tmp_id_refs;
2479         for (i = 0; i < refs_size; i++) {
2480                 struct ref **tmp;
2482                 if (strcmp(id, refs[i].id))
2483                         continue;
2485                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
2486                 if (!tmp) {
2487                         if (ref_list)
2488                                 free(ref_list);
2489                         return NULL;
2490                 }
2492                 ref_list = tmp;
2493                 if (ref_list_size > 0)
2494                         ref_list[ref_list_size - 1]->next = 1;
2495                 ref_list[ref_list_size] = &refs[i];
2497                 /* XXX: The properties of the commit chains ensures that we can
2498                  * safely modify the shared ref. The repo references will
2499                  * always be similar for the same id. */
2500                 ref_list[ref_list_size]->next = 0;
2501                 ref_list_size++;
2502         }
2504         if (ref_list)
2505                 id_refs[id_refs_size++] = ref_list;
2507         return ref_list;
2510 static int
2511 read_ref(char *id, int idlen, char *name, int namelen)
2513         struct ref *ref;
2514         bool tag = FALSE;
2516         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
2517                 /* Commits referenced by tags has "^{}" appended. */
2518                 if (name[namelen - 1] != '}')
2519                         return OK;
2521                 while (namelen > 0 && name[namelen] != '^')
2522                         namelen--;
2524                 tag = TRUE;
2525                 namelen -= STRING_SIZE("refs/tags/");
2526                 name    += STRING_SIZE("refs/tags/");
2528         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2529                 namelen -= STRING_SIZE("refs/heads/");
2530                 name    += STRING_SIZE("refs/heads/");
2532         } else if (!strcmp(name, "HEAD")) {
2533                 return OK;
2534         }
2536         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2537         if (!refs)
2538                 return ERR;
2540         ref = &refs[refs_size++];
2541         ref->name = malloc(namelen + 1);
2542         if (!ref->name)
2543                 return ERR;
2545         strncpy(ref->name, name, namelen);
2546         ref->name[namelen] = 0;
2547         ref->tag = tag;
2548         string_copy(ref->id, id);
2550         return OK;
2553 static int
2554 load_refs(void)
2556         const char *cmd_env = getenv("TIG_LS_REMOTE");
2557         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
2559         return read_properties(popen(cmd, "r"), "\t", read_ref);
2562 static int
2563 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
2565         if (!strcmp(name, "i18n.commitencoding"))
2566                 string_copy(opt_encoding, value);
2568         return OK;
2571 static int
2572 load_repo_config(void)
2574         return read_properties(popen("git repo-config --list", "r"),
2575                                "=", read_repo_config_option);
2578 static int
2579 read_properties(FILE *pipe, const char *separators,
2580                 int (*read_property)(char *, int, char *, int))
2582         char buffer[BUFSIZ];
2583         char *name;
2584         int state = OK;
2586         if (!pipe)
2587                 return ERR;
2589         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
2590                 char *value;
2591                 size_t namelen;
2592                 size_t valuelen;
2594                 name = chomp_string(name);
2595                 namelen = strcspn(name, separators);
2597                 if (name[namelen]) {
2598                         name[namelen] = 0;
2599                         value = chomp_string(name + namelen + 1);
2600                         valuelen = strlen(value);
2602                 } else {
2603                         value = "";
2604                         valuelen = 0;
2605                 }
2607                 state = read_property(name, namelen, value, valuelen);
2608         }
2610         if (state != ERR && ferror(pipe))
2611                 state = ERR;
2613         pclose(pipe);
2615         return state;
2619 /*
2620  * Main
2621  */
2623 static void __NORETURN
2624 quit(int sig)
2626         /* XXX: Restore tty modes and let the OS cleanup the rest! */
2627         if (cursed)
2628                 endwin();
2629         exit(0);
2632 static void __NORETURN
2633 die(const char *err, ...)
2635         va_list args;
2637         endwin();
2639         va_start(args, err);
2640         fputs("tig: ", stderr);
2641         vfprintf(stderr, err, args);
2642         fputs("\n", stderr);
2643         va_end(args);
2645         exit(1);
2648 int
2649 main(int argc, char *argv[])
2651         struct view *view;
2652         enum request request;
2653         size_t i;
2655         signal(SIGINT, quit);
2657         if (load_options() == ERR)
2658                 die("Failed to load user config.");
2660         /* Load the repo config file so options can be overwritten from
2661          * the command line.  */
2662         if (load_repo_config() == ERR)
2663                 die("Failed to load repo config.");
2665         if (!parse_options(argc, argv))
2666                 return 0;
2668         if (load_refs() == ERR)
2669                 die("Failed to load refs.");
2671         /* Require a git repository unless when running in pager mode. */
2672         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2673                 die("Not a git repository");
2675         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2676                 view->cmd_env = getenv(view->cmd_env);
2678         request = opt_request;
2680         init_display();
2682         while (view_driver(display[current_view], request)) {
2683                 int key;
2684                 int i;
2686                 foreach_view (view, i)
2687                         update_view(view);
2689                 /* Refresh, accept single keystroke of input */
2690                 key = wgetch(status_win);
2691                 request = get_request(key);
2693                 /* Some low-level request handling. This keeps access to
2694                  * status_win restricted. */
2695                 switch (request) {
2696                 case REQ_PROMPT:
2697                         report(":");
2698                         /* Temporarily switch to line-oriented and echoed
2699                          * input. */
2700                         nocbreak();
2701                         echo();
2703                         if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2704                                 memcpy(opt_cmd, "git ", 4);
2705                                 opt_request = REQ_VIEW_PAGER;
2706                         } else {
2707                                 report("Prompt interrupted by loading view, "
2708                                        "press 'z' to stop loading views");
2709                                 request = REQ_SCREEN_UPDATE;
2710                         }
2712                         noecho();
2713                         cbreak();
2714                         break;
2716                 case REQ_SCREEN_RESIZE:
2717                 {
2718                         int height, width;
2720                         getmaxyx(stdscr, height, width);
2722                         /* Resize the status view and let the view driver take
2723                          * care of resizing the displayed views. */
2724                         wresize(status_win, 1, width);
2725                         mvwin(status_win, height - 1, 0);
2726                         wrefresh(status_win);
2727                         break;
2728                 }
2729                 default:
2730                         break;
2731                 }
2732         }
2734         quit(0);
2736         return 0;