Code

Refactor current line activation to new select view operation
[tig.git] / tig.c
1 /* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifndef VERSION
15 #define VERSION "tig-0.4.git"
16 #endif
18 #ifndef DEBUG
19 #define NDEBUG
20 #endif
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
33 #include <sys/types.h>
34 #include <regex.h>
36 #include <locale.h>
37 #include <langinfo.h>
38 #include <iconv.h>
40 #include <curses.h>
42 #if __GNUC__ >= 3
43 #define __NORETURN __attribute__((__noreturn__))
44 #else
45 #define __NORETURN
46 #endif
48 static void __NORETURN die(const char *err, ...);
49 static void report(const char *msg, ...);
50 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
51 static void set_nonblocking_input(bool loading);
52 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
54 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
55 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
57 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
58 #define STRING_SIZE(x)  (sizeof(x) - 1)
60 #define SIZEOF_STR      1024    /* Default string size. */
61 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
62 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
64 /* This color name can be used to refer to the default term colors. */
65 #define COLOR_DEFAULT   (-1)
67 #define ICONV_NONE      ((iconv_t) -1)
69 /* The format and size of the date column in the main view. */
70 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
71 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
73 #define AUTHOR_COLS     20
75 /* The default interval between line numbers. */
76 #define NUMBER_INTERVAL 1
78 #define TABSIZE         8
80 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
82 #define TIG_LS_REMOTE \
83         "git ls-remote . 2>/dev/null"
85 #define TIG_DIFF_CMD \
86         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
88 #define TIG_LOG_CMD     \
89         "git log --cc --stat -n100 %s 2>/dev/null"
91 #define TIG_MAIN_CMD \
92         "git log --topo-order --pretty=raw %s 2>/dev/null"
94 #define TIG_TREE_CMD    \
95         "git ls-tree %s %s"
97 #define TIG_BLOB_CMD    \
98         "git cat-file blob %s"
100 /* XXX: Needs to be defined to the empty string. */
101 #define TIG_HELP_CMD    ""
102 #define TIG_PAGER_CMD   ""
104 /* Some ascii-shorthands fitted into the ncurses namespace. */
105 #define KEY_TAB         '\t'
106 #define KEY_RETURN      '\r'
107 #define KEY_ESC         27
110 struct ref {
111         char *name;             /* Ref name; tag or head names are shortened. */
112         char id[41];            /* Commit SHA1 ID */
113         unsigned int tag:1;     /* Is it a tag? */
114         unsigned int next:1;    /* For ref lists: are there more refs? */
115 };
117 static struct ref **get_refs(char *id);
119 struct int_map {
120         const char *name;
121         int namelen;
122         int value;
123 };
125 static int
126 set_from_int_map(struct int_map *map, size_t map_size,
127                  int *value, const char *name, int namelen)
130         int i;
132         for (i = 0; i < map_size; i++)
133                 if (namelen == map[i].namelen &&
134                     !strncasecmp(name, map[i].name, namelen)) {
135                         *value = map[i].value;
136                         return OK;
137                 }
139         return ERR;
143 /*
144  * String helpers
145  */
147 static inline void
148 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
150         if (srclen > dstlen - 1)
151                 srclen = dstlen - 1;
153         strncpy(dst, src, srclen);
154         dst[srclen] = 0;
157 /* Shorthands for safely copying into a fixed buffer. */
159 #define string_copy(dst, src) \
160         string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
162 #define string_ncopy(dst, src, srclen) \
163         string_ncopy_do(dst, sizeof(dst), src, srclen)
165 static char *
166 chomp_string(char *name)
168         int namelen;
170         while (isspace(*name))
171                 name++;
173         namelen = strlen(name) - 1;
174         while (namelen > 0 && isspace(name[namelen]))
175                 name[namelen--] = 0;
177         return name;
180 static bool
181 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
183         va_list args;
184         size_t pos = bufpos ? *bufpos : 0;
186         va_start(args, fmt);
187         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
188         va_end(args);
190         if (bufpos)
191                 *bufpos = pos;
193         return pos >= bufsize ? FALSE : TRUE;
196 #define string_format(buf, fmt, args...) \
197         string_nformat(buf, sizeof(buf), NULL, fmt, args)
199 #define string_format_from(buf, from, fmt, args...) \
200         string_nformat(buf, sizeof(buf), from, fmt, args)
202 static int
203 string_enum_compare(const char *str1, const char *str2, int len)
205         size_t i;
207 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
209         /* Diff-Header == DIFF_HEADER */
210         for (i = 0; i < len; i++) {
211                 if (toupper(str1[i]) == toupper(str2[i]))
212                         continue;
214                 if (string_enum_sep(str1[i]) &&
215                     string_enum_sep(str2[i]))
216                         continue;
218                 return str1[i] - str2[i];
219         }
221         return 0;
224 /* Shell quoting
225  *
226  * NOTE: The following is a slightly modified copy of the git project's shell
227  * quoting routines found in the quote.c file.
228  *
229  * Help to copy the thing properly quoted for the shell safety.  any single
230  * quote is replaced with '\'', any exclamation point is replaced with '\!',
231  * and the whole thing is enclosed in a
232  *
233  * E.g.
234  *  original     sq_quote     result
235  *  name     ==> name      ==> 'name'
236  *  a b      ==> a b       ==> 'a b'
237  *  a'b      ==> a'\''b    ==> 'a'\''b'
238  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
239  */
241 static size_t
242 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
244         char c;
246 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
248         BUFPUT('\'');
249         while ((c = *src++)) {
250                 if (c == '\'' || c == '!') {
251                         BUFPUT('\'');
252                         BUFPUT('\\');
253                         BUFPUT(c);
254                         BUFPUT('\'');
255                 } else {
256                         BUFPUT(c);
257                 }
258         }
259         BUFPUT('\'');
261         return bufsize;
265 /*
266  * User requests
267  */
269 #define REQ_INFO \
270         /* XXX: Keep the view request first and in sync with views[]. */ \
271         REQ_GROUP("View switching") \
272         REQ_(VIEW_MAIN,         "Show main view"), \
273         REQ_(VIEW_DIFF,         "Show diff view"), \
274         REQ_(VIEW_LOG,          "Show log view"), \
275         REQ_(VIEW_TREE,         "Show tree view"), \
276         REQ_(VIEW_BLOB,         "Show blob view"), \
277         REQ_(VIEW_HELP,         "Show help page"), \
278         REQ_(VIEW_PAGER,        "Show pager view"), \
279         \
280         REQ_GROUP("View manipulation") \
281         REQ_(ENTER,             "Enter current line and scroll"), \
282         REQ_(NEXT,              "Move to next"), \
283         REQ_(PREVIOUS,          "Move to previous"), \
284         REQ_(VIEW_NEXT,         "Move focus to next view"), \
285         REQ_(VIEW_CLOSE,        "Close the current view"), \
286         REQ_(QUIT,              "Close all views and quit"), \
287         \
288         REQ_GROUP("Cursor navigation") \
289         REQ_(MOVE_UP,           "Move cursor one line up"), \
290         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
291         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
292         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
293         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
294         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
295         \
296         REQ_GROUP("Scrolling") \
297         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
298         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
299         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
300         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
301         \
302         REQ_GROUP("Searching") \
303         REQ_(SEARCH,            "Search the view"), \
304         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
305         REQ_(FIND_NEXT,         "Find next search match"), \
306         REQ_(FIND_PREV,         "Find previous search match"), \
307         \
308         REQ_GROUP("Misc") \
309         REQ_(NONE,              "Do nothing"), \
310         REQ_(PROMPT,            "Bring up the prompt"), \
311         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
312         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
313         REQ_(SHOW_VERSION,      "Show version information"), \
314         REQ_(STOP_LOADING,      "Stop all loading views"), \
315         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
316         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
319 /* User action requests. */
320 enum request {
321 #define REQ_GROUP(help)
322 #define REQ_(req, help) REQ_##req
324         /* Offset all requests to avoid conflicts with ncurses getch values. */
325         REQ_OFFSET = KEY_MAX + 1,
326         REQ_INFO,
327         REQ_UNKNOWN,
329 #undef  REQ_GROUP
330 #undef  REQ_
331 };
333 struct request_info {
334         enum request request;
335         char *name;
336         int namelen;
337         char *help;
338 };
340 static struct request_info req_info[] = {
341 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
342 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
343         REQ_INFO
344 #undef  REQ_GROUP
345 #undef  REQ_
346 };
348 static enum request
349 get_request(const char *name)
351         int namelen = strlen(name);
352         int i;
354         for (i = 0; i < ARRAY_SIZE(req_info); i++)
355                 if (req_info[i].namelen == namelen &&
356                     !string_enum_compare(req_info[i].name, name, namelen))
357                         return req_info[i].request;
359         return REQ_UNKNOWN;
363 /*
364  * Options
365  */
367 static const char usage[] =
368 VERSION " (" __DATE__ ")\n"
369 "\n"
370 "Usage: tig [options]\n"
371 "   or: tig [options] [--] [git log options]\n"
372 "   or: tig [options] log  [git log options]\n"
373 "   or: tig [options] diff [git diff options]\n"
374 "   or: tig [options] show [git show options]\n"
375 "   or: tig [options] <    [git command output]\n"
376 "\n"
377 "Options:\n"
378 "  -l                          Start up in log view\n"
379 "  -d                          Start up in diff view\n"
380 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
381 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
382 "  --                          Mark end of tig options\n"
383 "  -v, --version               Show version and exit\n"
384 "  -h, --help                  Show help message and exit\n";
386 /* Option and state variables. */
387 static bool opt_line_number             = FALSE;
388 static bool opt_rev_graph               = TRUE;
389 static int opt_num_interval             = NUMBER_INTERVAL;
390 static int opt_tab_size                 = TABSIZE;
391 static enum request opt_request         = REQ_VIEW_MAIN;
392 static char opt_cmd[SIZEOF_STR]         = "";
393 static char opt_path[SIZEOF_STR]        = "";
394 static FILE *opt_pipe                   = NULL;
395 static char opt_encoding[20]            = "UTF-8";
396 static bool opt_utf8                    = TRUE;
397 static char opt_codeset[20]             = "UTF-8";
398 static iconv_t opt_iconv                = ICONV_NONE;
399 static char opt_search[SIZEOF_STR]      = "";
401 enum option_type {
402         OPT_NONE,
403         OPT_INT,
404 };
406 static bool
407 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
409         va_list args;
410         char *value = "";
411         int *number;
413         if (opt[0] != '-')
414                 return FALSE;
416         if (opt[1] == '-') {
417                 int namelen = strlen(name);
419                 opt += 2;
421                 if (strncmp(opt, name, namelen))
422                         return FALSE;
424                 if (opt[namelen] == '=')
425                         value = opt + namelen + 1;
427         } else {
428                 if (!short_name || opt[1] != short_name)
429                         return FALSE;
430                 value = opt + 2;
431         }
433         va_start(args, type);
434         if (type == OPT_INT) {
435                 number = va_arg(args, int *);
436                 if (isdigit(*value))
437                         *number = atoi(value);
438         }
439         va_end(args);
441         return TRUE;
444 /* Returns the index of log or diff command or -1 to exit. */
445 static bool
446 parse_options(int argc, char *argv[])
448         int i;
450         for (i = 1; i < argc; i++) {
451                 char *opt = argv[i];
453                 if (!strcmp(opt, "-l")) {
454                         opt_request = REQ_VIEW_LOG;
455                         continue;
456                 }
458                 if (!strcmp(opt, "-d")) {
459                         opt_request = REQ_VIEW_DIFF;
460                         continue;
461                 }
463                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
464                         opt_line_number = TRUE;
465                         continue;
466                 }
468                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
469                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
470                         continue;
471                 }
473                 if (check_option(opt, 'v', "version", OPT_NONE)) {
474                         printf("tig version %s\n", VERSION);
475                         return FALSE;
476                 }
478                 if (check_option(opt, 'h', "help", OPT_NONE)) {
479                         printf(usage);
480                         return FALSE;
481                 }
483                 if (!strcmp(opt, "--")) {
484                         i++;
485                         break;
486                 }
488                 if (!strcmp(opt, "log") ||
489                     !strcmp(opt, "diff") ||
490                     !strcmp(opt, "show")) {
491                         opt_request = opt[0] == 'l'
492                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
493                         break;
494                 }
496                 if (opt[0] && opt[0] != '-')
497                         break;
499                 die("unknown option '%s'\n\n%s", opt, usage);
500         }
502         if (!isatty(STDIN_FILENO)) {
503                 opt_request = REQ_VIEW_PAGER;
504                 opt_pipe = stdin;
506         } else if (i < argc) {
507                 size_t buf_size;
509                 if (opt_request == REQ_VIEW_MAIN)
510                         /* XXX: This is vulnerable to the user overriding
511                          * options required for the main view parser. */
512                         string_copy(opt_cmd, "git log --stat --pretty=raw");
513                 else
514                         string_copy(opt_cmd, "git");
515                 buf_size = strlen(opt_cmd);
517                 while (buf_size < sizeof(opt_cmd) && i < argc) {
518                         opt_cmd[buf_size++] = ' ';
519                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
520                 }
522                 if (buf_size >= sizeof(opt_cmd))
523                         die("command too long");
525                 opt_cmd[buf_size] = 0;
527         }
529         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
530                 opt_utf8 = FALSE;
532         return TRUE;
536 /*
537  * Line-oriented content detection.
538  */
540 #define LINE_INFO \
541 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
542 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
543 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
544 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
545 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
546 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
547 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
548 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
549 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
550 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
551 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
552 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
553 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
554 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
555 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
556 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
557 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
558 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
559 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
560 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
561 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
562 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
563 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
564 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
565 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
566 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
568 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
569 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
570 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
571 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
572 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
573 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
575 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
576 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
577 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
578 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
579 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
580 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
582 enum line_type {
583 #define LINE(type, line, fg, bg, attr) \
584         LINE_##type
585         LINE_INFO
586 #undef  LINE
587 };
589 struct line_info {
590         const char *name;       /* Option name. */
591         int namelen;            /* Size of option name. */
592         const char *line;       /* The start of line to match. */
593         int linelen;            /* Size of string to match. */
594         int fg, bg, attr;       /* Color and text attributes for the lines. */
595 };
597 static struct line_info line_info[] = {
598 #define LINE(type, line, fg, bg, attr) \
599         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
600         LINE_INFO
601 #undef  LINE
602 };
604 static enum line_type
605 get_line_type(char *line)
607         int linelen = strlen(line);
608         enum line_type type;
610         for (type = 0; type < ARRAY_SIZE(line_info); type++)
611                 /* Case insensitive search matches Signed-off-by lines better. */
612                 if (linelen >= line_info[type].linelen &&
613                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
614                         return type;
616         return LINE_DEFAULT;
619 static inline int
620 get_line_attr(enum line_type type)
622         assert(type < ARRAY_SIZE(line_info));
623         return COLOR_PAIR(type) | line_info[type].attr;
626 static struct line_info *
627 get_line_info(char *name, int namelen)
629         enum line_type type;
631         for (type = 0; type < ARRAY_SIZE(line_info); type++)
632                 if (namelen == line_info[type].namelen &&
633                     !string_enum_compare(line_info[type].name, name, namelen))
634                         return &line_info[type];
636         return NULL;
639 static void
640 init_colors(void)
642         int default_bg = COLOR_BLACK;
643         int default_fg = COLOR_WHITE;
644         enum line_type type;
646         start_color();
648         if (use_default_colors() != ERR) {
649                 default_bg = -1;
650                 default_fg = -1;
651         }
653         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
654                 struct line_info *info = &line_info[type];
655                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
656                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
658                 init_pair(type, fg, bg);
659         }
662 struct line {
663         enum line_type type;
664         void *data;             /* User data */
665 };
668 /*
669  * Keys
670  */
672 struct keybinding {
673         int alias;
674         enum request request;
675         struct keybinding *next;
676 };
678 static struct keybinding default_keybindings[] = {
679         /* View switching */
680         { 'm',          REQ_VIEW_MAIN },
681         { 'd',          REQ_VIEW_DIFF },
682         { 'l',          REQ_VIEW_LOG },
683         { 't',          REQ_VIEW_TREE },
684         { 'f',          REQ_VIEW_BLOB },
685         { 'p',          REQ_VIEW_PAGER },
686         { 'h',          REQ_VIEW_HELP },
688         /* View manipulation */
689         { 'q',          REQ_VIEW_CLOSE },
690         { KEY_TAB,      REQ_VIEW_NEXT },
691         { KEY_RETURN,   REQ_ENTER },
692         { KEY_UP,       REQ_PREVIOUS },
693         { KEY_DOWN,     REQ_NEXT },
695         /* Cursor navigation */
696         { 'k',          REQ_MOVE_UP },
697         { 'j',          REQ_MOVE_DOWN },
698         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
699         { KEY_END,      REQ_MOVE_LAST_LINE },
700         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
701         { ' ',          REQ_MOVE_PAGE_DOWN },
702         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
703         { 'b',          REQ_MOVE_PAGE_UP },
704         { '-',          REQ_MOVE_PAGE_UP },
706         /* Scrolling */
707         { KEY_IC,       REQ_SCROLL_LINE_UP },
708         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
709         { 'w',          REQ_SCROLL_PAGE_UP },
710         { 's',          REQ_SCROLL_PAGE_DOWN },
712         /* Searching */
713         { '/',          REQ_SEARCH },
714         { '?',          REQ_SEARCH_BACK },
715         { 'n',          REQ_FIND_NEXT },
716         { 'N',          REQ_FIND_PREV },
718         /* Misc */
719         { 'Q',          REQ_QUIT },
720         { 'z',          REQ_STOP_LOADING },
721         { 'v',          REQ_SHOW_VERSION },
722         { 'r',          REQ_SCREEN_REDRAW },
723         { '.',          REQ_TOGGLE_LINENO },
724         { 'g',          REQ_TOGGLE_REV_GRAPH },
725         { ':',          REQ_PROMPT },
727         /* wgetch() with nodelay() enabled returns ERR when there's no input. */
728         { ERR,          REQ_NONE },
730         /* Using the ncurses SIGWINCH handler. */
731         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
732 };
734 #define KEYMAP_INFO \
735         KEYMAP_(GENERIC), \
736         KEYMAP_(MAIN), \
737         KEYMAP_(DIFF), \
738         KEYMAP_(LOG), \
739         KEYMAP_(TREE), \
740         KEYMAP_(BLOB), \
741         KEYMAP_(PAGER), \
742         KEYMAP_(HELP) \
744 enum keymap {
745 #define KEYMAP_(name) KEYMAP_##name
746         KEYMAP_INFO
747 #undef  KEYMAP_
748 };
750 static struct int_map keymap_table[] = {
751 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
752         KEYMAP_INFO
753 #undef  KEYMAP_
754 };
756 #define set_keymap(map, name) \
757         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
759 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
761 static void
762 add_keybinding(enum keymap keymap, enum request request, int key)
764         struct keybinding *keybinding;
766         keybinding = calloc(1, sizeof(*keybinding));
767         if (!keybinding)
768                 die("Failed to allocate keybinding");
770         keybinding->alias = key;
771         keybinding->request = request;
772         keybinding->next = keybindings[keymap];
773         keybindings[keymap] = keybinding;
776 /* Looks for a key binding first in the given map, then in the generic map, and
777  * lastly in the default keybindings. */
778 static enum request
779 get_keybinding(enum keymap keymap, int key)
781         struct keybinding *kbd;
782         int i;
784         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
785                 if (kbd->alias == key)
786                         return kbd->request;
788         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
789                 if (kbd->alias == key)
790                         return kbd->request;
792         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
793                 if (default_keybindings[i].alias == key)
794                         return default_keybindings[i].request;
796         return (enum request) key;
800 struct key {
801         char *name;
802         int value;
803 };
805 static struct key key_table[] = {
806         { "Enter",      KEY_RETURN },
807         { "Space",      ' ' },
808         { "Backspace",  KEY_BACKSPACE },
809         { "Tab",        KEY_TAB },
810         { "Escape",     KEY_ESC },
811         { "Left",       KEY_LEFT },
812         { "Right",      KEY_RIGHT },
813         { "Up",         KEY_UP },
814         { "Down",       KEY_DOWN },
815         { "Insert",     KEY_IC },
816         { "Delete",     KEY_DC },
817         { "Hash",       '#' },
818         { "Home",       KEY_HOME },
819         { "End",        KEY_END },
820         { "PageUp",     KEY_PPAGE },
821         { "PageDown",   KEY_NPAGE },
822         { "F1",         KEY_F(1) },
823         { "F2",         KEY_F(2) },
824         { "F3",         KEY_F(3) },
825         { "F4",         KEY_F(4) },
826         { "F5",         KEY_F(5) },
827         { "F6",         KEY_F(6) },
828         { "F7",         KEY_F(7) },
829         { "F8",         KEY_F(8) },
830         { "F9",         KEY_F(9) },
831         { "F10",        KEY_F(10) },
832         { "F11",        KEY_F(11) },
833         { "F12",        KEY_F(12) },
834 };
836 static int
837 get_key_value(const char *name)
839         int i;
841         for (i = 0; i < ARRAY_SIZE(key_table); i++)
842                 if (!strcasecmp(key_table[i].name, name))
843                         return key_table[i].value;
845         if (strlen(name) == 1 && isprint(*name))
846                 return (int) *name;
848         return ERR;
851 static char *
852 get_key(enum request request)
854         static char buf[BUFSIZ];
855         static char key_char[] = "'X'";
856         size_t pos = 0;
857         char *sep = "    ";
858         int i;
860         buf[pos] = 0;
862         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
863                 struct keybinding *keybinding = &default_keybindings[i];
864                 char *seq = NULL;
865                 int key;
867                 if (keybinding->request != request)
868                         continue;
870                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
871                         if (key_table[key].value == keybinding->alias)
872                                 seq = key_table[key].name;
874                 if (seq == NULL &&
875                     keybinding->alias < 127 &&
876                     isprint(keybinding->alias)) {
877                         key_char[1] = (char) keybinding->alias;
878                         seq = key_char;
879                 }
881                 if (!seq)
882                         seq = "'?'";
884                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
885                         return "Too many keybindings!";
886                 sep = ", ";
887         }
889         return buf;
893 /*
894  * User config file handling.
895  */
897 static struct int_map color_map[] = {
898 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
899         COLOR_MAP(DEFAULT),
900         COLOR_MAP(BLACK),
901         COLOR_MAP(BLUE),
902         COLOR_MAP(CYAN),
903         COLOR_MAP(GREEN),
904         COLOR_MAP(MAGENTA),
905         COLOR_MAP(RED),
906         COLOR_MAP(WHITE),
907         COLOR_MAP(YELLOW),
908 };
910 #define set_color(color, name) \
911         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
913 static struct int_map attr_map[] = {
914 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
915         ATTR_MAP(NORMAL),
916         ATTR_MAP(BLINK),
917         ATTR_MAP(BOLD),
918         ATTR_MAP(DIM),
919         ATTR_MAP(REVERSE),
920         ATTR_MAP(STANDOUT),
921         ATTR_MAP(UNDERLINE),
922 };
924 #define set_attribute(attr, name) \
925         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
927 static int   config_lineno;
928 static bool  config_errors;
929 static char *config_msg;
931 /* Wants: object fgcolor bgcolor [attr] */
932 static int
933 option_color_command(int argc, char *argv[])
935         struct line_info *info;
937         if (argc != 3 && argc != 4) {
938                 config_msg = "Wrong number of arguments given to color command";
939                 return ERR;
940         }
942         info = get_line_info(argv[0], strlen(argv[0]));
943         if (!info) {
944                 config_msg = "Unknown color name";
945                 return ERR;
946         }
948         if (set_color(&info->fg, argv[1]) == ERR ||
949             set_color(&info->bg, argv[2]) == ERR) {
950                 config_msg = "Unknown color";
951                 return ERR;
952         }
954         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
955                 config_msg = "Unknown attribute";
956                 return ERR;
957         }
959         return OK;
962 /* Wants: name = value */
963 static int
964 option_set_command(int argc, char *argv[])
966         if (argc != 3) {
967                 config_msg = "Wrong number of arguments given to set command";
968                 return ERR;
969         }
971         if (strcmp(argv[1], "=")) {
972                 config_msg = "No value assigned";
973                 return ERR;
974         }
976         if (!strcmp(argv[0], "show-rev-graph")) {
977                 opt_rev_graph = (!strcmp(argv[2], "1") ||
978                                  !strcmp(argv[2], "true") ||
979                                  !strcmp(argv[2], "yes"));
980                 return OK;
981         }
983         if (!strcmp(argv[0], "line-number-interval")) {
984                 opt_num_interval = atoi(argv[2]);
985                 return OK;
986         }
988         if (!strcmp(argv[0], "tab-size")) {
989                 opt_tab_size = atoi(argv[2]);
990                 return OK;
991         }
993         if (!strcmp(argv[0], "commit-encoding")) {
994                 char *arg = argv[2];
995                 int delimiter = *arg;
996                 int i;
998                 switch (delimiter) {
999                 case '"':
1000                 case '\'':
1001                         for (arg++, i = 0; arg[i]; i++)
1002                                 if (arg[i] == delimiter) {
1003                                         arg[i] = 0;
1004                                         break;
1005                                 }
1006                 default:
1007                         string_copy(opt_encoding, arg);
1008                         return OK;
1009                 }
1010         }
1012         config_msg = "Unknown variable name";
1013         return ERR;
1016 /* Wants: mode request key */
1017 static int
1018 option_bind_command(int argc, char *argv[])
1020         enum request request;
1021         int keymap;
1022         int key;
1024         if (argc != 3) {
1025                 config_msg = "Wrong number of arguments given to bind command";
1026                 return ERR;
1027         }
1029         if (set_keymap(&keymap, argv[0]) == ERR) {
1030                 config_msg = "Unknown key map";
1031                 return ERR;
1032         }
1034         key = get_key_value(argv[1]);
1035         if (key == ERR) {
1036                 config_msg = "Unknown key";
1037                 return ERR;
1038         }
1040         request = get_request(argv[2]);
1041         if (request == REQ_UNKNOWN) {
1042                 config_msg = "Unknown request name";
1043                 return ERR;
1044         }
1046         add_keybinding(keymap, request, key);
1048         return OK;
1051 static int
1052 set_option(char *opt, char *value)
1054         char *argv[16];
1055         int valuelen;
1056         int argc = 0;
1058         /* Tokenize */
1059         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1060                 argv[argc++] = value;
1062                 value += valuelen;
1063                 if (!*value)
1064                         break;
1066                 *value++ = 0;
1067                 while (isspace(*value))
1068                         value++;
1069         }
1071         if (!strcmp(opt, "color"))
1072                 return option_color_command(argc, argv);
1074         if (!strcmp(opt, "set"))
1075                 return option_set_command(argc, argv);
1077         if (!strcmp(opt, "bind"))
1078                 return option_bind_command(argc, argv);
1080         config_msg = "Unknown option command";
1081         return ERR;
1084 static int
1085 read_option(char *opt, int optlen, char *value, int valuelen)
1087         int status = OK;
1089         config_lineno++;
1090         config_msg = "Internal error";
1092         /* Check for comment markers, since read_properties() will
1093          * only ensure opt and value are split at first " \t". */
1094         optlen = strcspn(opt, "#");
1095         if (optlen == 0)
1096                 return OK;
1098         if (opt[optlen] != 0) {
1099                 config_msg = "No option value";
1100                 status = ERR;
1102         }  else {
1103                 /* Look for comment endings in the value. */
1104                 int len = strcspn(value, "#");
1106                 if (len < valuelen) {
1107                         valuelen = len;
1108                         value[valuelen] = 0;
1109                 }
1111                 status = set_option(opt, value);
1112         }
1114         if (status == ERR) {
1115                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1116                         config_lineno, optlen, opt, config_msg);
1117                 config_errors = TRUE;
1118         }
1120         /* Always keep going if errors are encountered. */
1121         return OK;
1124 static int
1125 load_options(void)
1127         char *home = getenv("HOME");
1128         char buf[SIZEOF_STR];
1129         FILE *file;
1131         config_lineno = 0;
1132         config_errors = FALSE;
1134         if (!home || !string_format(buf, "%s/.tigrc", home))
1135                 return ERR;
1137         /* It's ok that the file doesn't exist. */
1138         file = fopen(buf, "r");
1139         if (!file)
1140                 return OK;
1142         if (read_properties(file, " \t", read_option) == ERR ||
1143             config_errors == TRUE)
1144                 fprintf(stderr, "Errors while loading %s.\n", buf);
1146         return OK;
1150 /*
1151  * The viewer
1152  */
1154 struct view;
1155 struct view_ops;
1157 /* The display array of active views and the index of the current view. */
1158 static struct view *display[2];
1159 static unsigned int current_view;
1161 #define foreach_displayed_view(view, i) \
1162         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1164 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1166 /* Current head and commit ID */
1167 static char ref_blob[SIZEOF_REF]        = "";
1168 static char ref_commit[SIZEOF_REF]      = "HEAD";
1169 static char ref_head[SIZEOF_REF]        = "HEAD";
1171 struct view {
1172         const char *name;       /* View name */
1173         const char *cmd_fmt;    /* Default command line format */
1174         const char *cmd_env;    /* Command line set via environment */
1175         const char *id;         /* Points to either of ref_{head,commit,blob} */
1177         struct view_ops *ops;   /* View operations */
1179         enum keymap keymap;     /* What keymap does this view have */
1181         char cmd[SIZEOF_STR];   /* Command buffer */
1182         char ref[SIZEOF_REF];   /* Hovered commit reference */
1183         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1185         int height, width;      /* The width and height of the main window */
1186         WINDOW *win;            /* The main window */
1187         WINDOW *title;          /* The title window living below the main window */
1189         /* Navigation */
1190         unsigned long offset;   /* Offset of the window top */
1191         unsigned long lineno;   /* Current line number */
1193         /* Searching */
1194         char grep[SIZEOF_STR];  /* Search string */
1195         regex_t *regex;         /* Pre-compiled regex */
1197         /* If non-NULL, points to the view that opened this view. If this view
1198          * is closed tig will switch back to the parent view. */
1199         struct view *parent;
1201         /* Buffering */
1202         unsigned long lines;    /* Total number of lines */
1203         struct line *line;      /* Line index */
1204         unsigned long line_size;/* Total number of allocated lines */
1205         unsigned int digits;    /* Number of digits in the lines member. */
1207         /* Loading */
1208         FILE *pipe;
1209         time_t start_time;
1210 };
1212 struct view_ops {
1213         /* What type of content being displayed. Used in the title bar. */
1214         const char *type;
1215         /* Draw one line; @lineno must be < view->height. */
1216         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1217         /* Read one line; updates view->line. */
1218         bool (*read)(struct view *view, char *data);
1219         /* Depending on view, change display based on current line. */
1220         bool (*enter)(struct view *view, struct line *line);
1221         /* Search for regex in a line. */
1222         bool (*grep)(struct view *view, struct line *line);
1223         /* Select line */
1224         void (*select)(struct view *view, struct line *line);
1225 };
1227 static struct view_ops pager_ops;
1228 static struct view_ops main_ops;
1229 static struct view_ops tree_ops;
1230 static struct view_ops blob_ops;
1232 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1233         { name, cmd, #env, ref, ops, map}
1235 #define VIEW_(id, name, ops, ref) \
1236         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1239 static struct view views[] = {
1240         VIEW_(MAIN,  "main",  &main_ops,  ref_head),
1241         VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
1242         VIEW_(LOG,   "log",   &pager_ops, ref_head),
1243         VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
1244         VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
1245         VIEW_(HELP,  "help",  &pager_ops, "static"),
1246         VIEW_(PAGER, "pager", &pager_ops, "static"),
1247 };
1249 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1251 #define foreach_view(view, i) \
1252         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1254 #define view_is_displayed(view) \
1255         (view == display[0] || view == display[1])
1257 static bool
1258 draw_view_line(struct view *view, unsigned int lineno)
1260         struct line *line;
1262         assert(view_is_displayed(view));
1264         if (view->offset + lineno >= view->lines)
1265                 return FALSE;
1267         line = &view->line[view->offset + lineno];
1269         if (view->offset + lineno == view->lineno)
1270                 view->ops->select(view, line);
1272         return view->ops->draw(view, line, lineno);
1275 static void
1276 redraw_view_from(struct view *view, int lineno)
1278         assert(0 <= lineno && lineno < view->height);
1280         for (; lineno < view->height; lineno++) {
1281                 if (!draw_view_line(view, lineno))
1282                         break;
1283         }
1285         redrawwin(view->win);
1286         wrefresh(view->win);
1289 static void
1290 redraw_view(struct view *view)
1292         wclear(view->win);
1293         redraw_view_from(view, 0);
1297 static void
1298 update_view_title(struct view *view)
1300         assert(view_is_displayed(view));
1302         if (view == display[current_view])
1303                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1304         else
1305                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1307         werase(view->title);
1308         wmove(view->title, 0, 0);
1310         if (*view->ref)
1311                 wprintw(view->title, "[%s] %s", view->name, view->ref);
1312         else
1313                 wprintw(view->title, "[%s]", view->name);
1315         if (view->lines || view->pipe) {
1316                 unsigned int view_lines = view->offset + view->height;
1317                 unsigned int lines = view->lines
1318                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1319                                    : 0;
1321                 wprintw(view->title, " - %s %d of %d (%d%%)",
1322                         view->ops->type,
1323                         view->lineno + 1,
1324                         view->lines,
1325                         lines);
1326         }
1328         if (view->pipe) {
1329                 time_t secs = time(NULL) - view->start_time;
1331                 /* Three git seconds are a long time ... */
1332                 if (secs > 2)
1333                         wprintw(view->title, " %lds", secs);
1334         }
1336         wmove(view->title, 0, view->width - 1);
1337         wrefresh(view->title);
1340 static void
1341 resize_display(void)
1343         int offset, i;
1344         struct view *base = display[0];
1345         struct view *view = display[1] ? display[1] : display[0];
1347         /* Setup window dimensions */
1349         getmaxyx(stdscr, base->height, base->width);
1351         /* Make room for the status window. */
1352         base->height -= 1;
1354         if (view != base) {
1355                 /* Horizontal split. */
1356                 view->width   = base->width;
1357                 view->height  = SCALE_SPLIT_VIEW(base->height);
1358                 base->height -= view->height;
1360                 /* Make room for the title bar. */
1361                 view->height -= 1;
1362         }
1364         /* Make room for the title bar. */
1365         base->height -= 1;
1367         offset = 0;
1369         foreach_displayed_view (view, i) {
1370                 if (!view->win) {
1371                         view->win = newwin(view->height, 0, offset, 0);
1372                         if (!view->win)
1373                                 die("Failed to create %s view", view->name);
1375                         scrollok(view->win, TRUE);
1377                         view->title = newwin(1, 0, offset + view->height, 0);
1378                         if (!view->title)
1379                                 die("Failed to create title window");
1381                 } else {
1382                         wresize(view->win, view->height, view->width);
1383                         mvwin(view->win,   offset, 0);
1384                         mvwin(view->title, offset + view->height, 0);
1385                 }
1387                 offset += view->height + 1;
1388         }
1391 static void
1392 redraw_display(void)
1394         struct view *view;
1395         int i;
1397         foreach_displayed_view (view, i) {
1398                 redraw_view(view);
1399                 update_view_title(view);
1400         }
1403 static void
1404 update_display_cursor(void)
1406         struct view *view = display[current_view];
1408         /* Move the cursor to the right-most column of the cursor line.
1409          *
1410          * XXX: This could turn out to be a bit expensive, but it ensures that
1411          * the cursor does not jump around. */
1412         if (view->lines) {
1413                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1414                 wrefresh(view->win);
1415         }
1418 /*
1419  * Navigation
1420  */
1422 /* Scrolling backend */
1423 static void
1424 do_scroll_view(struct view *view, int lines, bool redraw)
1426         assert(view_is_displayed(view));
1428         /* The rendering expects the new offset. */
1429         view->offset += lines;
1431         assert(0 <= view->offset && view->offset < view->lines);
1432         assert(lines);
1434         /* Redraw the whole screen if scrolling is pointless. */
1435         if (view->height < ABS(lines)) {
1436                 redraw_view(view);
1438         } else {
1439                 int line = lines > 0 ? view->height - lines : 0;
1440                 int end = line + ABS(lines);
1442                 wscrl(view->win, lines);
1444                 for (; line < end; line++) {
1445                         if (!draw_view_line(view, line))
1446                                 break;
1447                 }
1448         }
1450         /* Move current line into the view. */
1451         if (view->lineno < view->offset) {
1452                 view->lineno = view->offset;
1453                 draw_view_line(view, 0);
1455         } else if (view->lineno >= view->offset + view->height) {
1456                 if (view->lineno == view->offset + view->height) {
1457                         /* Clear the hidden line so it doesn't show if the view
1458                          * is scrolled up. */
1459                         wmove(view->win, view->height, 0);
1460                         wclrtoeol(view->win);
1461                 }
1462                 view->lineno = view->offset + view->height - 1;
1463                 draw_view_line(view, view->lineno - view->offset);
1464         }
1466         assert(view->offset <= view->lineno && view->lineno < view->lines);
1468         if (!redraw)
1469                 return;
1471         redrawwin(view->win);
1472         wrefresh(view->win);
1473         report("");
1476 /* Scroll frontend */
1477 static void
1478 scroll_view(struct view *view, enum request request)
1480         int lines = 1;
1482         switch (request) {
1483         case REQ_SCROLL_PAGE_DOWN:
1484                 lines = view->height;
1485         case REQ_SCROLL_LINE_DOWN:
1486                 if (view->offset + lines > view->lines)
1487                         lines = view->lines - view->offset;
1489                 if (lines == 0 || view->offset + view->height >= view->lines) {
1490                         report("Cannot scroll beyond the last line");
1491                         return;
1492                 }
1493                 break;
1495         case REQ_SCROLL_PAGE_UP:
1496                 lines = view->height;
1497         case REQ_SCROLL_LINE_UP:
1498                 if (lines > view->offset)
1499                         lines = view->offset;
1501                 if (lines == 0) {
1502                         report("Cannot scroll beyond the first line");
1503                         return;
1504                 }
1506                 lines = -lines;
1507                 break;
1509         default:
1510                 die("request %d not handled in switch", request);
1511         }
1513         do_scroll_view(view, lines, TRUE);
1516 /* Cursor moving */
1517 static void
1518 move_view(struct view *view, enum request request, bool redraw)
1520         int steps;
1522         switch (request) {
1523         case REQ_MOVE_FIRST_LINE:
1524                 steps = -view->lineno;
1525                 break;
1527         case REQ_MOVE_LAST_LINE:
1528                 steps = view->lines - view->lineno - 1;
1529                 break;
1531         case REQ_MOVE_PAGE_UP:
1532                 steps = view->height > view->lineno
1533                       ? -view->lineno : -view->height;
1534                 break;
1536         case REQ_MOVE_PAGE_DOWN:
1537                 steps = view->lineno + view->height >= view->lines
1538                       ? view->lines - view->lineno - 1 : view->height;
1539                 break;
1541         case REQ_MOVE_UP:
1542                 steps = -1;
1543                 break;
1545         case REQ_MOVE_DOWN:
1546                 steps = 1;
1547                 break;
1549         default:
1550                 die("request %d not handled in switch", request);
1551         }
1553         if (steps <= 0 && view->lineno == 0) {
1554                 report("Cannot move beyond the first line");
1555                 return;
1557         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1558                 report("Cannot move beyond the last line");
1559                 return;
1560         }
1562         /* Move the current line */
1563         view->lineno += steps;
1564         assert(0 <= view->lineno && view->lineno < view->lines);
1566         /* Repaint the old "current" line if we be scrolling */
1567         if (ABS(steps) < view->height) {
1568                 int prev_lineno = view->lineno - steps - view->offset;
1570                 wmove(view->win, prev_lineno, 0);
1571                 wclrtoeol(view->win);
1572                 draw_view_line(view,  prev_lineno);
1573         }
1575         /* Check whether the view needs to be scrolled */
1576         if (view->lineno < view->offset ||
1577             view->lineno >= view->offset + view->height) {
1578                 if (steps < 0 && -steps > view->offset) {
1579                         steps = -view->offset;
1581                 } else if (steps > 0) {
1582                         if (view->lineno == view->lines - 1 &&
1583                             view->lines > view->height) {
1584                                 steps = view->lines - view->offset - 1;
1585                                 if (steps >= view->height)
1586                                         steps -= view->height - 1;
1587                         }
1588                 }
1590                 do_scroll_view(view, steps, redraw);
1591                 return;
1592         }
1594         /* Draw the current line */
1595         draw_view_line(view, view->lineno - view->offset);
1597         if (!redraw)
1598                 return;
1600         redrawwin(view->win);
1601         wrefresh(view->win);
1602         report("");
1606 /*
1607  * Searching
1608  */
1610 static void search_view(struct view *view, enum request request, const char *search);
1612 static bool
1613 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1615         assert(view_is_displayed(view));
1617         if (!view->ops->grep(view, line))
1618                 return FALSE;
1620         if (lineno - view->offset >= view->height) {
1621                 view->offset = lineno;
1622                 view->lineno = lineno;
1623                 redraw_view(view);
1625         } else {
1626                 unsigned long old_lineno = view->lineno - view->offset;
1628                 view->lineno = lineno;
1630                 wmove(view->win, old_lineno, 0);
1631                 wclrtoeol(view->win);
1632                 draw_view_line(view, old_lineno);
1634                 draw_view_line(view, view->lineno - view->offset);
1635                 redrawwin(view->win);
1636                 wrefresh(view->win);
1637         }
1639         report("Line %ld matches '%s'", lineno + 1, view->grep);
1640         return TRUE;
1643 static void
1644 find_next(struct view *view, enum request request)
1646         unsigned long lineno = view->lineno;
1647         int direction;
1649         if (!*view->grep) {
1650                 if (!*opt_search)
1651                         report("No previous search");
1652                 else
1653                         search_view(view, request, opt_search);
1654                 return;
1655         }
1657         switch (request) {
1658         case REQ_SEARCH:
1659         case REQ_FIND_NEXT:
1660                 direction = 1;
1661                 break;
1663         case REQ_SEARCH_BACK:
1664         case REQ_FIND_PREV:
1665                 direction = -1;
1666                 break;
1668         default:
1669                 return;
1670         }
1672         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1673                 lineno += direction;
1675         /* Note, lineno is unsigned long so will wrap around in which case it
1676          * will become bigger than view->lines. */
1677         for (; lineno < view->lines; lineno += direction) {
1678                 struct line *line = &view->line[lineno];
1680                 if (find_next_line(view, lineno, line))
1681                         return;
1682         }
1684         report("No match found for '%s'", view->grep);
1687 static void
1688 search_view(struct view *view, enum request request, const char *search)
1690         int regex_err;
1692         if (view->regex) {
1693                 regfree(view->regex);
1694                 *view->grep = 0;
1695         } else {
1696                 view->regex = calloc(1, sizeof(*view->regex));
1697                 if (!view->regex)
1698                         return;
1699         }
1701         regex_err = regcomp(view->regex, search, REG_EXTENDED);
1702         if (regex_err != 0) {
1703                 char buf[SIZEOF_STR] = "unknown error";
1705                 regerror(regex_err, view->regex, buf, sizeof(buf));
1706                 report("Search failed: %s", buf);
1707                 return;
1708         }
1710         string_copy(view->grep, search);
1712         find_next(view, request);
1715 /*
1716  * Incremental updating
1717  */
1719 static void
1720 end_update(struct view *view)
1722         if (!view->pipe)
1723                 return;
1724         set_nonblocking_input(FALSE);
1725         if (view->pipe == stdin)
1726                 fclose(view->pipe);
1727         else
1728                 pclose(view->pipe);
1729         view->pipe = NULL;
1732 static bool
1733 begin_update(struct view *view)
1735         const char *id = view->id;
1737         if (view->pipe)
1738                 end_update(view);
1740         if (opt_cmd[0]) {
1741                 string_copy(view->cmd, opt_cmd);
1742                 opt_cmd[0] = 0;
1743                 /* When running random commands, the view ref could have become
1744                  * invalid so clear it. */
1745                 view->ref[0] = 0;
1747         } else if (view == VIEW(REQ_VIEW_TREE)) {
1748                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1750                 if (strcmp(view->vid, view->id))
1751                         opt_path[0] = 0;
1753                 if (!string_format(view->cmd, format, id, opt_path))
1754                         return FALSE;
1756         } else {
1757                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1759                 if (!string_format(view->cmd, format, id, id, id, id, id))
1760                         return FALSE;
1761         }
1763         /* Special case for the pager view. */
1764         if (opt_pipe) {
1765                 view->pipe = opt_pipe;
1766                 opt_pipe = NULL;
1767         } else {
1768                 view->pipe = popen(view->cmd, "r");
1769         }
1771         if (!view->pipe)
1772                 return FALSE;
1774         set_nonblocking_input(TRUE);
1776         view->offset = 0;
1777         view->lines  = 0;
1778         view->lineno = 0;
1779         string_copy(view->vid, id);
1781         if (view->line) {
1782                 int i;
1784                 for (i = 0; i < view->lines; i++)
1785                         if (view->line[i].data)
1786                                 free(view->line[i].data);
1788                 free(view->line);
1789                 view->line = NULL;
1790         }
1792         view->start_time = time(NULL);
1794         return TRUE;
1797 static struct line *
1798 realloc_lines(struct view *view, size_t line_size)
1800         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1802         if (!tmp)
1803                 return NULL;
1805         view->line = tmp;
1806         view->line_size = line_size;
1807         return view->line;
1810 static bool
1811 update_view(struct view *view)
1813         char in_buffer[BUFSIZ];
1814         char out_buffer[BUFSIZ * 2];
1815         char *line;
1816         /* The number of lines to read. If too low it will cause too much
1817          * redrawing (and possible flickering), if too high responsiveness
1818          * will suffer. */
1819         unsigned long lines = view->height;
1820         int redraw_from = -1;
1822         if (!view->pipe)
1823                 return TRUE;
1825         /* Only redraw if lines are visible. */
1826         if (view->offset + view->height >= view->lines)
1827                 redraw_from = view->lines - view->offset;
1829         /* FIXME: This is probably not perfect for backgrounded views. */
1830         if (!realloc_lines(view, view->lines + lines))
1831                 goto alloc_error;
1833         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1834                 size_t linelen = strlen(line);
1836                 if (linelen)
1837                         line[linelen - 1] = 0;
1839                 if (opt_iconv != ICONV_NONE) {
1840                         char *inbuf = line;
1841                         size_t inlen = linelen;
1843                         char *outbuf = out_buffer;
1844                         size_t outlen = sizeof(out_buffer);
1846                         size_t ret;
1848                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1849                         if (ret != (size_t) -1) {
1850                                 line = out_buffer;
1851                                 linelen = strlen(out_buffer);
1852                         }
1853                 }
1855                 if (!view->ops->read(view, line))
1856                         goto alloc_error;
1858                 if (lines-- == 1)
1859                         break;
1860         }
1862         {
1863                 int digits;
1865                 lines = view->lines;
1866                 for (digits = 0; lines; digits++)
1867                         lines /= 10;
1869                 /* Keep the displayed view in sync with line number scaling. */
1870                 if (digits != view->digits) {
1871                         view->digits = digits;
1872                         redraw_from = 0;
1873                 }
1874         }
1876         if (!view_is_displayed(view))
1877                 goto check_pipe;
1879         if (view == VIEW(REQ_VIEW_TREE)) {
1880                 /* Clear the view and redraw everything since the tree sorting
1881                  * might have rearranged things. */
1882                 redraw_view(view);
1884         } else if (redraw_from >= 0) {
1885                 /* If this is an incremental update, redraw the previous line
1886                  * since for commits some members could have changed when
1887                  * loading the main view. */
1888                 if (redraw_from > 0)
1889                         redraw_from--;
1891                 /* Incrementally draw avoids flickering. */
1892                 redraw_view_from(view, redraw_from);
1893         }
1895         /* Update the title _after_ the redraw so that if the redraw picks up a
1896          * commit reference in view->ref it'll be available here. */
1897         update_view_title(view);
1899 check_pipe:
1900         if (ferror(view->pipe)) {
1901                 report("Failed to read: %s", strerror(errno));
1902                 goto end;
1904         } else if (feof(view->pipe)) {
1905                 report("");
1906                 goto end;
1907         }
1909         return TRUE;
1911 alloc_error:
1912         report("Allocation failure");
1914 end:
1915         end_update(view);
1916         return FALSE;
1920 /*
1921  * View opening
1922  */
1924 static void open_help_view(struct view *view)
1926         char buf[BUFSIZ];
1927         int lines = ARRAY_SIZE(req_info) + 2;
1928         int i;
1930         if (view->lines > 0)
1931                 return;
1933         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1934                 if (!req_info[i].request)
1935                         lines++;
1937         view->line = calloc(lines, sizeof(*view->line));
1938         if (!view->line) {
1939                 report("Allocation failure");
1940                 return;
1941         }
1943         view->ops->read(view, "Quick reference for tig keybindings:");
1945         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
1946                 char *key;
1948                 if (!req_info[i].request) {
1949                         view->ops->read(view, "");
1950                         view->ops->read(view, req_info[i].help);
1951                         continue;
1952                 }
1954                 key = get_key(req_info[i].request);
1955                 if (!string_format(buf, "%-25s %s", key, req_info[i].help))
1956                         continue;
1958                 view->ops->read(view, buf);
1959         }
1962 enum open_flags {
1963         OPEN_DEFAULT = 0,       /* Use default view switching. */
1964         OPEN_SPLIT = 1,         /* Split current view. */
1965         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
1966         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
1967 };
1969 static void
1970 open_view(struct view *prev, enum request request, enum open_flags flags)
1972         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1973         bool split = !!(flags & OPEN_SPLIT);
1974         bool reload = !!(flags & OPEN_RELOAD);
1975         struct view *view = VIEW(request);
1976         int nviews = displayed_views();
1977         struct view *base_view = display[0];
1979         if (view == prev && nviews == 1 && !reload) {
1980                 report("Already in %s view", view->name);
1981                 return;
1982         }
1984         if (view == VIEW(REQ_VIEW_HELP)) {
1985                 open_help_view(view);
1987         } else if ((reload || strcmp(view->vid, view->id)) &&
1988                    !begin_update(view)) {
1989                 report("Failed to load %s view", view->name);
1990                 return;
1991         }
1993         if (split) {
1994                 display[1] = view;
1995                 if (!backgrounded)
1996                         current_view = 1;
1997         } else {
1998                 /* Maximize the current view. */
1999                 memset(display, 0, sizeof(display));
2000                 current_view = 0;
2001                 display[current_view] = view;
2002         }
2004         /* Resize the view when switching between split- and full-screen,
2005          * or when switching between two different full-screen views. */
2006         if (nviews != displayed_views() ||
2007             (nviews == 1 && base_view != display[0]))
2008                 resize_display();
2010         if (split && prev->lineno - prev->offset >= prev->height) {
2011                 /* Take the title line into account. */
2012                 int lines = prev->lineno - prev->offset - prev->height + 1;
2014                 /* Scroll the view that was split if the current line is
2015                  * outside the new limited view. */
2016                 do_scroll_view(prev, lines, TRUE);
2017         }
2019         if (prev && view != prev) {
2020                 if (split && !backgrounded) {
2021                         /* "Blur" the previous view. */
2022                         update_view_title(prev);
2023                 }
2025                 view->parent = prev;
2026         }
2028         if (view->pipe && view->lines == 0) {
2029                 /* Clear the old view and let the incremental updating refill
2030                  * the screen. */
2031                 wclear(view->win);
2032                 report("");
2033         } else {
2034                 redraw_view(view);
2035                 report("");
2036         }
2038         /* If the view is backgrounded the above calls to report()
2039          * won't redraw the view title. */
2040         if (backgrounded)
2041                 update_view_title(view);
2045 /*
2046  * User request switch noodle
2047  */
2049 static int
2050 view_driver(struct view *view, enum request request)
2052         int i;
2054         switch (request) {
2055         case REQ_MOVE_UP:
2056         case REQ_MOVE_DOWN:
2057         case REQ_MOVE_PAGE_UP:
2058         case REQ_MOVE_PAGE_DOWN:
2059         case REQ_MOVE_FIRST_LINE:
2060         case REQ_MOVE_LAST_LINE:
2061                 move_view(view, request, TRUE);
2062                 break;
2064         case REQ_SCROLL_LINE_DOWN:
2065         case REQ_SCROLL_LINE_UP:
2066         case REQ_SCROLL_PAGE_DOWN:
2067         case REQ_SCROLL_PAGE_UP:
2068                 scroll_view(view, request);
2069                 break;
2071         case REQ_VIEW_BLOB:
2072                 if (!ref_blob[0]) {
2073                         report("No file chosen, press 't' to open tree view");
2074                         break;
2075                 }
2076                 /* Fall-through */
2077         case REQ_VIEW_MAIN:
2078         case REQ_VIEW_DIFF:
2079         case REQ_VIEW_LOG:
2080         case REQ_VIEW_TREE:
2081         case REQ_VIEW_HELP:
2082         case REQ_VIEW_PAGER:
2083                 open_view(view, request, OPEN_DEFAULT);
2084                 break;
2086         case REQ_NEXT:
2087         case REQ_PREVIOUS:
2088                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2090                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2091                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2092                    (view == VIEW(REQ_VIEW_BLOB) &&
2093                      view->parent == VIEW(REQ_VIEW_TREE))) {
2094                         bool redraw = display[1] == view;
2096                         view = view->parent;
2097                         move_view(view, request, redraw);
2098                         if (redraw)
2099                                 update_view_title(view);
2100                 } else {
2101                         move_view(view, request, TRUE);
2102                         break;
2103                 }
2104                 /* Fall-through */
2106         case REQ_ENTER:
2107                 if (!view->lines) {
2108                         report("Nothing to enter");
2109                         break;
2110                 }
2111                 return view->ops->enter(view, &view->line[view->lineno]);
2113         case REQ_VIEW_NEXT:
2114         {
2115                 int nviews = displayed_views();
2116                 int next_view = (current_view + 1) % nviews;
2118                 if (next_view == current_view) {
2119                         report("Only one view is displayed");
2120                         break;
2121                 }
2123                 current_view = next_view;
2124                 /* Blur out the title of the previous view. */
2125                 update_view_title(view);
2126                 report("");
2127                 break;
2128         }
2129         case REQ_TOGGLE_LINENO:
2130                 opt_line_number = !opt_line_number;
2131                 redraw_display();
2132                 break;
2134         case REQ_TOGGLE_REV_GRAPH:
2135                 opt_rev_graph = !opt_rev_graph;
2136                 redraw_display();
2137                 break;
2139         case REQ_PROMPT:
2140                 /* Always reload^Wrerun commands from the prompt. */
2141                 open_view(view, opt_request, OPEN_RELOAD);
2142                 break;
2144         case REQ_SEARCH:
2145         case REQ_SEARCH_BACK:
2146                 search_view(view, request, opt_search);
2147                 break;
2149         case REQ_FIND_NEXT:
2150         case REQ_FIND_PREV:
2151                 find_next(view, request);
2152                 break;
2154         case REQ_STOP_LOADING:
2155                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2156                         view = &views[i];
2157                         if (view->pipe)
2158                                 report("Stopped loading the %s view", view->name),
2159                         end_update(view);
2160                 }
2161                 break;
2163         case REQ_SHOW_VERSION:
2164                 report("%s (built %s)", VERSION, __DATE__);
2165                 return TRUE;
2167         case REQ_SCREEN_RESIZE:
2168                 resize_display();
2169                 /* Fall-through */
2170         case REQ_SCREEN_REDRAW:
2171                 redraw_display();
2172                 break;
2174         case REQ_NONE:
2175                 doupdate();
2176                 return TRUE;
2178         case REQ_VIEW_CLOSE:
2179                 /* XXX: Mark closed views by letting view->parent point to the
2180                  * view itself. Parents to closed view should never be
2181                  * followed. */
2182                 if (view->parent &&
2183                     view->parent->parent != view->parent) {
2184                         memset(display, 0, sizeof(display));
2185                         current_view = 0;
2186                         display[current_view] = view->parent;
2187                         view->parent = view;
2188                         resize_display();
2189                         redraw_display();
2190                         break;
2191                 }
2192                 /* Fall-through */
2193         case REQ_QUIT:
2194                 return FALSE;
2196         default:
2197                 /* An unknown key will show most commonly used commands. */
2198                 report("Unknown key, press 'h' for help");
2199                 return TRUE;
2200         }
2202         return TRUE;
2206 /*
2207  * Pager backend
2208  */
2210 static bool
2211 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2213         char *text = line->data;
2214         enum line_type type = line->type;
2215         int textlen = strlen(text);
2216         int attr;
2218         wmove(view->win, lineno, 0);
2220         if (view->offset + lineno == view->lineno) {
2221                 type = LINE_CURSOR;
2222                 wchgat(view->win, -1, 0, type, NULL);
2223         }
2225         attr = get_line_attr(type);
2226         wattrset(view->win, attr);
2228         if (opt_line_number || opt_tab_size < TABSIZE) {
2229                 static char spaces[] = "                    ";
2230                 int col_offset = 0, col = 0;
2232                 if (opt_line_number) {
2233                         unsigned long real_lineno = view->offset + lineno + 1;
2235                         if (real_lineno == 1 ||
2236                             (real_lineno % opt_num_interval) == 0) {
2237                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2239                         } else {
2240                                 waddnstr(view->win, spaces,
2241                                          MIN(view->digits, STRING_SIZE(spaces)));
2242                         }
2243                         waddstr(view->win, ": ");
2244                         col_offset = view->digits + 2;
2245                 }
2247                 while (text && col_offset + col < view->width) {
2248                         int cols_max = view->width - col_offset - col;
2249                         char *pos = text;
2250                         int cols;
2252                         if (*text == '\t') {
2253                                 text++;
2254                                 assert(sizeof(spaces) > TABSIZE);
2255                                 pos = spaces;
2256                                 cols = opt_tab_size - (col % opt_tab_size);
2258                         } else {
2259                                 text = strchr(text, '\t');
2260                                 cols = line ? text - pos : strlen(pos);
2261                         }
2263                         waddnstr(view->win, pos, MIN(cols, cols_max));
2264                         col += cols;
2265                 }
2267         } else {
2268                 int col = 0, pos = 0;
2270                 for (; pos < textlen && col < view->width; pos++, col++)
2271                         if (text[pos] == '\t')
2272                                 col += TABSIZE - (col % TABSIZE) - 1;
2274                 waddnstr(view->win, text, pos);
2275         }
2277         return TRUE;
2280 static bool
2281 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2283         char refbuf[SIZEOF_STR];
2284         char *ref = NULL;
2285         FILE *pipe;
2287         if (!string_format(refbuf, "git describe %s", commit_id))
2288                 return TRUE;
2290         pipe = popen(refbuf, "r");
2291         if (!pipe)
2292                 return TRUE;
2294         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2295                 ref = chomp_string(ref);
2296         pclose(pipe);
2298         if (!ref || !*ref)
2299                 return TRUE;
2301         /* This is the only fatal call, since it can "corrupt" the buffer. */
2302         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2303                 return FALSE;
2305         return TRUE;
2308 static void
2309 add_pager_refs(struct view *view, struct line *line)
2311         char buf[SIZEOF_STR];
2312         char *commit_id = line->data + STRING_SIZE("commit ");
2313         struct ref **refs;
2314         size_t bufpos = 0, refpos = 0;
2315         const char *sep = "Refs: ";
2316         bool is_tag = FALSE;
2318         assert(line->type == LINE_COMMIT);
2320         refs = get_refs(commit_id);
2321         if (!refs) {
2322                 if (view == VIEW(REQ_VIEW_DIFF))
2323                         goto try_add_describe_ref;
2324                 return;
2325         }
2327         do {
2328                 struct ref *ref = refs[refpos];
2329                 char *fmt = ref->tag ? "%s[%s]" : "%s%s";
2331                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2332                         return;
2333                 sep = ", ";
2334                 if (ref->tag)
2335                         is_tag = TRUE;
2336         } while (refs[refpos++]->next);
2338         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2339 try_add_describe_ref:
2340                 /* Add <tag>-g<commit_id> "fake" reference. */
2341                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2342                         return;
2343         }
2345         if (bufpos == 0)
2346                 return;
2348         if (!realloc_lines(view, view->line_size + 1))
2349                 return;
2351         line = &view->line[view->lines];
2352         line->data = strdup(buf);
2353         if (!line->data)
2354                 return;
2356         line->type = LINE_PP_REFS;
2357         view->lines++;
2360 static bool
2361 pager_read(struct view *view, char *data)
2363         struct line *line = &view->line[view->lines];
2365         line->data = strdup(data);
2366         if (!line->data)
2367                 return FALSE;
2369         line->type = get_line_type(line->data);
2370         view->lines++;
2372         if (line->type == LINE_COMMIT &&
2373             (view == VIEW(REQ_VIEW_DIFF) ||
2374              view == VIEW(REQ_VIEW_LOG)))
2375                 add_pager_refs(view, line);
2377         return TRUE;
2380 static bool
2381 pager_enter(struct view *view, struct line *line)
2383         int split = 0;
2385         if (line->type == LINE_COMMIT &&
2386            (view == VIEW(REQ_VIEW_LOG) ||
2387             view == VIEW(REQ_VIEW_PAGER))) {
2388                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2389                 split = 1;
2390         }
2392         /* Always scroll the view even if it was split. That way
2393          * you can use Enter to scroll through the log view and
2394          * split open each commit diff. */
2395         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2397         /* FIXME: A minor workaround. Scrolling the view will call report("")
2398          * but if we are scrolling a non-current view this won't properly
2399          * update the view title. */
2400         if (split)
2401                 update_view_title(view);
2403         return TRUE;
2406 static bool
2407 pager_grep(struct view *view, struct line *line)
2409         regmatch_t pmatch;
2410         char *text = line->data;
2412         if (!*text)
2413                 return FALSE;
2415         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2416                 return FALSE;
2418         return TRUE;
2421 static void
2422 pager_select(struct view *view, struct line *line)
2424         if (line->type == LINE_COMMIT) {
2425                 char *text = line->data;
2427                 string_copy(view->ref, text + 7);
2428                 string_copy(ref_commit, view->ref);
2429         }
2432 static struct view_ops pager_ops = {
2433         "line",
2434         pager_draw,
2435         pager_read,
2436         pager_enter,
2437         pager_grep,
2438         pager_select,
2439 };
2442 /*
2443  * Tree backend
2444  */
2446 /* Parse output from git ls-tree:
2447  *
2448  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2449  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2450  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2451  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2452  */
2454 #define SIZEOF_TREE_ATTR \
2455         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2457 #define TREE_UP_FORMAT "040000 tree %s\t.."
2459 static int
2460 tree_compare_entry(enum line_type type1, char *name1,
2461                    enum line_type type2, char *name2)
2463         if (type1 != type2) {
2464                 if (type1 == LINE_TREE_DIR)
2465                         return -1;
2466                 return 1;
2467         }
2469         return strcmp(name1, name2);
2472 static bool
2473 tree_read(struct view *view, char *text)
2475         size_t textlen = strlen(text);
2476         char buf[SIZEOF_STR];
2477         unsigned long pos;
2478         enum line_type type;
2479         bool first_read = view->lines == 0;
2481         if (textlen <= SIZEOF_TREE_ATTR)
2482                 return FALSE;
2484         type = text[STRING_SIZE("100644 ")] == 't'
2485              ? LINE_TREE_DIR : LINE_TREE_FILE;
2487         if (first_read) {
2488                 /* Add path info line */
2489                 if (string_format(buf, "Directory path /%s", opt_path) &&
2490                     realloc_lines(view, view->line_size + 1) &&
2491                     pager_read(view, buf))
2492                         view->line[view->lines - 1].type = LINE_DEFAULT;
2493                 else
2494                         return FALSE;
2496                 /* Insert "link" to parent directory. */
2497                 if (*opt_path &&
2498                     string_format(buf, TREE_UP_FORMAT, view->ref) &&
2499                     realloc_lines(view, view->line_size + 1) &&
2500                     pager_read(view, buf))
2501                         view->line[view->lines - 1].type = LINE_TREE_DIR;
2502                 else if (*opt_path)
2503                         return FALSE;
2504         }
2506         /* Strip the path part ... */
2507         if (*opt_path) {
2508                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2509                 size_t striplen = strlen(opt_path);
2510                 char *path = text + SIZEOF_TREE_ATTR;
2512                 if (pathlen > striplen)
2513                         memmove(path, path + striplen,
2514                                 pathlen - striplen + 1);
2515         }
2517         /* Skip "Directory ..." and ".." line. */
2518         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2519                 struct line *line = &view->line[pos];
2520                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2521                 char *path2 = text + SIZEOF_TREE_ATTR;
2522                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2524                 if (cmp <= 0)
2525                         continue;
2527                 text = strdup(text);
2528                 if (!text)
2529                         return FALSE;
2531                 if (view->lines > pos)
2532                         memmove(&view->line[pos + 1], &view->line[pos],
2533                                 (view->lines - pos) * sizeof(*line));
2535                 line = &view->line[pos];
2536                 line->data = text;
2537                 line->type = type;
2538                 view->lines++;
2539                 return TRUE;
2540         }
2542         if (!pager_read(view, text))
2543                 return FALSE;
2545         /* Move the current line to the first tree entry. */
2546         if (first_read)
2547                 view->lineno++;
2549         view->line[view->lines - 1].type = type;
2550         return TRUE;
2553 static bool
2554 tree_enter(struct view *view, struct line *line)
2556         enum open_flags flags = OPEN_DEFAULT;
2557         char *data = line->data;
2558         enum request request;
2560         switch (line->type) {
2561         case LINE_TREE_DIR:
2562                 /* Depending on whether it is a subdir or parent (updir?) link
2563                  * mangle the path buffer. */
2564                 if (line == &view->line[1] && *opt_path) {
2565                         size_t path_len = strlen(opt_path);
2566                         char *dirsep = opt_path + path_len - 1;
2568                         while (dirsep > opt_path && dirsep[-1] != '/')
2569                                 dirsep--;
2571                         dirsep[0] = 0;
2573                 } else {
2574                         size_t pathlen = strlen(opt_path);
2575                         size_t origlen = pathlen;
2576                         char *basename = data + SIZEOF_TREE_ATTR;
2578                         if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
2579                                 opt_path[origlen] = 0;
2580                                 return TRUE;
2581                         }
2582                 }
2584                 /* Trees and subtrees share the same ID, so they are not not
2585                  * unique like blobs. */
2586                 flags |= OPEN_RELOAD;
2587                 request = REQ_VIEW_TREE;
2588                 break;
2590         case LINE_TREE_FILE:
2591                 /* This causes the blob view to become split, and not having it
2592                  * in the tree dir case will make the blob view automatically
2593                  * disappear when moving to a different directory. */
2594                 flags |= OPEN_SPLIT;
2595                 request = REQ_VIEW_BLOB;
2596                 break;
2598         default:
2599                 return TRUE;
2600         }
2602         open_view(view, request, flags);
2604         if (!VIEW(request)->pipe)
2605                 return TRUE;
2607         /* For tree views insert the path to the parent as the first line. */
2608         if (request == REQ_VIEW_BLOB) {
2609                 /* Mirror what is showed in the title bar. */
2610                 string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40);
2611                 string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
2612                 return TRUE;
2613         }
2615         return TRUE;
2618 static void
2619 tree_select(struct view *view, struct line *line)
2621         if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) {
2622                 char *text = line->data;
2624                 string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
2625                 string_copy(ref_blob, view->ref);
2626         }
2629 static struct view_ops tree_ops = {
2630         "file",
2631         pager_draw,
2632         tree_read,
2633         tree_enter,
2634         pager_grep,
2635         tree_select,
2636 };
2638 static bool
2639 blob_read(struct view *view, char *line)
2641         bool state = pager_read(view, line);
2643         if (state == TRUE)
2644                 view->line[view->lines - 1].type = LINE_DEFAULT;
2646         return state;
2649 static struct view_ops blob_ops = {
2650         "line",
2651         pager_draw,
2652         blob_read,
2653         pager_enter,
2654         pager_grep,
2655         pager_select,
2656 };
2659 /*
2660  * Main view backend
2661  */
2663 struct commit {
2664         char id[41];                    /* SHA1 ID. */
2665         char title[75];                 /* First line of the commit message. */
2666         char author[75];                /* Author of the commit. */
2667         struct tm time;                 /* Date from the author ident. */
2668         struct ref **refs;              /* Repository references. */
2669         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
2670         size_t graph_size;              /* The width of the graph array. */
2671 };
2673 static bool
2674 main_draw(struct view *view, struct line *line, unsigned int lineno)
2676         char buf[DATE_COLS + 1];
2677         struct commit *commit = line->data;
2678         enum line_type type;
2679         int col = 0;
2680         size_t timelen;
2681         size_t authorlen;
2682         int trimmed = 1;
2684         if (!*commit->author)
2685                 return FALSE;
2687         wmove(view->win, lineno, col);
2689         if (view->offset + lineno == view->lineno) {
2690                 type = LINE_CURSOR;
2691                 wattrset(view->win, get_line_attr(type));
2692                 wchgat(view->win, -1, 0, type, NULL);
2694         } else {
2695                 type = LINE_MAIN_COMMIT;
2696                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
2697         }
2699         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
2700         waddnstr(view->win, buf, timelen);
2701         waddstr(view->win, " ");
2703         col += DATE_COLS;
2704         wmove(view->win, lineno, col);
2705         if (type != LINE_CURSOR)
2706                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
2708         if (opt_utf8) {
2709                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
2710         } else {
2711                 authorlen = strlen(commit->author);
2712                 if (authorlen > AUTHOR_COLS - 2) {
2713                         authorlen = AUTHOR_COLS - 2;
2714                         trimmed = 1;
2715                 }
2716         }
2718         if (trimmed) {
2719                 waddnstr(view->win, commit->author, authorlen);
2720                 if (type != LINE_CURSOR)
2721                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
2722                 waddch(view->win, '~');
2723         } else {
2724                 waddstr(view->win, commit->author);
2725         }
2727         col += AUTHOR_COLS;
2728         if (type != LINE_CURSOR)
2729                 wattrset(view->win, A_NORMAL);
2731         if (opt_rev_graph && commit->graph_size) {
2732                 size_t i;
2734                 wmove(view->win, lineno, col);
2735                 /* Using waddch() instead of waddnstr() ensures that
2736                  * they'll be rendered correctly for the cursor line. */
2737                 for (i = 0; i < commit->graph_size; i++)
2738                         waddch(view->win, commit->graph[i]);
2740                 col += commit->graph_size + 1;
2741         }
2743         wmove(view->win, lineno, col);
2745         if (commit->refs) {
2746                 size_t i = 0;
2748                 do {
2749                         if (type == LINE_CURSOR)
2750                                 ;
2751                         else if (commit->refs[i]->tag)
2752                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
2753                         else
2754                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
2755                         waddstr(view->win, "[");
2756                         waddstr(view->win, commit->refs[i]->name);
2757                         waddstr(view->win, "]");
2758                         if (type != LINE_CURSOR)
2759                                 wattrset(view->win, A_NORMAL);
2760                         waddstr(view->win, " ");
2761                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
2762                 } while (commit->refs[i++]->next);
2763         }
2765         if (type != LINE_CURSOR)
2766                 wattrset(view->win, get_line_attr(type));
2768         {
2769                 int titlelen = strlen(commit->title);
2771                 if (col + titlelen > view->width)
2772                         titlelen = view->width - col;
2774                 waddnstr(view->win, commit->title, titlelen);
2775         }
2777         return TRUE;
2780 /* Reads git log --pretty=raw output and parses it into the commit struct. */
2781 static bool
2782 main_read(struct view *view, char *line)
2784         enum line_type type = get_line_type(line);
2785         struct commit *commit = view->lines
2786                               ? view->line[view->lines - 1].data : NULL;
2788         switch (type) {
2789         case LINE_COMMIT:
2790                 commit = calloc(1, sizeof(struct commit));
2791                 if (!commit)
2792                         return FALSE;
2794                 line += STRING_SIZE("commit ");
2796                 view->line[view->lines++].data = commit;
2797                 string_copy(commit->id, line);
2798                 commit->refs = get_refs(commit->id);
2799                 commit->graph[commit->graph_size++] = ACS_LTEE;
2800                 break;
2802         case LINE_AUTHOR:
2803         {
2804                 char *ident = line + STRING_SIZE("author ");
2805                 char *end = strchr(ident, '<');
2807                 if (!commit)
2808                         break;
2810                 if (end) {
2811                         char *email = end + 1;
2813                         for (; end > ident && isspace(end[-1]); end--) ;
2815                         if (end == ident && *email) {
2816                                 ident = email;
2817                                 end = strchr(ident, '>');
2818                                 for (; end > ident && isspace(end[-1]); end--) ;
2819                         }
2820                         *end = 0;
2821                 }
2823                 /* End is NULL or ident meaning there's no author. */
2824                 if (end <= ident)
2825                         ident = "Unknown";
2827                 string_copy(commit->author, ident);
2829                 /* Parse epoch and timezone */
2830                 if (end) {
2831                         char *secs = strchr(end + 1, '>');
2832                         char *zone;
2833                         time_t time;
2835                         if (!secs || secs[1] != ' ')
2836                                 break;
2838                         secs += 2;
2839                         time = (time_t) atol(secs);
2840                         zone = strchr(secs, ' ');
2841                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
2842                                 long tz;
2844                                 zone++;
2845                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
2846                                 tz += ('0' - zone[2]) * 60 * 60;
2847                                 tz += ('0' - zone[3]) * 60;
2848                                 tz += ('0' - zone[4]) * 60;
2850                                 if (zone[0] == '-')
2851                                         tz = -tz;
2853                                 time -= tz;
2854                         }
2855                         gmtime_r(&time, &commit->time);
2856                 }
2857                 break;
2858         }
2859         default:
2860                 if (!commit)
2861                         break;
2863                 /* Fill in the commit title if it has not already been set. */
2864                 if (commit->title[0])
2865                         break;
2867                 /* Require titles to start with a non-space character at the
2868                  * offset used by git log. */
2869                 /* FIXME: More gracefull handling of titles; append "..." to
2870                  * shortened titles, etc. */
2871                 if (strncmp(line, "    ", 4) ||
2872                     isspace(line[4]))
2873                         break;
2875                 string_copy(commit->title, line + 4);
2876         }
2878         return TRUE;
2881 static bool
2882 main_enter(struct view *view, struct line *line)
2884         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2886         open_view(view, REQ_VIEW_DIFF, flags);
2887         return TRUE;
2890 static bool
2891 main_grep(struct view *view, struct line *line)
2893         struct commit *commit = line->data;
2894         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
2895         char buf[DATE_COLS + 1];
2896         regmatch_t pmatch;
2898         for (state = S_TITLE; state < S_END; state++) {
2899                 char *text;
2901                 switch (state) {
2902                 case S_TITLE:   text = commit->title;   break;
2903                 case S_AUTHOR:  text = commit->author;  break;
2904                 case S_DATE:
2905                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
2906                                 continue;
2907                         text = buf;
2908                         break;
2910                 default:
2911                         return FALSE;
2912                 }
2914                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
2915                         return TRUE;
2916         }
2918         return FALSE;
2921 static void
2922 main_select(struct view *view, struct line *line)
2924         struct commit *commit = line->data;
2926         string_copy(view->ref, commit->id);
2927         string_copy(ref_commit, view->ref);
2930 static struct view_ops main_ops = {
2931         "commit",
2932         main_draw,
2933         main_read,
2934         main_enter,
2935         main_grep,
2936         main_select,
2937 };
2940 /*
2941  * Unicode / UTF-8 handling
2942  *
2943  * NOTE: Much of the following code for dealing with unicode is derived from
2944  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
2945  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
2946  */
2948 /* I've (over)annotated a lot of code snippets because I am not entirely
2949  * confident that the approach taken by this small UTF-8 interface is correct.
2950  * --jonas */
2952 static inline int
2953 unicode_width(unsigned long c)
2955         if (c >= 0x1100 &&
2956            (c <= 0x115f                         /* Hangul Jamo */
2957             || c == 0x2329
2958             || c == 0x232a
2959             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
2960                                                 /* CJK ... Yi */
2961             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
2962             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
2963             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
2964             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
2965             || (c >= 0xffe0  && c <= 0xffe6)
2966             || (c >= 0x20000 && c <= 0x2fffd)
2967             || (c >= 0x30000 && c <= 0x3fffd)))
2968                 return 2;
2970         return 1;
2973 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
2974  * Illegal bytes are set one. */
2975 static const unsigned char utf8_bytes[256] = {
2976         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,
2977         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,
2978         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,
2979         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,
2980         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,
2981         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,
2982         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,
2983         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,
2984 };
2986 /* Decode UTF-8 multi-byte representation into a unicode character. */
2987 static inline unsigned long
2988 utf8_to_unicode(const char *string, size_t length)
2990         unsigned long unicode;
2992         switch (length) {
2993         case 1:
2994                 unicode  =   string[0];
2995                 break;
2996         case 2:
2997                 unicode  =  (string[0] & 0x1f) << 6;
2998                 unicode +=  (string[1] & 0x3f);
2999                 break;
3000         case 3:
3001                 unicode  =  (string[0] & 0x0f) << 12;
3002                 unicode += ((string[1] & 0x3f) << 6);
3003                 unicode +=  (string[2] & 0x3f);
3004                 break;
3005         case 4:
3006                 unicode  =  (string[0] & 0x0f) << 18;
3007                 unicode += ((string[1] & 0x3f) << 12);
3008                 unicode += ((string[2] & 0x3f) << 6);
3009                 unicode +=  (string[3] & 0x3f);
3010                 break;
3011         case 5:
3012                 unicode  =  (string[0] & 0x0f) << 24;
3013                 unicode += ((string[1] & 0x3f) << 18);
3014                 unicode += ((string[2] & 0x3f) << 12);
3015                 unicode += ((string[3] & 0x3f) << 6);
3016                 unicode +=  (string[4] & 0x3f);
3017                 break;
3018         case 6:
3019                 unicode  =  (string[0] & 0x01) << 30;
3020                 unicode += ((string[1] & 0x3f) << 24);
3021                 unicode += ((string[2] & 0x3f) << 18);
3022                 unicode += ((string[3] & 0x3f) << 12);
3023                 unicode += ((string[4] & 0x3f) << 6);
3024                 unicode +=  (string[5] & 0x3f);
3025                 break;
3026         default:
3027                 die("Invalid unicode length");
3028         }
3030         /* Invalid characters could return the special 0xfffd value but NUL
3031          * should be just as good. */
3032         return unicode > 0xffff ? 0 : unicode;
3035 /* Calculates how much of string can be shown within the given maximum width
3036  * and sets trimmed parameter to non-zero value if all of string could not be
3037  * shown.
3038  *
3039  * Additionally, adds to coloffset how many many columns to move to align with
3040  * the expected position. Takes into account how multi-byte and double-width
3041  * characters will effect the cursor position.
3042  *
3043  * Returns the number of bytes to output from string to satisfy max_width. */
3044 static size_t
3045 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
3047         const char *start = string;
3048         const char *end = strchr(string, '\0');
3049         size_t mbwidth = 0;
3050         size_t width = 0;
3052         *trimmed = 0;
3054         while (string < end) {
3055                 int c = *(unsigned char *) string;
3056                 unsigned char bytes = utf8_bytes[c];
3057                 size_t ucwidth;
3058                 unsigned long unicode;
3060                 if (string + bytes > end)
3061                         break;
3063                 /* Change representation to figure out whether
3064                  * it is a single- or double-width character. */
3066                 unicode = utf8_to_unicode(string, bytes);
3067                 /* FIXME: Graceful handling of invalid unicode character. */
3068                 if (!unicode)
3069                         break;
3071                 ucwidth = unicode_width(unicode);
3072                 width  += ucwidth;
3073                 if (width > max_width) {
3074                         *trimmed = 1;
3075                         break;
3076                 }
3078                 /* The column offset collects the differences between the
3079                  * number of bytes encoding a character and the number of
3080                  * columns will be used for rendering said character.
3081                  *
3082                  * So if some character A is encoded in 2 bytes, but will be
3083                  * represented on the screen using only 1 byte this will and up
3084                  * adding 1 to the multi-byte column offset.
3085                  *
3086                  * Assumes that no double-width character can be encoding in
3087                  * less than two bytes. */
3088                 if (bytes > ucwidth)
3089                         mbwidth += bytes - ucwidth;
3091                 string  += bytes;
3092         }
3094         *coloffset += mbwidth;
3096         return string - start;
3100 /*
3101  * Status management
3102  */
3104 /* Whether or not the curses interface has been initialized. */
3105 static bool cursed = FALSE;
3107 /* The status window is used for polling keystrokes. */
3108 static WINDOW *status_win;
3110 /* Update status and title window. */
3111 static void
3112 report(const char *msg, ...)
3114         static bool empty = TRUE;
3115         struct view *view = display[current_view];
3117         if (!empty || *msg) {
3118                 va_list args;
3120                 va_start(args, msg);
3122                 werase(status_win);
3123                 wmove(status_win, 0, 0);
3124                 if (*msg) {
3125                         vwprintw(status_win, msg, args);
3126                         empty = FALSE;
3127                 } else {
3128                         empty = TRUE;
3129                 }
3130                 wrefresh(status_win);
3132                 va_end(args);
3133         }
3135         update_view_title(view);
3136         update_display_cursor();
3139 /* Controls when nodelay should be in effect when polling user input. */
3140 static void
3141 set_nonblocking_input(bool loading)
3143         static unsigned int loading_views;
3145         if ((loading == FALSE && loading_views-- == 1) ||
3146             (loading == TRUE  && loading_views++ == 0))
3147                 nodelay(status_win, loading);
3150 static void
3151 init_display(void)
3153         int x, y;
3155         /* Initialize the curses library */
3156         if (isatty(STDIN_FILENO)) {
3157                 cursed = !!initscr();
3158         } else {
3159                 /* Leave stdin and stdout alone when acting as a pager. */
3160                 FILE *io = fopen("/dev/tty", "r+");
3162                 if (!io)
3163                         die("Failed to open /dev/tty");
3164                 cursed = !!newterm(NULL, io, io);
3165         }
3167         if (!cursed)
3168                 die("Failed to initialize curses");
3170         nonl();         /* Tell curses not to do NL->CR/NL on output */
3171         cbreak();       /* Take input chars one at a time, no wait for \n */
3172         noecho();       /* Don't echo input */
3173         leaveok(stdscr, TRUE);
3175         if (has_colors())
3176                 init_colors();
3178         getmaxyx(stdscr, y, x);
3179         status_win = newwin(1, 0, y - 1, 0);
3180         if (!status_win)
3181                 die("Failed to create status window");
3183         /* Enable keyboard mapping */
3184         keypad(status_win, TRUE);
3185         wbkgdset(status_win, get_line_attr(LINE_STATUS));
3188 static char *
3189 read_prompt(const char *prompt)
3191         enum { READING, STOP, CANCEL } status = READING;
3192         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
3193         int pos = 0;
3195         while (status == READING) {
3196                 struct view *view;
3197                 int i, key;
3199                 foreach_view (view, i)
3200                         update_view(view);
3202                 report("%s%.*s", prompt, pos, buf);
3203                 /* Refresh, accept single keystroke of input */
3204                 key = wgetch(status_win);
3205                 switch (key) {
3206                 case KEY_RETURN:
3207                 case KEY_ENTER:
3208                 case '\n':
3209                         status = pos ? STOP : CANCEL;
3210                         break;
3212                 case KEY_BACKSPACE:
3213                         if (pos > 0)
3214                                 pos--;
3215                         else
3216                                 status = CANCEL;
3217                         break;
3219                 case KEY_ESC:
3220                         status = CANCEL;
3221                         break;
3223                 case ERR:
3224                         break;
3226                 default:
3227                         if (pos >= sizeof(buf)) {
3228                                 report("Input string too long");
3229                                 return NULL;
3230                         }
3232                         if (isprint(key))
3233                                 buf[pos++] = (char) key;
3234                 }
3235         }
3237         if (status == CANCEL) {
3238                 /* Clear the status window */
3239                 report("");
3240                 return NULL;
3241         }
3243         buf[pos++] = 0;
3245         return buf;
3248 /*
3249  * Repository references
3250  */
3252 static struct ref *refs;
3253 static size_t refs_size;
3255 /* Id <-> ref store */
3256 static struct ref ***id_refs;
3257 static size_t id_refs_size;
3259 static struct ref **
3260 get_refs(char *id)
3262         struct ref ***tmp_id_refs;
3263         struct ref **ref_list = NULL;
3264         size_t ref_list_size = 0;
3265         size_t i;
3267         for (i = 0; i < id_refs_size; i++)
3268                 if (!strcmp(id, id_refs[i][0]->id))
3269                         return id_refs[i];
3271         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
3272         if (!tmp_id_refs)
3273                 return NULL;
3275         id_refs = tmp_id_refs;
3277         for (i = 0; i < refs_size; i++) {
3278                 struct ref **tmp;
3280                 if (strcmp(id, refs[i].id))
3281                         continue;
3283                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
3284                 if (!tmp) {
3285                         if (ref_list)
3286                                 free(ref_list);
3287                         return NULL;
3288                 }
3290                 ref_list = tmp;
3291                 if (ref_list_size > 0)
3292                         ref_list[ref_list_size - 1]->next = 1;
3293                 ref_list[ref_list_size] = &refs[i];
3295                 /* XXX: The properties of the commit chains ensures that we can
3296                  * safely modify the shared ref. The repo references will
3297                  * always be similar for the same id. */
3298                 ref_list[ref_list_size]->next = 0;
3299                 ref_list_size++;
3300         }
3302         if (ref_list)
3303                 id_refs[id_refs_size++] = ref_list;
3305         return ref_list;
3308 static int
3309 read_ref(char *id, int idlen, char *name, int namelen)
3311         struct ref *ref;
3312         bool tag = FALSE;
3314         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
3315                 /* Commits referenced by tags has "^{}" appended. */
3316                 if (name[namelen - 1] != '}')
3317                         return OK;
3319                 while (namelen > 0 && name[namelen] != '^')
3320                         namelen--;
3322                 tag = TRUE;
3323                 namelen -= STRING_SIZE("refs/tags/");
3324                 name    += STRING_SIZE("refs/tags/");
3326         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
3327                 namelen -= STRING_SIZE("refs/heads/");
3328                 name    += STRING_SIZE("refs/heads/");
3330         } else if (!strcmp(name, "HEAD")) {
3331                 return OK;
3332         }
3334         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
3335         if (!refs)
3336                 return ERR;
3338         ref = &refs[refs_size++];
3339         ref->name = malloc(namelen + 1);
3340         if (!ref->name)
3341                 return ERR;
3343         strncpy(ref->name, name, namelen);
3344         ref->name[namelen] = 0;
3345         ref->tag = tag;
3346         string_copy(ref->id, id);
3348         return OK;
3351 static int
3352 load_refs(void)
3354         const char *cmd_env = getenv("TIG_LS_REMOTE");
3355         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
3357         return read_properties(popen(cmd, "r"), "\t", read_ref);
3360 static int
3361 read_repo_config_option(char *name, int namelen, char *value, int valuelen)
3363         if (!strcmp(name, "i18n.commitencoding"))
3364                 string_copy(opt_encoding, value);
3366         return OK;
3369 static int
3370 load_repo_config(void)
3372         return read_properties(popen("git repo-config --list", "r"),
3373                                "=", read_repo_config_option);
3376 static int
3377 read_properties(FILE *pipe, const char *separators,
3378                 int (*read_property)(char *, int, char *, int))
3380         char buffer[BUFSIZ];
3381         char *name;
3382         int state = OK;
3384         if (!pipe)
3385                 return ERR;
3387         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
3388                 char *value;
3389                 size_t namelen;
3390                 size_t valuelen;
3392                 name = chomp_string(name);
3393                 namelen = strcspn(name, separators);
3395                 if (name[namelen]) {
3396                         name[namelen] = 0;
3397                         value = chomp_string(name + namelen + 1);
3398                         valuelen = strlen(value);
3400                 } else {
3401                         value = "";
3402                         valuelen = 0;
3403                 }
3405                 state = read_property(name, namelen, value, valuelen);
3406         }
3408         if (state != ERR && ferror(pipe))
3409                 state = ERR;
3411         pclose(pipe);
3413         return state;
3417 /*
3418  * Main
3419  */
3421 static void __NORETURN
3422 quit(int sig)
3424         /* XXX: Restore tty modes and let the OS cleanup the rest! */
3425         if (cursed)
3426                 endwin();
3427         exit(0);
3430 static void __NORETURN
3431 die(const char *err, ...)
3433         va_list args;
3435         endwin();
3437         va_start(args, err);
3438         fputs("tig: ", stderr);
3439         vfprintf(stderr, err, args);
3440         fputs("\n", stderr);
3441         va_end(args);
3443         exit(1);
3446 int
3447 main(int argc, char *argv[])
3449         struct view *view;
3450         enum request request;
3451         size_t i;
3453         signal(SIGINT, quit);
3455         if (setlocale(LC_ALL, "")) {
3456                 string_copy(opt_codeset, nl_langinfo(CODESET));
3457         }
3459         if (load_options() == ERR)
3460                 die("Failed to load user config.");
3462         /* Load the repo config file so options can be overwritten from
3463          * the command line.  */
3464         if (load_repo_config() == ERR)
3465                 die("Failed to load repo config.");
3467         if (!parse_options(argc, argv))
3468                 return 0;
3470         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
3471                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
3472                 if (opt_iconv == ICONV_NONE)
3473                         die("Failed to initialize character set conversion");
3474         }
3476         if (load_refs() == ERR)
3477                 die("Failed to load refs.");
3479         /* Require a git repository unless when running in pager mode. */
3480         if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
3481                 die("Not a git repository");
3483         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
3484                 view->cmd_env = getenv(view->cmd_env);
3486         request = opt_request;
3488         init_display();
3490         while (view_driver(display[current_view], request)) {
3491                 int key;
3492                 int i;
3494                 foreach_view (view, i)
3495                         update_view(view);
3497                 /* Refresh, accept single keystroke of input */
3498                 key = wgetch(status_win);
3500                 request = get_keybinding(display[current_view]->keymap, key);
3502                 /* Some low-level request handling. This keeps access to
3503                  * status_win restricted. */
3504                 switch (request) {
3505                 case REQ_PROMPT:
3506                 {
3507                         char *cmd = read_prompt(":");
3509                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
3510                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
3511                                         opt_request = REQ_VIEW_DIFF;
3512                                 } else {
3513                                         opt_request = REQ_VIEW_PAGER;
3514                                 }
3515                                 break;
3516                         }
3518                         request = REQ_NONE;
3519                         break;
3520                 }
3521                 case REQ_SEARCH:
3522                 case REQ_SEARCH_BACK:
3523                 {
3524                         const char *prompt = request == REQ_SEARCH
3525                                            ? "/" : "?";
3526                         char *search = read_prompt(prompt);
3528                         if (search)
3529                                 string_copy(opt_search, search);
3530                         else
3531                                 request = REQ_NONE;
3532                         break;
3533                 }
3534                 case REQ_SCREEN_RESIZE:
3535                 {
3536                         int height, width;
3538                         getmaxyx(stdscr, height, width);
3540                         /* Resize the status view and let the view driver take
3541                          * care of resizing the displayed views. */
3542                         wresize(status_win, 1, width);
3543                         mvwin(status_win, height - 1, 0);
3544                         wrefresh(status_win);
3545                         break;
3546                 }
3547                 default:
3548                         break;
3549                 }
3550         }
3552         quit(0);
3554         return 0;