Code

draw_text: reduce indentation level
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 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 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 1
101 #define TABSIZE         8
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
105 #ifndef GIT_CONFIG
106 #define GIT_CONFIG "git config"
107 #endif
109 #define TIG_LS_REMOTE \
110         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
112 #define TIG_DIFF_CMD \
113         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
115 #define TIG_LOG_CMD     \
116         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
118 #define TIG_MAIN_CMD \
119         "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
121 #define TIG_TREE_CMD    \
122         "git ls-tree %s %s"
124 #define TIG_BLOB_CMD    \
125         "git cat-file blob %s"
127 /* XXX: Needs to be defined to the empty string. */
128 #define TIG_HELP_CMD    ""
129 #define TIG_PAGER_CMD   ""
130 #define TIG_STATUS_CMD  ""
131 #define TIG_STAGE_CMD   ""
133 /* Some ascii-shorthands fitted into the ncurses namespace. */
134 #define KEY_TAB         '\t'
135 #define KEY_RETURN      '\r'
136 #define KEY_ESC         27
139 struct ref {
140         char *name;             /* Ref name; tag or head names are shortened. */
141         char id[SIZEOF_REV];    /* Commit SHA1 ID */
142         unsigned int tag:1;     /* Is it a tag? */
143         unsigned int remote:1;  /* Is it a remote ref? */
144         unsigned int next:1;    /* For ref lists: are there more refs? */
145 };
147 static struct ref **get_refs(char *id);
149 struct int_map {
150         const char *name;
151         int namelen;
152         int value;
153 };
155 static int
156 set_from_int_map(struct int_map *map, size_t map_size,
157                  int *value, const char *name, int namelen)
160         int i;
162         for (i = 0; i < map_size; i++)
163                 if (namelen == map[i].namelen &&
164                     !strncasecmp(name, map[i].name, namelen)) {
165                         *value = map[i].value;
166                         return OK;
167                 }
169         return ERR;
173 /*
174  * String helpers
175  */
177 static inline void
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
180         if (srclen > dstlen - 1)
181                 srclen = dstlen - 1;
183         strncpy(dst, src, srclen);
184         dst[srclen] = 0;
187 /* Shorthands for safely copying into a fixed buffer. */
189 #define string_copy(dst, src) \
190         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
192 #define string_ncopy(dst, src, srclen) \
193         string_ncopy_do(dst, sizeof(dst), src, srclen)
195 #define string_copy_rev(dst, src) \
196         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
198 #define string_add(dst, from, src) \
199         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
201 static char *
202 chomp_string(char *name)
204         int namelen;
206         while (isspace(*name))
207                 name++;
209         namelen = strlen(name) - 1;
210         while (namelen > 0 && isspace(name[namelen]))
211                 name[namelen--] = 0;
213         return name;
216 static bool
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
219         va_list args;
220         size_t pos = bufpos ? *bufpos : 0;
222         va_start(args, fmt);
223         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224         va_end(args);
226         if (bufpos)
227                 *bufpos = pos;
229         return pos >= bufsize ? FALSE : TRUE;
232 #define string_format(buf, fmt, args...) \
233         string_nformat(buf, sizeof(buf), NULL, fmt, args)
235 #define string_format_from(buf, from, fmt, args...) \
236         string_nformat(buf, sizeof(buf), from, fmt, args)
238 static int
239 string_enum_compare(const char *str1, const char *str2, int len)
241         size_t i;
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
245         /* Diff-Header == DIFF_HEADER */
246         for (i = 0; i < len; i++) {
247                 if (toupper(str1[i]) == toupper(str2[i]))
248                         continue;
250                 if (string_enum_sep(str1[i]) &&
251                     string_enum_sep(str2[i]))
252                         continue;
254                 return str1[i] - str2[i];
255         }
257         return 0;
260 /* Shell quoting
261  *
262  * NOTE: The following is a slightly modified copy of the git project's shell
263  * quoting routines found in the quote.c file.
264  *
265  * Help to copy the thing properly quoted for the shell safety.  any single
266  * quote is replaced with '\'', any exclamation point is replaced with '\!',
267  * and the whole thing is enclosed in a
268  *
269  * E.g.
270  *  original     sq_quote     result
271  *  name     ==> name      ==> 'name'
272  *  a b      ==> a b       ==> 'a b'
273  *  a'b      ==> a'\''b    ==> 'a'\''b'
274  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
275  */
277 static size_t
278 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
280         char c;
282 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
284         BUFPUT('\'');
285         while ((c = *src++)) {
286                 if (c == '\'' || c == '!') {
287                         BUFPUT('\'');
288                         BUFPUT('\\');
289                         BUFPUT(c);
290                         BUFPUT('\'');
291                 } else {
292                         BUFPUT(c);
293                 }
294         }
295         BUFPUT('\'');
297         if (bufsize < SIZEOF_STR)
298                 buf[bufsize] = 0;
300         return bufsize;
304 /*
305  * User requests
306  */
308 #define REQ_INFO \
309         /* XXX: Keep the view request first and in sync with views[]. */ \
310         REQ_GROUP("View switching") \
311         REQ_(VIEW_MAIN,         "Show main view"), \
312         REQ_(VIEW_DIFF,         "Show diff view"), \
313         REQ_(VIEW_LOG,          "Show log view"), \
314         REQ_(VIEW_TREE,         "Show tree view"), \
315         REQ_(VIEW_BLOB,         "Show blob view"), \
316         REQ_(VIEW_HELP,         "Show help page"), \
317         REQ_(VIEW_PAGER,        "Show pager view"), \
318         REQ_(VIEW_STATUS,       "Show status view"), \
319         REQ_(VIEW_STAGE,        "Show stage view"), \
320         \
321         REQ_GROUP("View manipulation") \
322         REQ_(ENTER,             "Enter current line and scroll"), \
323         REQ_(NEXT,              "Move to next"), \
324         REQ_(PREVIOUS,          "Move to previous"), \
325         REQ_(VIEW_NEXT,         "Move focus to next view"), \
326         REQ_(REFRESH,           "Reload and refresh"), \
327         REQ_(VIEW_CLOSE,        "Close the current view"), \
328         REQ_(QUIT,              "Close all views and quit"), \
329         \
330         REQ_GROUP("Cursor navigation") \
331         REQ_(MOVE_UP,           "Move cursor one line up"), \
332         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
333         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
334         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
335         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
336         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
337         \
338         REQ_GROUP("Scrolling") \
339         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
340         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
341         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
342         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
343         \
344         REQ_GROUP("Searching") \
345         REQ_(SEARCH,            "Search the view"), \
346         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
347         REQ_(FIND_NEXT,         "Find next search match"), \
348         REQ_(FIND_PREV,         "Find previous search match"), \
349         \
350         REQ_GROUP("Misc") \
351         REQ_(PROMPT,            "Bring up the prompt"), \
352         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
353         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
354         REQ_(SHOW_VERSION,      "Show version information"), \
355         REQ_(STOP_LOADING,      "Stop all loading views"), \
356         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
357         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
358         REQ_(STATUS_UPDATE,     "Update file status"), \
359         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
360         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
361         REQ_(EDIT,              "Open in editor"), \
362         REQ_(NONE,              "Do nothing")
365 /* User action requests. */
366 enum request {
367 #define REQ_GROUP(help)
368 #define REQ_(req, help) REQ_##req
370         /* Offset all requests to avoid conflicts with ncurses getch values. */
371         REQ_OFFSET = KEY_MAX + 1,
372         REQ_INFO
374 #undef  REQ_GROUP
375 #undef  REQ_
376 };
378 struct request_info {
379         enum request request;
380         char *name;
381         int namelen;
382         char *help;
383 };
385 static struct request_info req_info[] = {
386 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
387 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
388         REQ_INFO
389 #undef  REQ_GROUP
390 #undef  REQ_
391 };
393 static enum request
394 get_request(const char *name)
396         int namelen = strlen(name);
397         int i;
399         for (i = 0; i < ARRAY_SIZE(req_info); i++)
400                 if (req_info[i].namelen == namelen &&
401                     !string_enum_compare(req_info[i].name, name, namelen))
402                         return req_info[i].request;
404         return REQ_NONE;
408 /*
409  * Options
410  */
412 static const char usage[] =
413 "tig " TIG_VERSION " (" __DATE__ ")\n"
414 "\n"
415 "Usage: tig        [options] [revs] [--] [paths]\n"
416 "   or: tig show   [options] [revs] [--] [paths]\n"
417 "   or: tig status\n"
418 "   or: tig <      [git command output]\n"
419 "\n"
420 "Options:\n"
421 "  -v, --version   Show version and exit\n"
422 "  -h, --help      Show help message and exit\n";
424 /* Option and state variables. */
425 static bool opt_line_number             = FALSE;
426 static bool opt_rev_graph               = FALSE;
427 static int opt_num_interval             = NUMBER_INTERVAL;
428 static int opt_tab_size                 = TABSIZE;
429 static enum request opt_request         = REQ_VIEW_MAIN;
430 static char opt_cmd[SIZEOF_STR]         = "";
431 static char opt_path[SIZEOF_STR]        = "";
432 static FILE *opt_pipe                   = NULL;
433 static char opt_encoding[20]            = "UTF-8";
434 static bool opt_utf8                    = TRUE;
435 static char opt_codeset[20]             = "UTF-8";
436 static iconv_t opt_iconv                = ICONV_NONE;
437 static char opt_search[SIZEOF_STR]      = "";
438 static char opt_cdup[SIZEOF_STR]        = "";
439 static char opt_git_dir[SIZEOF_STR]     = "";
440 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
441 static char opt_editor[SIZEOF_STR]      = "";
443 enum option_type {
444         OPT_NONE,
445         OPT_INT,
446 };
448 static bool
449 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
451         va_list args;
452         char *value = "";
453         int *number;
455         if (opt[0] != '-')
456                 return FALSE;
458         if (opt[1] == '-') {
459                 int namelen = strlen(name);
461                 opt += 2;
463                 if (strncmp(opt, name, namelen))
464                         return FALSE;
466                 if (opt[namelen] == '=')
467                         value = opt + namelen + 1;
469         } else {
470                 if (!short_name || opt[1] != short_name)
471                         return FALSE;
472                 value = opt + 2;
473         }
475         va_start(args, type);
476         if (type == OPT_INT) {
477                 number = va_arg(args, int *);
478                 if (isdigit(*value))
479                         *number = atoi(value);
480         }
481         va_end(args);
483         return TRUE;
486 /* Returns the index of log or diff command or -1 to exit. */
487 static bool
488 parse_options(int argc, char *argv[])
490         char *altargv[1024];
491         int altargc = 0;
492         char *subcommand = NULL;
493         int i;
495         for (i = 1; i < argc; i++) {
496                 char *opt = argv[i];
498                 if (!strcmp(opt, "log") ||
499                     !strcmp(opt, "diff")) {
500                         subcommand = opt;
501                         opt_request = opt[0] == 'l'
502                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503                         warn("`tig %s' has been deprecated", opt);
504                         break;
505                 }
507                 if (!strcmp(opt, "show")) {
508                         subcommand = opt;
509                         opt_request = REQ_VIEW_DIFF;
510                         break;
511                 }
513                 if (!strcmp(opt, "status")) {
514                         subcommand = opt;
515                         opt_request = REQ_VIEW_STATUS;
516                         break;
517                 }
519                 if (opt[0] && opt[0] != '-')
520                         break;
522                 if (!strcmp(opt, "--")) {
523                         i++;
524                         break;
525                 }
527                 if (check_option(opt, 'v', "version", OPT_NONE)) {
528                         printf("tig version %s\n", TIG_VERSION);
529                         return FALSE;
530                 }
532                 if (check_option(opt, 'h', "help", OPT_NONE)) {
533                         printf(usage);
534                         return FALSE;
535                 }
537                 if (!strcmp(opt, "-S")) {
538                         warn("`%s' has been deprecated; use `tig status' instead", opt);
539                         opt_request = REQ_VIEW_STATUS;
540                         continue;
541                 }
543                 if (!strcmp(opt, "-l")) {
544                         opt_request = REQ_VIEW_LOG;
545                 } else if (!strcmp(opt, "-d")) {
546                         opt_request = REQ_VIEW_DIFF;
547                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
548                         opt_line_number = TRUE;
549                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
550                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
551                 } else {
552                         if (altargc >= ARRAY_SIZE(altargv))
553                                 die("maximum number of arguments exceeded");
554                         altargv[altargc++] = opt;
555                         continue;
556                 }
558                 warn("`%s' has been deprecated", opt);
559         }
561         /* Check that no 'alt' arguments occured before a subcommand. */
562         if (subcommand && i < argc && altargc > 0)
563                 die("unknown arguments before `%s'", argv[i]);
565         if (!isatty(STDIN_FILENO)) {
566                 opt_request = REQ_VIEW_PAGER;
567                 opt_pipe = stdin;
569         } else if (opt_request == REQ_VIEW_STATUS) {
570                 if (argc - i > 1)
571                         warn("ignoring arguments after `%s'", argv[i]);
573         } else if (i < argc || altargc > 0) {
574                 int alti = 0;
575                 size_t buf_size;
577                 if (opt_request == REQ_VIEW_MAIN)
578                         /* XXX: This is vulnerable to the user overriding
579                          * options required for the main view parser. */
580                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
581                 else
582                         string_copy(opt_cmd, "git");
583                 buf_size = strlen(opt_cmd);
585                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
586                         opt_cmd[buf_size++] = ' ';
587                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
588                 }
590                 while (buf_size < sizeof(opt_cmd) && i < argc) {
591                         opt_cmd[buf_size++] = ' ';
592                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
593                 }
595                 if (buf_size >= sizeof(opt_cmd))
596                         die("command too long");
598                 opt_cmd[buf_size] = 0;
599         }
601         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
602                 opt_utf8 = FALSE;
604         return TRUE;
608 /*
609  * Line-oriented content detection.
610  */
612 #define LINE_INFO \
613 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
614 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
616 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
617 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
618 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
619 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
620 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
621 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
622 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
623 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
624 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
625 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
626 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
627 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
628 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
629 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
630 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
631 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
632 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
633 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
634 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
635 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
636 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
637 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
638 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
639 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
640 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
641 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
642 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
643 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
644 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
645 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
646 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
647 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
648 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
649 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
650 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
651 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
652 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
653 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
654 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
655 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
656 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
657 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
658 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
659 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
660 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
662 enum line_type {
663 #define LINE(type, line, fg, bg, attr) \
664         LINE_##type
665         LINE_INFO
666 #undef  LINE
667 };
669 struct line_info {
670         const char *name;       /* Option name. */
671         int namelen;            /* Size of option name. */
672         const char *line;       /* The start of line to match. */
673         int linelen;            /* Size of string to match. */
674         int fg, bg, attr;       /* Color and text attributes for the lines. */
675 };
677 static struct line_info line_info[] = {
678 #define LINE(type, line, fg, bg, attr) \
679         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
680         LINE_INFO
681 #undef  LINE
682 };
684 static enum line_type
685 get_line_type(char *line)
687         int linelen = strlen(line);
688         enum line_type type;
690         for (type = 0; type < ARRAY_SIZE(line_info); type++)
691                 /* Case insensitive search matches Signed-off-by lines better. */
692                 if (linelen >= line_info[type].linelen &&
693                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
694                         return type;
696         return LINE_DEFAULT;
699 static inline int
700 get_line_attr(enum line_type type)
702         assert(type < ARRAY_SIZE(line_info));
703         return COLOR_PAIR(type) | line_info[type].attr;
706 static struct line_info *
707 get_line_info(char *name, int namelen)
709         enum line_type type;
711         for (type = 0; type < ARRAY_SIZE(line_info); type++)
712                 if (namelen == line_info[type].namelen &&
713                     !string_enum_compare(line_info[type].name, name, namelen))
714                         return &line_info[type];
716         return NULL;
719 static void
720 init_colors(void)
722         int default_bg = line_info[LINE_DEFAULT].bg;
723         int default_fg = line_info[LINE_DEFAULT].fg;
724         enum line_type type;
726         start_color();
728         if (assume_default_colors(default_fg, default_bg) == ERR) {
729                 default_bg = COLOR_BLACK;
730                 default_fg = COLOR_WHITE;
731         }
733         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
734                 struct line_info *info = &line_info[type];
735                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
736                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
738                 init_pair(type, fg, bg);
739         }
742 struct line {
743         enum line_type type;
745         /* State flags */
746         unsigned int selected:1;
748         void *data;             /* User data */
749 };
752 /*
753  * Keys
754  */
756 struct keybinding {
757         int alias;
758         enum request request;
759         struct keybinding *next;
760 };
762 static struct keybinding default_keybindings[] = {
763         /* View switching */
764         { 'm',          REQ_VIEW_MAIN },
765         { 'd',          REQ_VIEW_DIFF },
766         { 'l',          REQ_VIEW_LOG },
767         { 't',          REQ_VIEW_TREE },
768         { 'f',          REQ_VIEW_BLOB },
769         { 'p',          REQ_VIEW_PAGER },
770         { 'h',          REQ_VIEW_HELP },
771         { 'S',          REQ_VIEW_STATUS },
772         { 'c',          REQ_VIEW_STAGE },
774         /* View manipulation */
775         { 'q',          REQ_VIEW_CLOSE },
776         { KEY_TAB,      REQ_VIEW_NEXT },
777         { KEY_RETURN,   REQ_ENTER },
778         { KEY_UP,       REQ_PREVIOUS },
779         { KEY_DOWN,     REQ_NEXT },
780         { 'R',          REQ_REFRESH },
782         /* Cursor navigation */
783         { 'k',          REQ_MOVE_UP },
784         { 'j',          REQ_MOVE_DOWN },
785         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
786         { KEY_END,      REQ_MOVE_LAST_LINE },
787         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
788         { ' ',          REQ_MOVE_PAGE_DOWN },
789         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
790         { 'b',          REQ_MOVE_PAGE_UP },
791         { '-',          REQ_MOVE_PAGE_UP },
793         /* Scrolling */
794         { KEY_IC,       REQ_SCROLL_LINE_UP },
795         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
796         { 'w',          REQ_SCROLL_PAGE_UP },
797         { 's',          REQ_SCROLL_PAGE_DOWN },
799         /* Searching */
800         { '/',          REQ_SEARCH },
801         { '?',          REQ_SEARCH_BACK },
802         { 'n',          REQ_FIND_NEXT },
803         { 'N',          REQ_FIND_PREV },
805         /* Misc */
806         { 'Q',          REQ_QUIT },
807         { 'z',          REQ_STOP_LOADING },
808         { 'v',          REQ_SHOW_VERSION },
809         { 'r',          REQ_SCREEN_REDRAW },
810         { '.',          REQ_TOGGLE_LINENO },
811         { 'g',          REQ_TOGGLE_REV_GRAPH },
812         { ':',          REQ_PROMPT },
813         { 'u',          REQ_STATUS_UPDATE },
814         { 'M',          REQ_STATUS_MERGE },
815         { ',',          REQ_TREE_PARENT },
816         { 'e',          REQ_EDIT },
818         /* Using the ncurses SIGWINCH handler. */
819         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
820 };
822 #define KEYMAP_INFO \
823         KEYMAP_(GENERIC), \
824         KEYMAP_(MAIN), \
825         KEYMAP_(DIFF), \
826         KEYMAP_(LOG), \
827         KEYMAP_(TREE), \
828         KEYMAP_(BLOB), \
829         KEYMAP_(PAGER), \
830         KEYMAP_(HELP), \
831         KEYMAP_(STATUS), \
832         KEYMAP_(STAGE)
834 enum keymap {
835 #define KEYMAP_(name) KEYMAP_##name
836         KEYMAP_INFO
837 #undef  KEYMAP_
838 };
840 static struct int_map keymap_table[] = {
841 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
842         KEYMAP_INFO
843 #undef  KEYMAP_
844 };
846 #define set_keymap(map, name) \
847         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
849 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
851 static void
852 add_keybinding(enum keymap keymap, enum request request, int key)
854         struct keybinding *keybinding;
856         keybinding = calloc(1, sizeof(*keybinding));
857         if (!keybinding)
858                 die("Failed to allocate keybinding");
860         keybinding->alias = key;
861         keybinding->request = request;
862         keybinding->next = keybindings[keymap];
863         keybindings[keymap] = keybinding;
866 /* Looks for a key binding first in the given map, then in the generic map, and
867  * lastly in the default keybindings. */
868 static enum request
869 get_keybinding(enum keymap keymap, int key)
871         struct keybinding *kbd;
872         int i;
874         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
875                 if (kbd->alias == key)
876                         return kbd->request;
878         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
879                 if (kbd->alias == key)
880                         return kbd->request;
882         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
883                 if (default_keybindings[i].alias == key)
884                         return default_keybindings[i].request;
886         return (enum request) key;
890 struct key {
891         char *name;
892         int value;
893 };
895 static struct key key_table[] = {
896         { "Enter",      KEY_RETURN },
897         { "Space",      ' ' },
898         { "Backspace",  KEY_BACKSPACE },
899         { "Tab",        KEY_TAB },
900         { "Escape",     KEY_ESC },
901         { "Left",       KEY_LEFT },
902         { "Right",      KEY_RIGHT },
903         { "Up",         KEY_UP },
904         { "Down",       KEY_DOWN },
905         { "Insert",     KEY_IC },
906         { "Delete",     KEY_DC },
907         { "Hash",       '#' },
908         { "Home",       KEY_HOME },
909         { "End",        KEY_END },
910         { "PageUp",     KEY_PPAGE },
911         { "PageDown",   KEY_NPAGE },
912         { "F1",         KEY_F(1) },
913         { "F2",         KEY_F(2) },
914         { "F3",         KEY_F(3) },
915         { "F4",         KEY_F(4) },
916         { "F5",         KEY_F(5) },
917         { "F6",         KEY_F(6) },
918         { "F7",         KEY_F(7) },
919         { "F8",         KEY_F(8) },
920         { "F9",         KEY_F(9) },
921         { "F10",        KEY_F(10) },
922         { "F11",        KEY_F(11) },
923         { "F12",        KEY_F(12) },
924 };
926 static int
927 get_key_value(const char *name)
929         int i;
931         for (i = 0; i < ARRAY_SIZE(key_table); i++)
932                 if (!strcasecmp(key_table[i].name, name))
933                         return key_table[i].value;
935         if (strlen(name) == 1 && isprint(*name))
936                 return (int) *name;
938         return ERR;
941 static char *
942 get_key_name(int key_value)
944         static char key_char[] = "'X'";
945         char *seq = NULL;
946         int key;
948         for (key = 0; key < ARRAY_SIZE(key_table); key++)
949                 if (key_table[key].value == key_value)
950                         seq = key_table[key].name;
952         if (seq == NULL &&
953             key_value < 127 &&
954             isprint(key_value)) {
955                 key_char[1] = (char) key_value;
956                 seq = key_char;
957         }
959         return seq ? seq : "'?'";
962 static char *
963 get_key(enum request request)
965         static char buf[BUFSIZ];
966         size_t pos = 0;
967         char *sep = "";
968         int i;
970         buf[pos] = 0;
972         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
973                 struct keybinding *keybinding = &default_keybindings[i];
975                 if (keybinding->request != request)
976                         continue;
978                 if (!string_format_from(buf, &pos, "%s%s", sep,
979                                         get_key_name(keybinding->alias)))
980                         return "Too many keybindings!";
981                 sep = ", ";
982         }
984         return buf;
987 struct run_request {
988         enum keymap keymap;
989         int key;
990         char cmd[SIZEOF_STR];
991 };
993 static struct run_request *run_request;
994 static size_t run_requests;
996 static enum request
997 add_run_request(enum keymap keymap, int key, int argc, char **argv)
999         struct run_request *tmp;
1000         struct run_request req = { keymap, key };
1001         size_t bufpos;
1003         for (bufpos = 0; argc > 0; argc--, argv++)
1004                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1005                         return REQ_NONE;
1007         req.cmd[bufpos - 1] = 0;
1009         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1010         if (!tmp)
1011                 return REQ_NONE;
1013         run_request = tmp;
1014         run_request[run_requests++] = req;
1016         return REQ_NONE + run_requests;
1019 static struct run_request *
1020 get_run_request(enum request request)
1022         if (request <= REQ_NONE)
1023                 return NULL;
1024         return &run_request[request - REQ_NONE - 1];
1027 static void
1028 add_builtin_run_requests(void)
1030         struct {
1031                 enum keymap keymap;
1032                 int key;
1033                 char *argv[1];
1034         } reqs[] = {
1035                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1036                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1037         };
1038         int i;
1040         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1041                 enum request req;
1043                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1044                 if (req != REQ_NONE)
1045                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1046         }
1049 /*
1050  * User config file handling.
1051  */
1053 static struct int_map color_map[] = {
1054 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1055         COLOR_MAP(DEFAULT),
1056         COLOR_MAP(BLACK),
1057         COLOR_MAP(BLUE),
1058         COLOR_MAP(CYAN),
1059         COLOR_MAP(GREEN),
1060         COLOR_MAP(MAGENTA),
1061         COLOR_MAP(RED),
1062         COLOR_MAP(WHITE),
1063         COLOR_MAP(YELLOW),
1064 };
1066 #define set_color(color, name) \
1067         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1069 static struct int_map attr_map[] = {
1070 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1071         ATTR_MAP(NORMAL),
1072         ATTR_MAP(BLINK),
1073         ATTR_MAP(BOLD),
1074         ATTR_MAP(DIM),
1075         ATTR_MAP(REVERSE),
1076         ATTR_MAP(STANDOUT),
1077         ATTR_MAP(UNDERLINE),
1078 };
1080 #define set_attribute(attr, name) \
1081         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1083 static int   config_lineno;
1084 static bool  config_errors;
1085 static char *config_msg;
1087 /* Wants: object fgcolor bgcolor [attr] */
1088 static int
1089 option_color_command(int argc, char *argv[])
1091         struct line_info *info;
1093         if (argc != 3 && argc != 4) {
1094                 config_msg = "Wrong number of arguments given to color command";
1095                 return ERR;
1096         }
1098         info = get_line_info(argv[0], strlen(argv[0]));
1099         if (!info) {
1100                 config_msg = "Unknown color name";
1101                 return ERR;
1102         }
1104         if (set_color(&info->fg, argv[1]) == ERR ||
1105             set_color(&info->bg, argv[2]) == ERR) {
1106                 config_msg = "Unknown color";
1107                 return ERR;
1108         }
1110         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1111                 config_msg = "Unknown attribute";
1112                 return ERR;
1113         }
1115         return OK;
1118 /* Wants: name = value */
1119 static int
1120 option_set_command(int argc, char *argv[])
1122         if (argc != 3) {
1123                 config_msg = "Wrong number of arguments given to set command";
1124                 return ERR;
1125         }
1127         if (strcmp(argv[1], "=")) {
1128                 config_msg = "No value assigned";
1129                 return ERR;
1130         }
1132         if (!strcmp(argv[0], "show-rev-graph")) {
1133                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1134                                  !strcmp(argv[2], "true") ||
1135                                  !strcmp(argv[2], "yes"));
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "line-number-interval")) {
1140                 opt_num_interval = atoi(argv[2]);
1141                 return OK;
1142         }
1144         if (!strcmp(argv[0], "tab-size")) {
1145                 opt_tab_size = atoi(argv[2]);
1146                 return OK;
1147         }
1149         if (!strcmp(argv[0], "commit-encoding")) {
1150                 char *arg = argv[2];
1151                 int delimiter = *arg;
1152                 int i;
1154                 switch (delimiter) {
1155                 case '"':
1156                 case '\'':
1157                         for (arg++, i = 0; arg[i]; i++)
1158                                 if (arg[i] == delimiter) {
1159                                         arg[i] = 0;
1160                                         break;
1161                                 }
1162                 default:
1163                         string_ncopy(opt_encoding, arg, strlen(arg));
1164                         return OK;
1165                 }
1166         }
1168         config_msg = "Unknown variable name";
1169         return ERR;
1172 /* Wants: mode request key */
1173 static int
1174 option_bind_command(int argc, char *argv[])
1176         enum request request;
1177         int keymap;
1178         int key;
1180         if (argc < 3) {
1181                 config_msg = "Wrong number of arguments given to bind command";
1182                 return ERR;
1183         }
1185         if (set_keymap(&keymap, argv[0]) == ERR) {
1186                 config_msg = "Unknown key map";
1187                 return ERR;
1188         }
1190         key = get_key_value(argv[1]);
1191         if (key == ERR) {
1192                 config_msg = "Unknown key";
1193                 return ERR;
1194         }
1196         request = get_request(argv[2]);
1197         if (request == REQ_NONE) {
1198                 const char *obsolete[] = { "cherry-pick" };
1199                 size_t namelen = strlen(argv[2]);
1200                 int i;
1202                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1203                         if (namelen == strlen(obsolete[i]) &&
1204                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1205                                 config_msg = "Obsolete request name";
1206                                 return ERR;
1207                         }
1208                 }
1209         }
1210         if (request == REQ_NONE && *argv[2]++ == '!')
1211                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1212         if (request == REQ_NONE) {
1213                 config_msg = "Unknown request name";
1214                 return ERR;
1215         }
1217         add_keybinding(keymap, request, key);
1219         return OK;
1222 static int
1223 set_option(char *opt, char *value)
1225         char *argv[16];
1226         int valuelen;
1227         int argc = 0;
1229         /* Tokenize */
1230         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1231                 argv[argc++] = value;
1232                 value += valuelen;
1234                 /* Nothing more to tokenize or last available token. */
1235                 if (!*value || argc >= ARRAY_SIZE(argv))
1236                         break;
1238                 *value++ = 0;
1239                 while (isspace(*value))
1240                         value++;
1241         }
1243         if (!strcmp(opt, "color"))
1244                 return option_color_command(argc, argv);
1246         if (!strcmp(opt, "set"))
1247                 return option_set_command(argc, argv);
1249         if (!strcmp(opt, "bind"))
1250                 return option_bind_command(argc, argv);
1252         config_msg = "Unknown option command";
1253         return ERR;
1256 static int
1257 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1259         int status = OK;
1261         config_lineno++;
1262         config_msg = "Internal error";
1264         /* Check for comment markers, since read_properties() will
1265          * only ensure opt and value are split at first " \t". */
1266         optlen = strcspn(opt, "#");
1267         if (optlen == 0)
1268                 return OK;
1270         if (opt[optlen] != 0) {
1271                 config_msg = "No option value";
1272                 status = ERR;
1274         }  else {
1275                 /* Look for comment endings in the value. */
1276                 size_t len = strcspn(value, "#");
1278                 if (len < valuelen) {
1279                         valuelen = len;
1280                         value[valuelen] = 0;
1281                 }
1283                 status = set_option(opt, value);
1284         }
1286         if (status == ERR) {
1287                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1288                         config_lineno, (int) optlen, opt, config_msg);
1289                 config_errors = TRUE;
1290         }
1292         /* Always keep going if errors are encountered. */
1293         return OK;
1296 static void
1297 load_option_file(const char *path)
1299         FILE *file;
1301         /* It's ok that the file doesn't exist. */
1302         file = fopen(path, "r");
1303         if (!file)
1304                 return;
1306         config_lineno = 0;
1307         config_errors = FALSE;
1309         if (read_properties(file, " \t", read_option) == ERR ||
1310             config_errors == TRUE)
1311                 fprintf(stderr, "Errors while loading %s.\n", path);
1314 static int
1315 load_options(void)
1317         char *home = getenv("HOME");
1318         char *tigrc_user = getenv("TIGRC_USER");
1319         char *tigrc_system = getenv("TIGRC_SYSTEM");
1320         char buf[SIZEOF_STR];
1322         add_builtin_run_requests();
1324         if (!tigrc_system) {
1325                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1326                         return ERR;
1327                 tigrc_system = buf;
1328         }
1329         load_option_file(tigrc_system);
1331         if (!tigrc_user) {
1332                 if (!home || !string_format(buf, "%s/.tigrc", home))
1333                         return ERR;
1334                 tigrc_user = buf;
1335         }
1336         load_option_file(tigrc_user);
1338         return OK;
1342 /*
1343  * The viewer
1344  */
1346 struct view;
1347 struct view_ops;
1349 /* The display array of active views and the index of the current view. */
1350 static struct view *display[2];
1351 static unsigned int current_view;
1353 /* Reading from the prompt? */
1354 static bool input_mode = FALSE;
1356 #define foreach_displayed_view(view, i) \
1357         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1359 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1361 /* Current head and commit ID */
1362 static char ref_blob[SIZEOF_REF]        = "";
1363 static char ref_commit[SIZEOF_REF]      = "HEAD";
1364 static char ref_head[SIZEOF_REF]        = "HEAD";
1366 struct view {
1367         const char *name;       /* View name */
1368         const char *cmd_fmt;    /* Default command line format */
1369         const char *cmd_env;    /* Command line set via environment */
1370         const char *id;         /* Points to either of ref_{head,commit,blob} */
1372         struct view_ops *ops;   /* View operations */
1374         enum keymap keymap;     /* What keymap does this view have */
1376         char cmd[SIZEOF_STR];   /* Command buffer */
1377         char ref[SIZEOF_REF];   /* Hovered commit reference */
1378         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1380         int height, width;      /* The width and height of the main window */
1381         WINDOW *win;            /* The main window */
1382         WINDOW *title;          /* The title window living below the main window */
1384         /* Navigation */
1385         unsigned long offset;   /* Offset of the window top */
1386         unsigned long lineno;   /* Current line number */
1388         /* Searching */
1389         char grep[SIZEOF_STR];  /* Search string */
1390         regex_t *regex;         /* Pre-compiled regex */
1392         /* If non-NULL, points to the view that opened this view. If this view
1393          * is closed tig will switch back to the parent view. */
1394         struct view *parent;
1396         /* Buffering */
1397         unsigned long lines;    /* Total number of lines */
1398         struct line *line;      /* Line index */
1399         unsigned long line_size;/* Total number of allocated lines */
1400         unsigned int digits;    /* Number of digits in the lines member. */
1402         /* Loading */
1403         FILE *pipe;
1404         time_t start_time;
1405 };
1407 struct view_ops {
1408         /* What type of content being displayed. Used in the title bar. */
1409         const char *type;
1410         /* Open and reads in all view content. */
1411         bool (*open)(struct view *view);
1412         /* Read one line; updates view->line. */
1413         bool (*read)(struct view *view, char *data);
1414         /* Draw one line; @lineno must be < view->height. */
1415         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1416         /* Depending on view handle a special requests. */
1417         enum request (*request)(struct view *view, enum request request, struct line *line);
1418         /* Search for regex in a line. */
1419         bool (*grep)(struct view *view, struct line *line);
1420         /* Select line */
1421         void (*select)(struct view *view, struct line *line);
1422 };
1424 static struct view_ops pager_ops;
1425 static struct view_ops main_ops;
1426 static struct view_ops tree_ops;
1427 static struct view_ops blob_ops;
1428 static struct view_ops help_ops;
1429 static struct view_ops status_ops;
1430 static struct view_ops stage_ops;
1432 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1433         { name, cmd, #env, ref, ops, map}
1435 #define VIEW_(id, name, ops, ref) \
1436         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1439 static struct view views[] = {
1440         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1441         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1442         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1443         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1444         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1445         VIEW_(HELP,   "help",   &help_ops,   ""),
1446         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1447         VIEW_(STATUS, "status", &status_ops, ""),
1448         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1449 };
1451 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1453 #define foreach_view(view, i) \
1454         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1456 #define view_is_displayed(view) \
1457         (view == display[0] || view == display[1])
1459 static int
1460 draw_text(struct view *view, const char *string, int max_len, int col,
1461           bool use_tilde, int tilde_attr)
1463         int len = 0;
1464         int trimmed = FALSE;
1466         if (max_len <= 0)
1467                 return 0;
1469         if (opt_utf8) {
1470                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1471         } else {
1472                 len = strlen(string);
1473                 if (len > max_len) {
1474                         if (use_tilde) {
1475                                 max_len -= 1;
1476                         }
1477                         len = max_len;
1478                         trimmed = TRUE;
1479                 }
1480         }
1482         waddnstr(view->win, string, len);
1483         if (trimmed && use_tilde) {
1484                 if (tilde_attr != -1)
1485                         wattrset(view->win, tilde_attr);
1486                 waddch(view->win, '~');
1487                 len++;
1488         }
1490         return len;
1493 static bool
1494 draw_view_line(struct view *view, unsigned int lineno)
1496         struct line *line;
1497         bool selected = (view->offset + lineno == view->lineno);
1498         bool draw_ok;
1500         assert(view_is_displayed(view));
1502         if (view->offset + lineno >= view->lines)
1503                 return FALSE;
1505         line = &view->line[view->offset + lineno];
1507         if (selected) {
1508                 line->selected = TRUE;
1509                 view->ops->select(view, line);
1510         } else if (line->selected) {
1511                 line->selected = FALSE;
1512                 wmove(view->win, lineno, 0);
1513                 wclrtoeol(view->win);
1514         }
1516         scrollok(view->win, FALSE);
1517         draw_ok = view->ops->draw(view, line, lineno, selected);
1518         scrollok(view->win, TRUE);
1520         return draw_ok;
1523 static void
1524 redraw_view_from(struct view *view, int lineno)
1526         assert(0 <= lineno && lineno < view->height);
1528         for (; lineno < view->height; lineno++) {
1529                 if (!draw_view_line(view, lineno))
1530                         break;
1531         }
1533         redrawwin(view->win);
1534         if (input_mode)
1535                 wnoutrefresh(view->win);
1536         else
1537                 wrefresh(view->win);
1540 static void
1541 redraw_view(struct view *view)
1543         wclear(view->win);
1544         redraw_view_from(view, 0);
1548 static void
1549 update_view_title(struct view *view)
1551         char buf[SIZEOF_STR];
1552         char state[SIZEOF_STR];
1553         size_t bufpos = 0, statelen = 0;
1555         assert(view_is_displayed(view));
1557         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1558                 unsigned int view_lines = view->offset + view->height;
1559                 unsigned int lines = view->lines
1560                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1561                                    : 0;
1563                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1564                                    view->ops->type,
1565                                    view->lineno + 1,
1566                                    view->lines,
1567                                    lines);
1569                 if (view->pipe) {
1570                         time_t secs = time(NULL) - view->start_time;
1572                         /* Three git seconds are a long time ... */
1573                         if (secs > 2)
1574                                 string_format_from(state, &statelen, " %lds", secs);
1575                 }
1576         }
1578         string_format_from(buf, &bufpos, "[%s]", view->name);
1579         if (*view->ref && bufpos < view->width) {
1580                 size_t refsize = strlen(view->ref);
1581                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1583                 if (minsize < view->width)
1584                         refsize = view->width - minsize + 7;
1585                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1586         }
1588         if (statelen && bufpos < view->width) {
1589                 string_format_from(buf, &bufpos, " %s", state);
1590         }
1592         if (view == display[current_view])
1593                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1594         else
1595                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1597         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1598         wclrtoeol(view->title);
1599         wmove(view->title, 0, view->width - 1);
1601         if (input_mode)
1602                 wnoutrefresh(view->title);
1603         else
1604                 wrefresh(view->title);
1607 static void
1608 resize_display(void)
1610         int offset, i;
1611         struct view *base = display[0];
1612         struct view *view = display[1] ? display[1] : display[0];
1614         /* Setup window dimensions */
1616         getmaxyx(stdscr, base->height, base->width);
1618         /* Make room for the status window. */
1619         base->height -= 1;
1621         if (view != base) {
1622                 /* Horizontal split. */
1623                 view->width   = base->width;
1624                 view->height  = SCALE_SPLIT_VIEW(base->height);
1625                 base->height -= view->height;
1627                 /* Make room for the title bar. */
1628                 view->height -= 1;
1629         }
1631         /* Make room for the title bar. */
1632         base->height -= 1;
1634         offset = 0;
1636         foreach_displayed_view (view, i) {
1637                 if (!view->win) {
1638                         view->win = newwin(view->height, 0, offset, 0);
1639                         if (!view->win)
1640                                 die("Failed to create %s view", view->name);
1642                         scrollok(view->win, TRUE);
1644                         view->title = newwin(1, 0, offset + view->height, 0);
1645                         if (!view->title)
1646                                 die("Failed to create title window");
1648                 } else {
1649                         wresize(view->win, view->height, view->width);
1650                         mvwin(view->win,   offset, 0);
1651                         mvwin(view->title, offset + view->height, 0);
1652                 }
1654                 offset += view->height + 1;
1655         }
1658 static void
1659 redraw_display(void)
1661         struct view *view;
1662         int i;
1664         foreach_displayed_view (view, i) {
1665                 redraw_view(view);
1666                 update_view_title(view);
1667         }
1670 static void
1671 update_display_cursor(struct view *view)
1673         /* Move the cursor to the right-most column of the cursor line.
1674          *
1675          * XXX: This could turn out to be a bit expensive, but it ensures that
1676          * the cursor does not jump around. */
1677         if (view->lines) {
1678                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1679                 wrefresh(view->win);
1680         }
1683 /*
1684  * Navigation
1685  */
1687 /* Scrolling backend */
1688 static void
1689 do_scroll_view(struct view *view, int lines)
1691         bool redraw_current_line = FALSE;
1693         /* The rendering expects the new offset. */
1694         view->offset += lines;
1696         assert(0 <= view->offset && view->offset < view->lines);
1697         assert(lines);
1699         /* Move current line into the view. */
1700         if (view->lineno < view->offset) {
1701                 view->lineno = view->offset;
1702                 redraw_current_line = TRUE;
1703         } else if (view->lineno >= view->offset + view->height) {
1704                 view->lineno = view->offset + view->height - 1;
1705                 redraw_current_line = TRUE;
1706         }
1708         assert(view->offset <= view->lineno && view->lineno < view->lines);
1710         /* Redraw the whole screen if scrolling is pointless. */
1711         if (view->height < ABS(lines)) {
1712                 redraw_view(view);
1714         } else {
1715                 int line = lines > 0 ? view->height - lines : 0;
1716                 int end = line + ABS(lines);
1718                 wscrl(view->win, lines);
1720                 for (; line < end; line++) {
1721                         if (!draw_view_line(view, line))
1722                                 break;
1723                 }
1725                 if (redraw_current_line)
1726                         draw_view_line(view, view->lineno - view->offset);
1727         }
1729         redrawwin(view->win);
1730         wrefresh(view->win);
1731         report("");
1734 /* Scroll frontend */
1735 static void
1736 scroll_view(struct view *view, enum request request)
1738         int lines = 1;
1740         assert(view_is_displayed(view));
1742         switch (request) {
1743         case REQ_SCROLL_PAGE_DOWN:
1744                 lines = view->height;
1745         case REQ_SCROLL_LINE_DOWN:
1746                 if (view->offset + lines > view->lines)
1747                         lines = view->lines - view->offset;
1749                 if (lines == 0 || view->offset + view->height >= view->lines) {
1750                         report("Cannot scroll beyond the last line");
1751                         return;
1752                 }
1753                 break;
1755         case REQ_SCROLL_PAGE_UP:
1756                 lines = view->height;
1757         case REQ_SCROLL_LINE_UP:
1758                 if (lines > view->offset)
1759                         lines = view->offset;
1761                 if (lines == 0) {
1762                         report("Cannot scroll beyond the first line");
1763                         return;
1764                 }
1766                 lines = -lines;
1767                 break;
1769         default:
1770                 die("request %d not handled in switch", request);
1771         }
1773         do_scroll_view(view, lines);
1776 /* Cursor moving */
1777 static void
1778 move_view(struct view *view, enum request request)
1780         int scroll_steps = 0;
1781         int steps;
1783         switch (request) {
1784         case REQ_MOVE_FIRST_LINE:
1785                 steps = -view->lineno;
1786                 break;
1788         case REQ_MOVE_LAST_LINE:
1789                 steps = view->lines - view->lineno - 1;
1790                 break;
1792         case REQ_MOVE_PAGE_UP:
1793                 steps = view->height > view->lineno
1794                       ? -view->lineno : -view->height;
1795                 break;
1797         case REQ_MOVE_PAGE_DOWN:
1798                 steps = view->lineno + view->height >= view->lines
1799                       ? view->lines - view->lineno - 1 : view->height;
1800                 break;
1802         case REQ_MOVE_UP:
1803                 steps = -1;
1804                 break;
1806         case REQ_MOVE_DOWN:
1807                 steps = 1;
1808                 break;
1810         default:
1811                 die("request %d not handled in switch", request);
1812         }
1814         if (steps <= 0 && view->lineno == 0) {
1815                 report("Cannot move beyond the first line");
1816                 return;
1818         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1819                 report("Cannot move beyond the last line");
1820                 return;
1821         }
1823         /* Move the current line */
1824         view->lineno += steps;
1825         assert(0 <= view->lineno && view->lineno < view->lines);
1827         /* Check whether the view needs to be scrolled */
1828         if (view->lineno < view->offset ||
1829             view->lineno >= view->offset + view->height) {
1830                 scroll_steps = steps;
1831                 if (steps < 0 && -steps > view->offset) {
1832                         scroll_steps = -view->offset;
1834                 } else if (steps > 0) {
1835                         if (view->lineno == view->lines - 1 &&
1836                             view->lines > view->height) {
1837                                 scroll_steps = view->lines - view->offset - 1;
1838                                 if (scroll_steps >= view->height)
1839                                         scroll_steps -= view->height - 1;
1840                         }
1841                 }
1842         }
1844         if (!view_is_displayed(view)) {
1845                 view->offset += scroll_steps;
1846                 assert(0 <= view->offset && view->offset < view->lines);
1847                 view->ops->select(view, &view->line[view->lineno]);
1848                 return;
1849         }
1851         /* Repaint the old "current" line if we be scrolling */
1852         if (ABS(steps) < view->height)
1853                 draw_view_line(view, view->lineno - steps - view->offset);
1855         if (scroll_steps) {
1856                 do_scroll_view(view, scroll_steps);
1857                 return;
1858         }
1860         /* Draw the current line */
1861         draw_view_line(view, view->lineno - view->offset);
1863         redrawwin(view->win);
1864         wrefresh(view->win);
1865         report("");
1869 /*
1870  * Searching
1871  */
1873 static void search_view(struct view *view, enum request request);
1875 static bool
1876 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1878         assert(view_is_displayed(view));
1880         if (!view->ops->grep(view, line))
1881                 return FALSE;
1883         if (lineno - view->offset >= view->height) {
1884                 view->offset = lineno;
1885                 view->lineno = lineno;
1886                 redraw_view(view);
1888         } else {
1889                 unsigned long old_lineno = view->lineno - view->offset;
1891                 view->lineno = lineno;
1892                 draw_view_line(view, old_lineno);
1894                 draw_view_line(view, view->lineno - view->offset);
1895                 redrawwin(view->win);
1896                 wrefresh(view->win);
1897         }
1899         report("Line %ld matches '%s'", lineno + 1, view->grep);
1900         return TRUE;
1903 static void
1904 find_next(struct view *view, enum request request)
1906         unsigned long lineno = view->lineno;
1907         int direction;
1909         if (!*view->grep) {
1910                 if (!*opt_search)
1911                         report("No previous search");
1912                 else
1913                         search_view(view, request);
1914                 return;
1915         }
1917         switch (request) {
1918         case REQ_SEARCH:
1919         case REQ_FIND_NEXT:
1920                 direction = 1;
1921                 break;
1923         case REQ_SEARCH_BACK:
1924         case REQ_FIND_PREV:
1925                 direction = -1;
1926                 break;
1928         default:
1929                 return;
1930         }
1932         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1933                 lineno += direction;
1935         /* Note, lineno is unsigned long so will wrap around in which case it
1936          * will become bigger than view->lines. */
1937         for (; lineno < view->lines; lineno += direction) {
1938                 struct line *line = &view->line[lineno];
1940                 if (find_next_line(view, lineno, line))
1941                         return;
1942         }
1944         report("No match found for '%s'", view->grep);
1947 static void
1948 search_view(struct view *view, enum request request)
1950         int regex_err;
1952         if (view->regex) {
1953                 regfree(view->regex);
1954                 *view->grep = 0;
1955         } else {
1956                 view->regex = calloc(1, sizeof(*view->regex));
1957                 if (!view->regex)
1958                         return;
1959         }
1961         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1962         if (regex_err != 0) {
1963                 char buf[SIZEOF_STR] = "unknown error";
1965                 regerror(regex_err, view->regex, buf, sizeof(buf));
1966                 report("Search failed: %s", buf);
1967                 return;
1968         }
1970         string_copy(view->grep, opt_search);
1972         find_next(view, request);
1975 /*
1976  * Incremental updating
1977  */
1979 static void
1980 end_update(struct view *view)
1982         if (!view->pipe)
1983                 return;
1984         set_nonblocking_input(FALSE);
1985         if (view->pipe == stdin)
1986                 fclose(view->pipe);
1987         else
1988                 pclose(view->pipe);
1989         view->pipe = NULL;
1992 static bool
1993 begin_update(struct view *view)
1995         if (view->pipe)
1996                 end_update(view);
1998         if (opt_cmd[0]) {
1999                 string_copy(view->cmd, opt_cmd);
2000                 opt_cmd[0] = 0;
2001                 /* When running random commands, initially show the
2002                  * command in the title. However, it maybe later be
2003                  * overwritten if a commit line is selected. */
2004                 if (view == VIEW(REQ_VIEW_PAGER))
2005                         string_copy(view->ref, view->cmd);
2006                 else
2007                         view->ref[0] = 0;
2009         } else if (view == VIEW(REQ_VIEW_TREE)) {
2010                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2011                 char path[SIZEOF_STR];
2013                 if (strcmp(view->vid, view->id))
2014                         opt_path[0] = path[0] = 0;
2015                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2016                         return FALSE;
2018                 if (!string_format(view->cmd, format, view->id, path))
2019                         return FALSE;
2021         } else {
2022                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2023                 const char *id = view->id;
2025                 if (!string_format(view->cmd, format, id, id, id, id, id))
2026                         return FALSE;
2028                 /* Put the current ref_* value to the view title ref
2029                  * member. This is needed by the blob view. Most other
2030                  * views sets it automatically after loading because the
2031                  * first line is a commit line. */
2032                 string_copy_rev(view->ref, view->id);
2033         }
2035         /* Special case for the pager view. */
2036         if (opt_pipe) {
2037                 view->pipe = opt_pipe;
2038                 opt_pipe = NULL;
2039         } else {
2040                 view->pipe = popen(view->cmd, "r");
2041         }
2043         if (!view->pipe)
2044                 return FALSE;
2046         set_nonblocking_input(TRUE);
2048         view->offset = 0;
2049         view->lines  = 0;
2050         view->lineno = 0;
2051         string_copy_rev(view->vid, view->id);
2053         if (view->line) {
2054                 int i;
2056                 for (i = 0; i < view->lines; i++)
2057                         if (view->line[i].data)
2058                                 free(view->line[i].data);
2060                 free(view->line);
2061                 view->line = NULL;
2062         }
2064         view->start_time = time(NULL);
2066         return TRUE;
2069 static struct line *
2070 realloc_lines(struct view *view, size_t line_size)
2072         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2074         if (!tmp)
2075                 return NULL;
2077         view->line = tmp;
2078         view->line_size = line_size;
2079         return view->line;
2082 static bool
2083 update_view(struct view *view)
2085         char in_buffer[BUFSIZ];
2086         char out_buffer[BUFSIZ * 2];
2087         char *line;
2088         /* The number of lines to read. If too low it will cause too much
2089          * redrawing (and possible flickering), if too high responsiveness
2090          * will suffer. */
2091         unsigned long lines = view->height;
2092         int redraw_from = -1;
2094         if (!view->pipe)
2095                 return TRUE;
2097         /* Only redraw if lines are visible. */
2098         if (view->offset + view->height >= view->lines)
2099                 redraw_from = view->lines - view->offset;
2101         /* FIXME: This is probably not perfect for backgrounded views. */
2102         if (!realloc_lines(view, view->lines + lines))
2103                 goto alloc_error;
2105         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2106                 size_t linelen = strlen(line);
2108                 if (linelen)
2109                         line[linelen - 1] = 0;
2111                 if (opt_iconv != ICONV_NONE) {
2112                         ICONV_CONST char *inbuf = line;
2113                         size_t inlen = linelen;
2115                         char *outbuf = out_buffer;
2116                         size_t outlen = sizeof(out_buffer);
2118                         size_t ret;
2120                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2121                         if (ret != (size_t) -1) {
2122                                 line = out_buffer;
2123                                 linelen = strlen(out_buffer);
2124                         }
2125                 }
2127                 if (!view->ops->read(view, line))
2128                         goto alloc_error;
2130                 if (lines-- == 1)
2131                         break;
2132         }
2134         {
2135                 int digits;
2137                 lines = view->lines;
2138                 for (digits = 0; lines; digits++)
2139                         lines /= 10;
2141                 /* Keep the displayed view in sync with line number scaling. */
2142                 if (digits != view->digits) {
2143                         view->digits = digits;
2144                         redraw_from = 0;
2145                 }
2146         }
2148         if (!view_is_displayed(view))
2149                 goto check_pipe;
2151         if (view == VIEW(REQ_VIEW_TREE)) {
2152                 /* Clear the view and redraw everything since the tree sorting
2153                  * might have rearranged things. */
2154                 redraw_view(view);
2156         } else if (redraw_from >= 0) {
2157                 /* If this is an incremental update, redraw the previous line
2158                  * since for commits some members could have changed when
2159                  * loading the main view. */
2160                 if (redraw_from > 0)
2161                         redraw_from--;
2163                 /* Since revision graph visualization requires knowledge
2164                  * about the parent commit, it causes a further one-off
2165                  * needed to be redrawn for incremental updates. */
2166                 if (redraw_from > 0 && opt_rev_graph)
2167                         redraw_from--;
2169                 /* Incrementally draw avoids flickering. */
2170                 redraw_view_from(view, redraw_from);
2171         }
2173         /* Update the title _after_ the redraw so that if the redraw picks up a
2174          * commit reference in view->ref it'll be available here. */
2175         update_view_title(view);
2177 check_pipe:
2178         if (ferror(view->pipe)) {
2179                 report("Failed to read: %s", strerror(errno));
2180                 goto end;
2182         } else if (feof(view->pipe)) {
2183                 report("");
2184                 goto end;
2185         }
2187         return TRUE;
2189 alloc_error:
2190         report("Allocation failure");
2192 end:
2193         view->ops->read(view, NULL);
2194         end_update(view);
2195         return FALSE;
2198 static struct line *
2199 add_line_data(struct view *view, void *data, enum line_type type)
2201         struct line *line = &view->line[view->lines++];
2203         memset(line, 0, sizeof(*line));
2204         line->type = type;
2205         line->data = data;
2207         return line;
2210 static struct line *
2211 add_line_text(struct view *view, char *data, enum line_type type)
2213         if (data)
2214                 data = strdup(data);
2216         return data ? add_line_data(view, data, type) : NULL;
2220 /*
2221  * View opening
2222  */
2224 enum open_flags {
2225         OPEN_DEFAULT = 0,       /* Use default view switching. */
2226         OPEN_SPLIT = 1,         /* Split current view. */
2227         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2228         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2229 };
2231 static void
2232 open_view(struct view *prev, enum request request, enum open_flags flags)
2234         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2235         bool split = !!(flags & OPEN_SPLIT);
2236         bool reload = !!(flags & OPEN_RELOAD);
2237         struct view *view = VIEW(request);
2238         int nviews = displayed_views();
2239         struct view *base_view = display[0];
2241         if (view == prev && nviews == 1 && !reload) {
2242                 report("Already in %s view", view->name);
2243                 return;
2244         }
2246         if (view->ops->open) {
2247                 if (!view->ops->open(view)) {
2248                         report("Failed to load %s view", view->name);
2249                         return;
2250                 }
2252         } else if ((reload || strcmp(view->vid, view->id)) &&
2253                    !begin_update(view)) {
2254                 report("Failed to load %s view", view->name);
2255                 return;
2256         }
2258         if (split) {
2259                 display[1] = view;
2260                 if (!backgrounded)
2261                         current_view = 1;
2262         } else {
2263                 /* Maximize the current view. */
2264                 memset(display, 0, sizeof(display));
2265                 current_view = 0;
2266                 display[current_view] = view;
2267         }
2269         /* Resize the view when switching between split- and full-screen,
2270          * or when switching between two different full-screen views. */
2271         if (nviews != displayed_views() ||
2272             (nviews == 1 && base_view != display[0]))
2273                 resize_display();
2275         if (split && prev->lineno - prev->offset >= prev->height) {
2276                 /* Take the title line into account. */
2277                 int lines = prev->lineno - prev->offset - prev->height + 1;
2279                 /* Scroll the view that was split if the current line is
2280                  * outside the new limited view. */
2281                 do_scroll_view(prev, lines);
2282         }
2284         if (prev && view != prev) {
2285                 if (split && !backgrounded) {
2286                         /* "Blur" the previous view. */
2287                         update_view_title(prev);
2288                 }
2290                 view->parent = prev;
2291         }
2293         if (view->pipe && view->lines == 0) {
2294                 /* Clear the old view and let the incremental updating refill
2295                  * the screen. */
2296                 wclear(view->win);
2297                 report("");
2298         } else {
2299                 redraw_view(view);
2300                 report("");
2301         }
2303         /* If the view is backgrounded the above calls to report()
2304          * won't redraw the view title. */
2305         if (backgrounded)
2306                 update_view_title(view);
2309 static void
2310 open_external_viewer(const char *cmd)
2312         def_prog_mode();           /* save current tty modes */
2313         endwin();                  /* restore original tty modes */
2314         system(cmd);
2315         fprintf(stderr, "Press Enter to continue");
2316         getc(stdin);
2317         reset_prog_mode();
2318         redraw_display();
2321 static void
2322 open_mergetool(const char *file)
2324         char cmd[SIZEOF_STR];
2325         char file_sq[SIZEOF_STR];
2327         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2328             string_format(cmd, "git mergetool %s", file_sq)) {
2329                 open_external_viewer(cmd);
2330         }
2333 static void
2334 open_editor(bool from_root, const char *file)
2336         char cmd[SIZEOF_STR];
2337         char file_sq[SIZEOF_STR];
2338         char *editor;
2339         char *prefix = from_root ? opt_cdup : "";
2341         editor = getenv("GIT_EDITOR");
2342         if (!editor && *opt_editor)
2343                 editor = opt_editor;
2344         if (!editor)
2345                 editor = getenv("VISUAL");
2346         if (!editor)
2347                 editor = getenv("EDITOR");
2348         if (!editor)
2349                 editor = "vi";
2351         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2352             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2353                 open_external_viewer(cmd);
2354         }
2357 static void
2358 open_run_request(enum request request)
2360         struct run_request *req = get_run_request(request);
2361         char buf[SIZEOF_STR * 2];
2362         size_t bufpos;
2363         char *cmd;
2365         if (!req) {
2366                 report("Unknown run request");
2367                 return;
2368         }
2370         bufpos = 0;
2371         cmd = req->cmd;
2373         while (cmd) {
2374                 char *next = strstr(cmd, "%(");
2375                 int len = next - cmd;
2376                 char *value;
2378                 if (!next) {
2379                         len = strlen(cmd);
2380                         value = "";
2382                 } else if (!strncmp(next, "%(head)", 7)) {
2383                         value = ref_head;
2385                 } else if (!strncmp(next, "%(commit)", 9)) {
2386                         value = ref_commit;
2388                 } else if (!strncmp(next, "%(blob)", 7)) {
2389                         value = ref_blob;
2391                 } else {
2392                         report("Unknown replacement in run request: `%s`", req->cmd);
2393                         return;
2394                 }
2396                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2397                         return;
2399                 if (next)
2400                         next = strchr(next, ')') + 1;
2401                 cmd = next;
2402         }
2404         open_external_viewer(buf);
2407 /*
2408  * User request switch noodle
2409  */
2411 static int
2412 view_driver(struct view *view, enum request request)
2414         int i;
2416         if (request == REQ_NONE) {
2417                 doupdate();
2418                 return TRUE;
2419         }
2421         if (request > REQ_NONE) {
2422                 open_run_request(request);
2423                 return TRUE;
2424         }
2426         if (view && view->lines) {
2427                 request = view->ops->request(view, request, &view->line[view->lineno]);
2428                 if (request == REQ_NONE)
2429                         return TRUE;
2430         }
2432         switch (request) {
2433         case REQ_MOVE_UP:
2434         case REQ_MOVE_DOWN:
2435         case REQ_MOVE_PAGE_UP:
2436         case REQ_MOVE_PAGE_DOWN:
2437         case REQ_MOVE_FIRST_LINE:
2438         case REQ_MOVE_LAST_LINE:
2439                 move_view(view, request);
2440                 break;
2442         case REQ_SCROLL_LINE_DOWN:
2443         case REQ_SCROLL_LINE_UP:
2444         case REQ_SCROLL_PAGE_DOWN:
2445         case REQ_SCROLL_PAGE_UP:
2446                 scroll_view(view, request);
2447                 break;
2449         case REQ_VIEW_BLOB:
2450                 if (!ref_blob[0]) {
2451                         report("No file chosen, press %s to open tree view",
2452                                get_key(REQ_VIEW_TREE));
2453                         break;
2454                 }
2455                 open_view(view, request, OPEN_DEFAULT);
2456                 break;
2458         case REQ_VIEW_PAGER:
2459                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2460                         report("No pager content, press %s to run command from prompt",
2461                                get_key(REQ_PROMPT));
2462                         break;
2463                 }
2464                 open_view(view, request, OPEN_DEFAULT);
2465                 break;
2467         case REQ_VIEW_STAGE:
2468                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2469                         report("No stage content, press %s to open the status view and choose file",
2470                                get_key(REQ_VIEW_STATUS));
2471                         break;
2472                 }
2473                 open_view(view, request, OPEN_DEFAULT);
2474                 break;
2476         case REQ_VIEW_STATUS:
2477                 if (opt_is_inside_work_tree == FALSE) {
2478                         report("The status view requires a working tree");
2479                         break;
2480                 }
2481                 open_view(view, request, OPEN_DEFAULT);
2482                 break;
2484         case REQ_VIEW_MAIN:
2485         case REQ_VIEW_DIFF:
2486         case REQ_VIEW_LOG:
2487         case REQ_VIEW_TREE:
2488         case REQ_VIEW_HELP:
2489                 open_view(view, request, OPEN_DEFAULT);
2490                 break;
2492         case REQ_NEXT:
2493         case REQ_PREVIOUS:
2494                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2496                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2497                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2498                    (view == VIEW(REQ_VIEW_STAGE) &&
2499                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2500                    (view == VIEW(REQ_VIEW_BLOB) &&
2501                      view->parent == VIEW(REQ_VIEW_TREE))) {
2502                         int line;
2504                         view = view->parent;
2505                         line = view->lineno;
2506                         move_view(view, request);
2507                         if (view_is_displayed(view))
2508                                 update_view_title(view);
2509                         if (line != view->lineno)
2510                                 view->ops->request(view, REQ_ENTER,
2511                                                    &view->line[view->lineno]);
2513                 } else {
2514                         move_view(view, request);
2515                 }
2516                 break;
2518         case REQ_VIEW_NEXT:
2519         {
2520                 int nviews = displayed_views();
2521                 int next_view = (current_view + 1) % nviews;
2523                 if (next_view == current_view) {
2524                         report("Only one view is displayed");
2525                         break;
2526                 }
2528                 current_view = next_view;
2529                 /* Blur out the title of the previous view. */
2530                 update_view_title(view);
2531                 report("");
2532                 break;
2533         }
2534         case REQ_REFRESH:
2535                 report("Refreshing is not yet supported for the %s view", view->name);
2536                 break;
2538         case REQ_TOGGLE_LINENO:
2539                 opt_line_number = !opt_line_number;
2540                 redraw_display();
2541                 break;
2543         case REQ_TOGGLE_REV_GRAPH:
2544                 opt_rev_graph = !opt_rev_graph;
2545                 redraw_display();
2546                 break;
2548         case REQ_PROMPT:
2549                 /* Always reload^Wrerun commands from the prompt. */
2550                 open_view(view, opt_request, OPEN_RELOAD);
2551                 break;
2553         case REQ_SEARCH:
2554         case REQ_SEARCH_BACK:
2555                 search_view(view, request);
2556                 break;
2558         case REQ_FIND_NEXT:
2559         case REQ_FIND_PREV:
2560                 find_next(view, request);
2561                 break;
2563         case REQ_STOP_LOADING:
2564                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2565                         view = &views[i];
2566                         if (view->pipe)
2567                                 report("Stopped loading the %s view", view->name),
2568                         end_update(view);
2569                 }
2570                 break;
2572         case REQ_SHOW_VERSION:
2573                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2574                 return TRUE;
2576         case REQ_SCREEN_RESIZE:
2577                 resize_display();
2578                 /* Fall-through */
2579         case REQ_SCREEN_REDRAW:
2580                 redraw_display();
2581                 break;
2583         case REQ_EDIT:
2584                 report("Nothing to edit");
2585                 break;
2588         case REQ_ENTER:
2589                 report("Nothing to enter");
2590                 break;
2593         case REQ_VIEW_CLOSE:
2594                 /* XXX: Mark closed views by letting view->parent point to the
2595                  * view itself. Parents to closed view should never be
2596                  * followed. */
2597                 if (view->parent &&
2598                     view->parent->parent != view->parent) {
2599                         memset(display, 0, sizeof(display));
2600                         current_view = 0;
2601                         display[current_view] = view->parent;
2602                         view->parent = view;
2603                         resize_display();
2604                         redraw_display();
2605                         break;
2606                 }
2607                 /* Fall-through */
2608         case REQ_QUIT:
2609                 return FALSE;
2611         default:
2612                 /* An unknown key will show most commonly used commands. */
2613                 report("Unknown key, press 'h' for help");
2614                 return TRUE;
2615         }
2617         return TRUE;
2621 /*
2622  * Pager backend
2623  */
2625 static bool
2626 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2628         char *text = line->data;
2629         enum line_type type = line->type;
2630         int attr;
2632         wmove(view->win, lineno, 0);
2634         if (selected) {
2635                 type = LINE_CURSOR;
2636                 wchgat(view->win, -1, 0, type, NULL);
2637         }
2639         attr = get_line_attr(type);
2640         wattrset(view->win, attr);
2642         if (opt_line_number || opt_tab_size < TABSIZE) {
2643                 static char spaces[] = "                    ";
2644                 int col_offset = 0, col = 0;
2646                 if (opt_line_number) {
2647                         unsigned long real_lineno = view->offset + lineno + 1;
2649                         if (real_lineno == 1 ||
2650                             (real_lineno % opt_num_interval) == 0) {
2651                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2653                         } else {
2654                                 waddnstr(view->win, spaces,
2655                                          MIN(view->digits, STRING_SIZE(spaces)));
2656                         }
2657                         waddstr(view->win, ": ");
2658                         col_offset = view->digits + 2;
2659                 }
2661                 while (text && col_offset + col < view->width) {
2662                         int cols_max = view->width - col_offset - col;
2663                         char *pos = text;
2664                         int cols;
2666                         if (*text == '\t') {
2667                                 text++;
2668                                 assert(sizeof(spaces) > TABSIZE);
2669                                 pos = spaces;
2670                                 cols = opt_tab_size - (col % opt_tab_size);
2672                         } else {
2673                                 text = strchr(text, '\t');
2674                                 cols = line ? text - pos : strlen(pos);
2675                         }
2677                         waddnstr(view->win, pos, MIN(cols, cols_max));
2678                         col += cols;
2679                 }
2681         } else {
2682                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2684                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2685         }
2687         return TRUE;
2690 static bool
2691 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2693         char refbuf[SIZEOF_STR];
2694         char *ref = NULL;
2695         FILE *pipe;
2697         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2698                 return TRUE;
2700         pipe = popen(refbuf, "r");
2701         if (!pipe)
2702                 return TRUE;
2704         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2705                 ref = chomp_string(ref);
2706         pclose(pipe);
2708         if (!ref || !*ref)
2709                 return TRUE;
2711         /* This is the only fatal call, since it can "corrupt" the buffer. */
2712         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2713                 return FALSE;
2715         return TRUE;
2718 static void
2719 add_pager_refs(struct view *view, struct line *line)
2721         char buf[SIZEOF_STR];
2722         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2723         struct ref **refs;
2724         size_t bufpos = 0, refpos = 0;
2725         const char *sep = "Refs: ";
2726         bool is_tag = FALSE;
2728         assert(line->type == LINE_COMMIT);
2730         refs = get_refs(commit_id);
2731         if (!refs) {
2732                 if (view == VIEW(REQ_VIEW_DIFF))
2733                         goto try_add_describe_ref;
2734                 return;
2735         }
2737         do {
2738                 struct ref *ref = refs[refpos];
2739                 char *fmt = ref->tag    ? "%s[%s]" :
2740                             ref->remote ? "%s<%s>" : "%s%s";
2742                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2743                         return;
2744                 sep = ", ";
2745                 if (ref->tag)
2746                         is_tag = TRUE;
2747         } while (refs[refpos++]->next);
2749         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2750 try_add_describe_ref:
2751                 /* Add <tag>-g<commit_id> "fake" reference. */
2752                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2753                         return;
2754         }
2756         if (bufpos == 0)
2757                 return;
2759         if (!realloc_lines(view, view->line_size + 1))
2760                 return;
2762         add_line_text(view, buf, LINE_PP_REFS);
2765 static bool
2766 pager_read(struct view *view, char *data)
2768         struct line *line;
2770         if (!data)
2771                 return TRUE;
2773         line = add_line_text(view, data, get_line_type(data));
2774         if (!line)
2775                 return FALSE;
2777         if (line->type == LINE_COMMIT &&
2778             (view == VIEW(REQ_VIEW_DIFF) ||
2779              view == VIEW(REQ_VIEW_LOG)))
2780                 add_pager_refs(view, line);
2782         return TRUE;
2785 static enum request
2786 pager_request(struct view *view, enum request request, struct line *line)
2788         int split = 0;
2790         if (request != REQ_ENTER)
2791                 return request;
2793         if (line->type == LINE_COMMIT &&
2794            (view == VIEW(REQ_VIEW_LOG) ||
2795             view == VIEW(REQ_VIEW_PAGER))) {
2796                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2797                 split = 1;
2798         }
2800         /* Always scroll the view even if it was split. That way
2801          * you can use Enter to scroll through the log view and
2802          * split open each commit diff. */
2803         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2805         /* FIXME: A minor workaround. Scrolling the view will call report("")
2806          * but if we are scrolling a non-current view this won't properly
2807          * update the view title. */
2808         if (split)
2809                 update_view_title(view);
2811         return REQ_NONE;
2814 static bool
2815 pager_grep(struct view *view, struct line *line)
2817         regmatch_t pmatch;
2818         char *text = line->data;
2820         if (!*text)
2821                 return FALSE;
2823         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2824                 return FALSE;
2826         return TRUE;
2829 static void
2830 pager_select(struct view *view, struct line *line)
2832         if (line->type == LINE_COMMIT) {
2833                 char *text = (char *)line->data + STRING_SIZE("commit ");
2835                 if (view != VIEW(REQ_VIEW_PAGER))
2836                         string_copy_rev(view->ref, text);
2837                 string_copy_rev(ref_commit, text);
2838         }
2841 static struct view_ops pager_ops = {
2842         "line",
2843         NULL,
2844         pager_read,
2845         pager_draw,
2846         pager_request,
2847         pager_grep,
2848         pager_select,
2849 };
2852 /*
2853  * Help backend
2854  */
2856 static bool
2857 help_open(struct view *view)
2859         char buf[BUFSIZ];
2860         int lines = ARRAY_SIZE(req_info) + 2;
2861         int i;
2863         if (view->lines > 0)
2864                 return TRUE;
2866         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2867                 if (!req_info[i].request)
2868                         lines++;
2870         lines += run_requests + 1;
2872         view->line = calloc(lines, sizeof(*view->line));
2873         if (!view->line)
2874                 return FALSE;
2876         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2878         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2879                 char *key;
2881                 if (req_info[i].request == REQ_NONE)
2882                         continue;
2884                 if (!req_info[i].request) {
2885                         add_line_text(view, "", LINE_DEFAULT);
2886                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2887                         continue;
2888                 }
2890                 key = get_key(req_info[i].request);
2891                 if (!*key)
2892                         key = "(no key defined)";
2894                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2895                         continue;
2897                 add_line_text(view, buf, LINE_DEFAULT);
2898         }
2900         if (run_requests) {
2901                 add_line_text(view, "", LINE_DEFAULT);
2902                 add_line_text(view, "External commands:", LINE_DEFAULT);
2903         }
2905         for (i = 0; i < run_requests; i++) {
2906                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2907                 char *key;
2909                 if (!req)
2910                         continue;
2912                 key = get_key_name(req->key);
2913                 if (!*key)
2914                         key = "(no key defined)";
2916                 if (!string_format(buf, "    %-10s %-14s `%s`",
2917                                    keymap_table[req->keymap].name,
2918                                    key, req->cmd))
2919                         continue;
2921                 add_line_text(view, buf, LINE_DEFAULT);
2922         }
2924         return TRUE;
2927 static struct view_ops help_ops = {
2928         "line",
2929         help_open,
2930         NULL,
2931         pager_draw,
2932         pager_request,
2933         pager_grep,
2934         pager_select,
2935 };
2938 /*
2939  * Tree backend
2940  */
2942 struct tree_stack_entry {
2943         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2944         unsigned long lineno;           /* Line number to restore */
2945         char *name;                     /* Position of name in opt_path */
2946 };
2948 /* The top of the path stack. */
2949 static struct tree_stack_entry *tree_stack = NULL;
2950 unsigned long tree_lineno = 0;
2952 static void
2953 pop_tree_stack_entry(void)
2955         struct tree_stack_entry *entry = tree_stack;
2957         tree_lineno = entry->lineno;
2958         entry->name[0] = 0;
2959         tree_stack = entry->prev;
2960         free(entry);
2963 static void
2964 push_tree_stack_entry(char *name, unsigned long lineno)
2966         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2967         size_t pathlen = strlen(opt_path);
2969         if (!entry)
2970                 return;
2972         entry->prev = tree_stack;
2973         entry->name = opt_path + pathlen;
2974         tree_stack = entry;
2976         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2977                 pop_tree_stack_entry();
2978                 return;
2979         }
2981         /* Move the current line to the first tree entry. */
2982         tree_lineno = 1;
2983         entry->lineno = lineno;
2986 /* Parse output from git-ls-tree(1):
2987  *
2988  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2989  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2990  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2991  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2992  */
2994 #define SIZEOF_TREE_ATTR \
2995         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2997 #define TREE_UP_FORMAT "040000 tree %s\t.."
2999 static int
3000 tree_compare_entry(enum line_type type1, char *name1,
3001                    enum line_type type2, char *name2)
3003         if (type1 != type2) {
3004                 if (type1 == LINE_TREE_DIR)
3005                         return -1;
3006                 return 1;
3007         }
3009         return strcmp(name1, name2);
3012 static bool
3013 tree_read(struct view *view, char *text)
3015         size_t textlen = text ? strlen(text) : 0;
3016         char buf[SIZEOF_STR];
3017         unsigned long pos;
3018         enum line_type type;
3019         bool first_read = view->lines == 0;
3021         if (textlen <= SIZEOF_TREE_ATTR)
3022                 return FALSE;
3024         type = text[STRING_SIZE("100644 ")] == 't'
3025              ? LINE_TREE_DIR : LINE_TREE_FILE;
3027         if (first_read) {
3028                 /* Add path info line */
3029                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3030                     !realloc_lines(view, view->line_size + 1) ||
3031                     !add_line_text(view, buf, LINE_DEFAULT))
3032                         return FALSE;
3034                 /* Insert "link" to parent directory. */
3035                 if (*opt_path) {
3036                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3037                             !realloc_lines(view, view->line_size + 1) ||
3038                             !add_line_text(view, buf, LINE_TREE_DIR))
3039                                 return FALSE;
3040                 }
3041         }
3043         /* Strip the path part ... */
3044         if (*opt_path) {
3045                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3046                 size_t striplen = strlen(opt_path);
3047                 char *path = text + SIZEOF_TREE_ATTR;
3049                 if (pathlen > striplen)
3050                         memmove(path, path + striplen,
3051                                 pathlen - striplen + 1);
3052         }
3054         /* Skip "Directory ..." and ".." line. */
3055         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3056                 struct line *line = &view->line[pos];
3057                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3058                 char *path2 = text + SIZEOF_TREE_ATTR;
3059                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3061                 if (cmp <= 0)
3062                         continue;
3064                 text = strdup(text);
3065                 if (!text)
3066                         return FALSE;
3068                 if (view->lines > pos)
3069                         memmove(&view->line[pos + 1], &view->line[pos],
3070                                 (view->lines - pos) * sizeof(*line));
3072                 line = &view->line[pos];
3073                 line->data = text;
3074                 line->type = type;
3075                 view->lines++;
3076                 return TRUE;
3077         }
3079         if (!add_line_text(view, text, type))
3080                 return FALSE;
3082         if (tree_lineno > view->lineno) {
3083                 view->lineno = tree_lineno;
3084                 tree_lineno = 0;
3085         }
3087         return TRUE;
3090 static enum request
3091 tree_request(struct view *view, enum request request, struct line *line)
3093         enum open_flags flags;
3095         if (request == REQ_TREE_PARENT) {
3096                 if (*opt_path) {
3097                         /* fake 'cd  ..' */
3098                         request = REQ_ENTER;
3099                         line = &view->line[1];
3100                 } else {
3101                         /* quit view if at top of tree */
3102                         return REQ_VIEW_CLOSE;
3103                 }
3104         }
3105         if (request != REQ_ENTER)
3106                 return request;
3108         /* Cleanup the stack if the tree view is at a different tree. */
3109         while (!*opt_path && tree_stack)
3110                 pop_tree_stack_entry();
3112         switch (line->type) {
3113         case LINE_TREE_DIR:
3114                 /* Depending on whether it is a subdir or parent (updir?) link
3115                  * mangle the path buffer. */
3116                 if (line == &view->line[1] && *opt_path) {
3117                         pop_tree_stack_entry();
3119                 } else {
3120                         char *data = line->data;
3121                         char *basename = data + SIZEOF_TREE_ATTR;
3123                         push_tree_stack_entry(basename, view->lineno);
3124                 }
3126                 /* Trees and subtrees share the same ID, so they are not not
3127                  * unique like blobs. */
3128                 flags = OPEN_RELOAD;
3129                 request = REQ_VIEW_TREE;
3130                 break;
3132         case LINE_TREE_FILE:
3133                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3134                 request = REQ_VIEW_BLOB;
3135                 break;
3137         default:
3138                 return TRUE;
3139         }
3141         open_view(view, request, flags);
3142         if (request == REQ_VIEW_TREE) {
3143                 view->lineno = tree_lineno;
3144         }
3146         return REQ_NONE;
3149 static void
3150 tree_select(struct view *view, struct line *line)
3152         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3154         if (line->type == LINE_TREE_FILE) {
3155                 string_copy_rev(ref_blob, text);
3157         } else if (line->type != LINE_TREE_DIR) {
3158                 return;
3159         }
3161         string_copy_rev(view->ref, text);
3164 static struct view_ops tree_ops = {
3165         "file",
3166         NULL,
3167         tree_read,
3168         pager_draw,
3169         tree_request,
3170         pager_grep,
3171         tree_select,
3172 };
3174 static bool
3175 blob_read(struct view *view, char *line)
3177         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3180 static struct view_ops blob_ops = {
3181         "line",
3182         NULL,
3183         blob_read,
3184         pager_draw,
3185         pager_request,
3186         pager_grep,
3187         pager_select,
3188 };
3191 /*
3192  * Status backend
3193  */
3195 struct status {
3196         char status;
3197         struct {
3198                 mode_t mode;
3199                 char rev[SIZEOF_REV];
3200                 char name[SIZEOF_STR];
3201         } old;
3202         struct {
3203                 mode_t mode;
3204                 char rev[SIZEOF_REV];
3205                 char name[SIZEOF_STR];
3206         } new;
3207 };
3209 static struct status stage_status;
3210 static enum line_type stage_line_type;
3212 /* Get fields from the diff line:
3213  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3214  */
3215 static inline bool
3216 status_get_diff(struct status *file, char *buf, size_t bufsize)
3218         char *old_mode = buf +  1;
3219         char *new_mode = buf +  8;
3220         char *old_rev  = buf + 15;
3221         char *new_rev  = buf + 56;
3222         char *status   = buf + 97;
3224         if (bufsize < 99 ||
3225             old_mode[-1] != ':' ||
3226             new_mode[-1] != ' ' ||
3227             old_rev[-1]  != ' ' ||
3228             new_rev[-1]  != ' ' ||
3229             status[-1]   != ' ')
3230                 return FALSE;
3232         file->status = *status;
3234         string_copy_rev(file->old.rev, old_rev);
3235         string_copy_rev(file->new.rev, new_rev);
3237         file->old.mode = strtoul(old_mode, NULL, 8);
3238         file->new.mode = strtoul(new_mode, NULL, 8);
3240         file->old.name[0] = file->new.name[0] = 0;
3242         return TRUE;
3245 static bool
3246 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3248         struct status *file = NULL;
3249         struct status *unmerged = NULL;
3250         char buf[SIZEOF_STR * 4];
3251         size_t bufsize = 0;
3252         FILE *pipe;
3254         pipe = popen(cmd, "r");
3255         if (!pipe)
3256                 return FALSE;
3258         add_line_data(view, NULL, type);
3260         while (!feof(pipe) && !ferror(pipe)) {
3261                 char *sep;
3262                 size_t readsize;
3264                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3265                 if (!readsize)
3266                         break;
3267                 bufsize += readsize;
3269                 /* Process while we have NUL chars. */
3270                 while ((sep = memchr(buf, 0, bufsize))) {
3271                         size_t sepsize = sep - buf + 1;
3273                         if (!file) {
3274                                 if (!realloc_lines(view, view->line_size + 1))
3275                                         goto error_out;
3277                                 file = calloc(1, sizeof(*file));
3278                                 if (!file)
3279                                         goto error_out;
3281                                 add_line_data(view, file, type);
3282                         }
3284                         /* Parse diff info part. */
3285                         if (!diff) {
3286                                 file->status = '?';
3288                         } else if (!file->status) {
3289                                 if (!status_get_diff(file, buf, sepsize))
3290                                         goto error_out;
3292                                 bufsize -= sepsize;
3293                                 memmove(buf, sep + 1, bufsize);
3295                                 sep = memchr(buf, 0, bufsize);
3296                                 if (!sep)
3297                                         break;
3298                                 sepsize = sep - buf + 1;
3300                                 /* Collapse all 'M'odified entries that
3301                                  * follow a associated 'U'nmerged entry.
3302                                  */
3303                                 if (file->status == 'U') {
3304                                         unmerged = file;
3306                                 } else if (unmerged) {
3307                                         int collapse = !strcmp(buf, unmerged->new.name);
3309                                         unmerged = NULL;
3310                                         if (collapse) {
3311                                                 free(file);
3312                                                 view->lines--;
3313                                                 continue;
3314                                         }
3315                                 }
3316                         }
3318                         /* Grab the old name for rename/copy. */
3319                         if (!*file->old.name &&
3320                             (file->status == 'R' || file->status == 'C')) {
3321                                 sepsize = sep - buf + 1;
3322                                 string_ncopy(file->old.name, buf, sepsize);
3323                                 bufsize -= sepsize;
3324                                 memmove(buf, sep + 1, bufsize);
3326                                 sep = memchr(buf, 0, bufsize);
3327                                 if (!sep)
3328                                         break;
3329                                 sepsize = sep - buf + 1;
3330                         }
3332                         /* git-ls-files just delivers a NUL separated
3333                          * list of file names similar to the second half
3334                          * of the git-diff-* output. */
3335                         string_ncopy(file->new.name, buf, sepsize);
3336                         if (!*file->old.name)
3337                                 string_copy(file->old.name, file->new.name);
3338                         bufsize -= sepsize;
3339                         memmove(buf, sep + 1, bufsize);
3340                         file = NULL;
3341                 }
3342         }
3344         if (ferror(pipe)) {
3345 error_out:
3346                 pclose(pipe);
3347                 return FALSE;
3348         }
3350         if (!view->line[view->lines - 1].data)
3351                 add_line_data(view, NULL, LINE_STAT_NONE);
3353         pclose(pipe);
3354         return TRUE;
3357 /* Don't show unmerged entries in the staged section. */
3358 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3359 #define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
3360 #define STATUS_LIST_OTHER_CMD \
3361         "git ls-files -z --others --exclude-per-directory=.gitignore"
3363 #define STATUS_DIFF_INDEX_SHOW_CMD \
3364         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3366 #define STATUS_DIFF_FILES_SHOW_CMD \
3367         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3369 /* First parse staged info using git-diff-index(1), then parse unstaged
3370  * info using git-diff-files(1), and finally untracked files using
3371  * git-ls-files(1). */
3372 static bool
3373 status_open(struct view *view)
3375         struct stat statbuf;
3376         char exclude[SIZEOF_STR];
3377         char cmd[SIZEOF_STR];
3378         unsigned long prev_lineno = view->lineno;
3379         size_t i;
3381         for (i = 0; i < view->lines; i++)
3382                 free(view->line[i].data);
3383         free(view->line);
3384         view->lines = view->line_size = view->lineno = 0;
3385         view->line = NULL;
3387         if (!realloc_lines(view, view->line_size + 6))
3388                 return FALSE;
3390         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3391                 return FALSE;
3393         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3395         if (stat(exclude, &statbuf) >= 0) {
3396                 size_t cmdsize = strlen(cmd);
3398                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3399                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3400                         return FALSE;
3401         }
3403         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3404             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3405             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3406                 return FALSE;
3408         /* If all went well restore the previous line number to stay in
3409          * the context. */
3410         if (prev_lineno < view->lines)
3411                 view->lineno = prev_lineno;
3412         else
3413                 view->lineno = view->lines - 1;
3415         return TRUE;
3418 static bool
3419 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3421         struct status *status = line->data;
3422         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3424         wmove(view->win, lineno, 0);
3426         if (selected) {
3427                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3428                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3429                 tilde_attr = -1;
3431         } else if (!status && line->type != LINE_STAT_NONE) {
3432                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3433                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3435         } else {
3436                 wattrset(view->win, get_line_attr(line->type));
3437         }
3439         if (!status) {
3440                 char *text;
3442                 switch (line->type) {
3443                 case LINE_STAT_STAGED:
3444                         text = "Changes to be committed:";
3445                         break;
3447                 case LINE_STAT_UNSTAGED:
3448                         text = "Changed but not updated:";
3449                         break;
3451                 case LINE_STAT_UNTRACKED:
3452                         text = "Untracked files:";
3453                         break;
3455                 case LINE_STAT_NONE:
3456                         text = "    (no files)";
3457                         break;
3459                 default:
3460                         return FALSE;
3461                 }
3463                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3464                 return TRUE;
3465         }
3467         waddch(view->win, status->status);
3468         if (!selected)
3469                 wattrset(view->win, A_NORMAL);
3470         wmove(view->win, lineno, 4);
3471         if (view->width < 5)
3472                 return TRUE;
3474         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3475         return TRUE;
3478 static enum request
3479 status_enter(struct view *view, struct line *line)
3481         struct status *status = line->data;
3482         char oldpath[SIZEOF_STR] = "";
3483         char newpath[SIZEOF_STR] = "";
3484         char *info;
3485         size_t cmdsize = 0;
3487         if (line->type == LINE_STAT_NONE ||
3488             (!status && line[1].type == LINE_STAT_NONE)) {
3489                 report("No file to diff");
3490                 return REQ_NONE;
3491         }
3493         if (status) {
3494                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3495                         return REQ_QUIT;
3496                 /* Diffs for unmerged entries are empty when pasing the
3497                  * new path, so leave it empty. */
3498                 if (status->status != 'U' &&
3499                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3500                         return REQ_QUIT;
3501         }
3503         if (opt_cdup[0] &&
3504             line->type != LINE_STAT_UNTRACKED &&
3505             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3506                 return REQ_QUIT;
3508         switch (line->type) {
3509         case LINE_STAT_STAGED:
3510                 if (!string_format_from(opt_cmd, &cmdsize,
3511                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3512                         return REQ_QUIT;
3513                 if (status)
3514                         info = "Staged changes to %s";
3515                 else
3516                         info = "Staged changes";
3517                 break;
3519         case LINE_STAT_UNSTAGED:
3520                 if (!string_format_from(opt_cmd, &cmdsize,
3521                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3522                         return REQ_QUIT;
3523                 if (status)
3524                         info = "Unstaged changes to %s";
3525                 else
3526                         info = "Unstaged changes";
3527                 break;
3529         case LINE_STAT_UNTRACKED:
3530                 if (opt_pipe)
3531                         return REQ_QUIT;
3534                 if (!status) {
3535                         report("No file to show");
3536                         return REQ_NONE;
3537                 }
3539                 opt_pipe = fopen(status->new.name, "r");
3540                 info = "Untracked file %s";
3541                 break;
3543         default:
3544                 die("line type %d not handled in switch", line->type);
3545         }
3547         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3548         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3549                 if (status) {
3550                         stage_status = *status;
3551                 } else {
3552                         memset(&stage_status, 0, sizeof(stage_status));
3553                 }
3555                 stage_line_type = line->type;
3556                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3557         }
3559         return REQ_NONE;
3563 static bool
3564 status_update_file(struct view *view, struct status *status, enum line_type type)
3566         char cmd[SIZEOF_STR];
3567         char buf[SIZEOF_STR];
3568         size_t cmdsize = 0;
3569         size_t bufsize = 0;
3570         size_t written = 0;
3571         FILE *pipe;
3573         if (opt_cdup[0] &&
3574             type != LINE_STAT_UNTRACKED &&
3575             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3576                 return FALSE;
3578         switch (type) {
3579         case LINE_STAT_STAGED:
3580                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3581                                         status->old.mode,
3582                                         status->old.rev,
3583                                         status->old.name, 0))
3584                         return FALSE;
3586                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3587                 break;
3589         case LINE_STAT_UNSTAGED:
3590         case LINE_STAT_UNTRACKED:
3591                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3592                         return FALSE;
3594                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3595                 break;
3597         default:
3598                 die("line type %d not handled in switch", type);
3599         }
3601         pipe = popen(cmd, "w");
3602         if (!pipe)
3603                 return FALSE;
3605         while (!ferror(pipe) && written < bufsize) {
3606                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3607         }
3609         pclose(pipe);
3611         if (written != bufsize)
3612                 return FALSE;
3614         return TRUE;
3617 static void
3618 status_update(struct view *view)
3620         struct line *line = &view->line[view->lineno];
3622         assert(view->lines);
3624         if (!line->data) {
3625                 while (++line < view->line + view->lines && line->data) {
3626                         if (!status_update_file(view, line->data, line->type))
3627                                 report("Failed to update file status");
3628                 }
3630                 if (!line[-1].data) {
3631                         report("Nothing to update");
3632                         return;
3633                 }
3635         } else if (!status_update_file(view, line->data, line->type)) {
3636                 report("Failed to update file status");
3637         }
3640 static enum request
3641 status_request(struct view *view, enum request request, struct line *line)
3643         struct status *status = line->data;
3645         switch (request) {
3646         case REQ_STATUS_UPDATE:
3647                 status_update(view);
3648                 break;
3650         case REQ_STATUS_MERGE:
3651                 if (!status || status->status != 'U') {
3652                         report("Merging only possible for files with unmerged status ('U').");
3653                         return REQ_NONE;
3654                 }
3655                 open_mergetool(status->new.name);
3656                 break;
3658         case REQ_EDIT:
3659                 if (!status)
3660                         return request;
3662                 open_editor(status->status != '?', status->new.name);
3663                 break;
3665         case REQ_ENTER:
3666                 /* After returning the status view has been split to
3667                  * show the stage view. No further reloading is
3668                  * necessary. */
3669                 status_enter(view, line);
3670                 return REQ_NONE;
3672         case REQ_REFRESH:
3673                 /* Simply reload the view. */
3674                 break;
3676         default:
3677                 return request;
3678         }
3680         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3682         return REQ_NONE;
3685 static void
3686 status_select(struct view *view, struct line *line)
3688         struct status *status = line->data;
3689         char file[SIZEOF_STR] = "all files";
3690         char *text;
3691         char *key;
3693         if (status && !string_format(file, "'%s'", status->new.name))
3694                 return;
3696         if (!status && line[1].type == LINE_STAT_NONE)
3697                 line++;
3699         switch (line->type) {
3700         case LINE_STAT_STAGED:
3701                 text = "Press %s to unstage %s for commit";
3702                 break;
3704         case LINE_STAT_UNSTAGED:
3705                 text = "Press %s to stage %s for commit";
3706                 break;
3708         case LINE_STAT_UNTRACKED:
3709                 text = "Press %s to stage %s for addition";
3710                 break;
3712         case LINE_STAT_NONE:
3713                 text = "Nothing to update";
3714                 break;
3716         default:
3717                 die("line type %d not handled in switch", line->type);
3718         }
3720         if (status && status->status == 'U') {
3721                 text = "Press %s to resolve conflict in %s";
3722                 key = get_key(REQ_STATUS_MERGE);
3724         } else {
3725                 key = get_key(REQ_STATUS_UPDATE);
3726         }
3728         string_format(view->ref, text, key, file);
3731 static bool
3732 status_grep(struct view *view, struct line *line)
3734         struct status *status = line->data;
3735         enum { S_STATUS, S_NAME, S_END } state;
3736         char buf[2] = "?";
3737         regmatch_t pmatch;
3739         if (!status)
3740                 return FALSE;
3742         for (state = S_STATUS; state < S_END; state++) {
3743                 char *text;
3745                 switch (state) {
3746                 case S_NAME:    text = status->new.name;        break;
3747                 case S_STATUS:
3748                         buf[0] = status->status;
3749                         text = buf;
3750                         break;
3752                 default:
3753                         return FALSE;
3754                 }
3756                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3757                         return TRUE;
3758         }
3760         return FALSE;
3763 static struct view_ops status_ops = {
3764         "file",
3765         status_open,
3766         NULL,
3767         status_draw,
3768         status_request,
3769         status_grep,
3770         status_select,
3771 };
3774 static bool
3775 stage_diff_line(FILE *pipe, struct line *line)
3777         char *buf = line->data;
3778         size_t bufsize = strlen(buf);
3779         size_t written = 0;
3781         while (!ferror(pipe) && written < bufsize) {
3782                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3783         }
3785         fputc('\n', pipe);
3787         return written == bufsize;
3790 static struct line *
3791 stage_diff_hdr(struct view *view, struct line *line)
3793         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3794         struct line *diff_hdr;
3796         if (line->type == LINE_DIFF_CHUNK)
3797                 diff_hdr = line - 1;
3798         else
3799                 diff_hdr = view->line + 1;
3801         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3802                 if (diff_hdr->type == LINE_DIFF_HEADER)
3803                         return diff_hdr;
3805                 diff_hdr += diff_hdr_dir;
3806         }
3808         return NULL;
3811 static bool
3812 stage_update_chunk(struct view *view, struct line *line)
3814         char cmd[SIZEOF_STR];
3815         size_t cmdsize = 0;
3816         struct line *diff_hdr, *diff_chunk, *diff_end;
3817         FILE *pipe;
3819         diff_hdr = stage_diff_hdr(view, line);
3820         if (!diff_hdr)
3821                 return FALSE;
3823         if (opt_cdup[0] &&
3824             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3825                 return FALSE;
3827         if (!string_format_from(cmd, &cmdsize,
3828                                 "git apply --cached %s - && "
3829                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3830                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3831                 return FALSE;
3833         pipe = popen(cmd, "w");
3834         if (!pipe)
3835                 return FALSE;
3837         diff_end = view->line + view->lines;
3838         if (line->type != LINE_DIFF_CHUNK) {
3839                 diff_chunk = diff_hdr;
3841         } else {
3842                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3843                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3844                             diff_chunk->type == LINE_DIFF_HEADER)
3845                                 diff_end = diff_chunk;
3847                 diff_chunk = line;
3849                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3850                         switch (diff_hdr->type) {
3851                         case LINE_DIFF_HEADER:
3852                         case LINE_DIFF_INDEX:
3853                         case LINE_DIFF_ADD:
3854                         case LINE_DIFF_DEL:
3855                                 break;
3857                         default:
3858                                 diff_hdr++;
3859                                 continue;
3860                         }
3862                         if (!stage_diff_line(pipe, diff_hdr++)) {
3863                                 pclose(pipe);
3864                                 return FALSE;
3865                         }
3866                 }
3867         }
3869         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3870                 diff_chunk++;
3872         pclose(pipe);
3874         if (diff_chunk != diff_end)
3875                 return FALSE;
3877         return TRUE;
3880 static void
3881 stage_update(struct view *view, struct line *line)
3883         if (stage_line_type != LINE_STAT_UNTRACKED &&
3884             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3885                 if (!stage_update_chunk(view, line)) {
3886                         report("Failed to apply chunk");
3887                         return;
3888                 }
3890         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3891                 report("Failed to update file");
3892                 return;
3893         }
3895         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3897         view = VIEW(REQ_VIEW_STATUS);
3898         if (view_is_displayed(view))
3899                 status_enter(view, &view->line[view->lineno]);
3902 static enum request
3903 stage_request(struct view *view, enum request request, struct line *line)
3905         switch (request) {
3906         case REQ_STATUS_UPDATE:
3907                 stage_update(view, line);
3908                 break;
3910         case REQ_EDIT:
3911                 if (!stage_status.new.name[0])
3912                         return request;
3914                 open_editor(stage_status.status != '?', stage_status.new.name);
3915                 break;
3917         case REQ_ENTER:
3918                 pager_request(view, request, line);
3919                 break;
3921         default:
3922                 return request;
3923         }
3925         return REQ_NONE;
3928 static struct view_ops stage_ops = {
3929         "line",
3930         NULL,
3931         pager_read,
3932         pager_draw,
3933         stage_request,
3934         pager_grep,
3935         pager_select,
3936 };
3939 /*
3940  * Revision graph
3941  */
3943 struct commit {
3944         char id[SIZEOF_REV];            /* SHA1 ID. */
3945         char title[128];                /* First line of the commit message. */
3946         char author[75];                /* Author of the commit. */
3947         struct tm time;                 /* Date from the author ident. */
3948         struct ref **refs;              /* Repository references. */
3949         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3950         size_t graph_size;              /* The width of the graph array. */
3951 };
3953 /* Size of rev graph with no  "padding" columns */
3954 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3956 struct rev_graph {
3957         struct rev_graph *prev, *next, *parents;
3958         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3959         size_t size;
3960         struct commit *commit;
3961         size_t pos;
3962         unsigned int boundary:1;
3963 };
3965 /* Parents of the commit being visualized. */
3966 static struct rev_graph graph_parents[4];
3968 /* The current stack of revisions on the graph. */
3969 static struct rev_graph graph_stacks[4] = {
3970         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3971         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3972         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3973         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3974 };
3976 static inline bool
3977 graph_parent_is_merge(struct rev_graph *graph)
3979         return graph->parents->size > 1;
3982 static inline void
3983 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3985         struct commit *commit = graph->commit;
3987         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3988                 commit->graph[commit->graph_size++] = symbol;
3991 static void
3992 done_rev_graph(struct rev_graph *graph)
3994         if (graph_parent_is_merge(graph) &&
3995             graph->pos < graph->size - 1 &&
3996             graph->next->size == graph->size + graph->parents->size - 1) {
3997                 size_t i = graph->pos + graph->parents->size - 1;
3999                 graph->commit->graph_size = i * 2;
4000                 while (i < graph->next->size - 1) {
4001                         append_to_rev_graph(graph, ' ');
4002                         append_to_rev_graph(graph, '\\');
4003                         i++;
4004                 }
4005         }
4007         graph->size = graph->pos = 0;
4008         graph->commit = NULL;
4009         memset(graph->parents, 0, sizeof(*graph->parents));
4012 static void
4013 push_rev_graph(struct rev_graph *graph, char *parent)
4015         int i;
4017         /* "Collapse" duplicate parents lines.
4018          *
4019          * FIXME: This needs to also update update the drawn graph but
4020          * for now it just serves as a method for pruning graph lines. */
4021         for (i = 0; i < graph->size; i++)
4022                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4023                         return;
4025         if (graph->size < SIZEOF_REVITEMS) {
4026                 string_copy_rev(graph->rev[graph->size++], parent);
4027         }
4030 static chtype
4031 get_rev_graph_symbol(struct rev_graph *graph)
4033         chtype symbol;
4035         if (graph->boundary)
4036                 symbol = REVGRAPH_BOUND;
4037         else if (graph->parents->size == 0)
4038                 symbol = REVGRAPH_INIT;
4039         else if (graph_parent_is_merge(graph))
4040                 symbol = REVGRAPH_MERGE;
4041         else if (graph->pos >= graph->size)
4042                 symbol = REVGRAPH_BRANCH;
4043         else
4044                 symbol = REVGRAPH_COMMIT;
4046         return symbol;
4049 static void
4050 draw_rev_graph(struct rev_graph *graph)
4052         struct rev_filler {
4053                 chtype separator, line;
4054         };
4055         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4056         static struct rev_filler fillers[] = {
4057                 { ' ',  REVGRAPH_LINE },
4058                 { '`',  '.' },
4059                 { '\'', ' ' },
4060                 { '/',  ' ' },
4061         };
4062         chtype symbol = get_rev_graph_symbol(graph);
4063         struct rev_filler *filler;
4064         size_t i;
4066         filler = &fillers[DEFAULT];
4068         for (i = 0; i < graph->pos; i++) {
4069                 append_to_rev_graph(graph, filler->line);
4070                 if (graph_parent_is_merge(graph->prev) &&
4071                     graph->prev->pos == i)
4072                         filler = &fillers[RSHARP];
4074                 append_to_rev_graph(graph, filler->separator);
4075         }
4077         /* Place the symbol for this revision. */
4078         append_to_rev_graph(graph, symbol);
4080         if (graph->prev->size > graph->size)
4081                 filler = &fillers[RDIAG];
4082         else
4083                 filler = &fillers[DEFAULT];
4085         i++;
4087         for (; i < graph->size; i++) {
4088                 append_to_rev_graph(graph, filler->separator);
4089                 append_to_rev_graph(graph, filler->line);
4090                 if (graph_parent_is_merge(graph->prev) &&
4091                     i < graph->prev->pos + graph->parents->size)
4092                         filler = &fillers[RSHARP];
4093                 if (graph->prev->size > graph->size)
4094                         filler = &fillers[LDIAG];
4095         }
4097         if (graph->prev->size > graph->size) {
4098                 append_to_rev_graph(graph, filler->separator);
4099                 if (filler->line != ' ')
4100                         append_to_rev_graph(graph, filler->line);
4101         }
4104 /* Prepare the next rev graph */
4105 static void
4106 prepare_rev_graph(struct rev_graph *graph)
4108         size_t i;
4110         /* First, traverse all lines of revisions up to the active one. */
4111         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4112                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4113                         break;
4115                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4116         }
4118         /* Interleave the new revision parent(s). */
4119         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4120                 push_rev_graph(graph->next, graph->parents->rev[i]);
4122         /* Lastly, put any remaining revisions. */
4123         for (i = graph->pos + 1; i < graph->size; i++)
4124                 push_rev_graph(graph->next, graph->rev[i]);
4127 static void
4128 update_rev_graph(struct rev_graph *graph)
4130         /* If this is the finalizing update ... */
4131         if (graph->commit)
4132                 prepare_rev_graph(graph);
4134         /* Graph visualization needs a one rev look-ahead,
4135          * so the first update doesn't visualize anything. */
4136         if (!graph->prev->commit)
4137                 return;
4139         draw_rev_graph(graph->prev);
4140         done_rev_graph(graph->prev->prev);
4144 /*
4145  * Main view backend
4146  */
4148 static bool
4149 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4151         char buf[DATE_COLS + 1];
4152         struct commit *commit = line->data;
4153         enum line_type type;
4154         int col = 0;
4155         size_t timelen;
4156         int tilde_attr;
4157         int space;
4159         if (!*commit->author)
4160                 return FALSE;
4162         space = view->width;
4163         wmove(view->win, lineno, col);
4165         if (selected) {
4166                 type = LINE_CURSOR;
4167                 wattrset(view->win, get_line_attr(type));
4168                 wchgat(view->win, -1, 0, type, NULL);
4169                 tilde_attr = -1;
4170         } else {
4171                 type = LINE_MAIN_COMMIT;
4172                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4173                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4174         }
4176         {
4177                 int n;
4179                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4180                 n = draw_text(
4181                         view, buf, view->width - col, col, FALSE, tilde_attr);
4182                 draw_text(
4183                         view, " ", view->width - col - n, col + n, FALSE,
4184                         tilde_attr);
4186                 col += DATE_COLS;
4187                 wmove(view->win, lineno, col);
4188                 if (col >= view->width)
4189                         return TRUE;
4190         }
4191         if (type != LINE_CURSOR)
4192                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4194         {
4195                 int max_len;
4197                 max_len = view->width - col;
4198                 if (max_len > AUTHOR_COLS - 1)
4199                         max_len = AUTHOR_COLS - 1;
4200                 draw_text(
4201                         view, commit->author, max_len, col, TRUE, tilde_attr);
4202                 col += AUTHOR_COLS;
4203                 if (col >= view->width)
4204                         return TRUE;
4205         }
4207         if (opt_rev_graph && commit->graph_size) {
4208                 size_t graph_size = view->width - col;
4209                 size_t i;
4211                 if (type != LINE_CURSOR)
4212                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4213                 wmove(view->win, lineno, col);
4214                 if (graph_size > commit->graph_size)
4215                         graph_size = commit->graph_size;
4216                 /* Using waddch() instead of waddnstr() ensures that
4217                  * they'll be rendered correctly for the cursor line. */
4218                 for (i = 0; i < graph_size; i++)
4219                         waddch(view->win, commit->graph[i]);
4221                 col += commit->graph_size + 1;
4222                 if (col >= view->width)
4223                         return TRUE;
4224                 waddch(view->win, ' ');
4225         }
4226         if (type != LINE_CURSOR)
4227                 wattrset(view->win, A_NORMAL);
4229         wmove(view->win, lineno, col);
4231         if (commit->refs) {
4232                 size_t i = 0;
4234                 do {
4235                         if (type == LINE_CURSOR)
4236                                 ;
4237                         else if (commit->refs[i]->tag)
4238                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4239                         else if (commit->refs[i]->remote)
4240                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4241                         else
4242                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4244                         col += draw_text(
4245                                 view, "[", view->width - col, col, TRUE,
4246                                 tilde_attr);
4247                         col += draw_text(
4248                                 view, commit->refs[i]->name, view->width - col,
4249                                 col, TRUE, tilde_attr);
4250                         col += draw_text(
4251                                 view, "]", view->width - col, col, TRUE,
4252                                 tilde_attr);
4253                         if (type != LINE_CURSOR)
4254                                 wattrset(view->win, A_NORMAL);
4255                         col += draw_text(
4256                                 view, " ", view->width - col, col, TRUE,
4257                                 tilde_attr);
4258                         if (col >= view->width)
4259                                 return TRUE;
4260                 } while (commit->refs[i++]->next);
4261         }
4263         if (type != LINE_CURSOR)
4264                 wattrset(view->win, get_line_attr(type));
4266         col += draw_text(
4267                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4269         return TRUE;
4272 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4273 static bool
4274 main_read(struct view *view, char *line)
4276         static struct rev_graph *graph = graph_stacks;
4277         enum line_type type;
4278         struct commit *commit;
4280         if (!line) {
4281                 update_rev_graph(graph);
4282                 return TRUE;
4283         }
4285         type = get_line_type(line);
4286         if (type == LINE_COMMIT) {
4287                 commit = calloc(1, sizeof(struct commit));
4288                 if (!commit)
4289                         return FALSE;
4291                 line += STRING_SIZE("commit ");
4292                 if (*line == '-') {
4293                         graph->boundary = 1;
4294                         line++;
4295                 }
4297                 string_copy_rev(commit->id, line);
4298                 commit->refs = get_refs(commit->id);
4299                 graph->commit = commit;
4300                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4301                 return TRUE;
4302         }
4304         if (!view->lines)
4305                 return TRUE;
4306         commit = view->line[view->lines - 1].data;
4308         switch (type) {
4309         case LINE_PARENT:
4310                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4311                 break;
4313         case LINE_AUTHOR:
4314         {
4315                 /* Parse author lines where the name may be empty:
4316                  *      author  <email@address.tld> 1138474660 +0100
4317                  */
4318                 char *ident = line + STRING_SIZE("author ");
4319                 char *nameend = strchr(ident, '<');
4320                 char *emailend = strchr(ident, '>');
4322                 if (!nameend || !emailend)
4323                         break;
4325                 update_rev_graph(graph);
4326                 graph = graph->next;
4328                 *nameend = *emailend = 0;
4329                 ident = chomp_string(ident);
4330                 if (!*ident) {
4331                         ident = chomp_string(nameend + 1);
4332                         if (!*ident)
4333                                 ident = "Unknown";
4334                 }
4336                 string_ncopy(commit->author, ident, strlen(ident));
4338                 /* Parse epoch and timezone */
4339                 if (emailend[1] == ' ') {
4340                         char *secs = emailend + 2;
4341                         char *zone = strchr(secs, ' ');
4342                         time_t time = (time_t) atol(secs);
4344                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4345                                 long tz;
4347                                 zone++;
4348                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4349                                 tz += ('0' - zone[2]) * 60 * 60;
4350                                 tz += ('0' - zone[3]) * 60;
4351                                 tz += ('0' - zone[4]) * 60;
4353                                 if (zone[0] == '-')
4354                                         tz = -tz;
4356                                 time -= tz;
4357                         }
4359                         gmtime_r(&time, &commit->time);
4360                 }
4361                 break;
4362         }
4363         default:
4364                 /* Fill in the commit title if it has not already been set. */
4365                 if (commit->title[0])
4366                         break;
4368                 /* Require titles to start with a non-space character at the
4369                  * offset used by git log. */
4370                 if (strncmp(line, "    ", 4))
4371                         break;
4372                 line += 4;
4373                 /* Well, if the title starts with a whitespace character,
4374                  * try to be forgiving.  Otherwise we end up with no title. */
4375                 while (isspace(*line))
4376                         line++;
4377                 if (*line == '\0')
4378                         break;
4379                 /* FIXME: More graceful handling of titles; append "..." to
4380                  * shortened titles, etc. */
4382                 string_ncopy(commit->title, line, strlen(line));
4383         }
4385         return TRUE;
4388 static enum request
4389 main_request(struct view *view, enum request request, struct line *line)
4391         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4393         if (request == REQ_ENTER)
4394                 open_view(view, REQ_VIEW_DIFF, flags);
4395         else
4396                 return request;
4398         return REQ_NONE;
4401 static bool
4402 main_grep(struct view *view, struct line *line)
4404         struct commit *commit = line->data;
4405         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4406         char buf[DATE_COLS + 1];
4407         regmatch_t pmatch;
4409         for (state = S_TITLE; state < S_END; state++) {
4410                 char *text;
4412                 switch (state) {
4413                 case S_TITLE:   text = commit->title;   break;
4414                 case S_AUTHOR:  text = commit->author;  break;
4415                 case S_DATE:
4416                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4417                                 continue;
4418                         text = buf;
4419                         break;
4421                 default:
4422                         return FALSE;
4423                 }
4425                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4426                         return TRUE;
4427         }
4429         return FALSE;
4432 static void
4433 main_select(struct view *view, struct line *line)
4435         struct commit *commit = line->data;
4437         string_copy_rev(view->ref, commit->id);
4438         string_copy_rev(ref_commit, view->ref);
4441 static struct view_ops main_ops = {
4442         "commit",
4443         NULL,
4444         main_read,
4445         main_draw,
4446         main_request,
4447         main_grep,
4448         main_select,
4449 };
4452 /*
4453  * Unicode / UTF-8 handling
4454  *
4455  * NOTE: Much of the following code for dealing with unicode is derived from
4456  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4457  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4458  */
4460 /* I've (over)annotated a lot of code snippets because I am not entirely
4461  * confident that the approach taken by this small UTF-8 interface is correct.
4462  * --jonas */
4464 static inline int
4465 unicode_width(unsigned long c)
4467         if (c >= 0x1100 &&
4468            (c <= 0x115f                         /* Hangul Jamo */
4469             || c == 0x2329
4470             || c == 0x232a
4471             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4472                                                 /* CJK ... Yi */
4473             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4474             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4475             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4476             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4477             || (c >= 0xffe0  && c <= 0xffe6)
4478             || (c >= 0x20000 && c <= 0x2fffd)
4479             || (c >= 0x30000 && c <= 0x3fffd)))
4480                 return 2;
4482         if (c == '\t')
4483                 return opt_tab_size;
4485         return 1;
4488 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4489  * Illegal bytes are set one. */
4490 static const unsigned char utf8_bytes[256] = {
4491         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,
4492         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,
4493         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,
4494         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,
4495         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,
4496         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,
4497         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,
4498         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,
4499 };
4501 /* Decode UTF-8 multi-byte representation into a unicode character. */
4502 static inline unsigned long
4503 utf8_to_unicode(const char *string, size_t length)
4505         unsigned long unicode;
4507         switch (length) {
4508         case 1:
4509                 unicode  =   string[0];
4510                 break;
4511         case 2:
4512                 unicode  =  (string[0] & 0x1f) << 6;
4513                 unicode +=  (string[1] & 0x3f);
4514                 break;
4515         case 3:
4516                 unicode  =  (string[0] & 0x0f) << 12;
4517                 unicode += ((string[1] & 0x3f) << 6);
4518                 unicode +=  (string[2] & 0x3f);
4519                 break;
4520         case 4:
4521                 unicode  =  (string[0] & 0x0f) << 18;
4522                 unicode += ((string[1] & 0x3f) << 12);
4523                 unicode += ((string[2] & 0x3f) << 6);
4524                 unicode +=  (string[3] & 0x3f);
4525                 break;
4526         case 5:
4527                 unicode  =  (string[0] & 0x0f) << 24;
4528                 unicode += ((string[1] & 0x3f) << 18);
4529                 unicode += ((string[2] & 0x3f) << 12);
4530                 unicode += ((string[3] & 0x3f) << 6);
4531                 unicode +=  (string[4] & 0x3f);
4532                 break;
4533         case 6:
4534                 unicode  =  (string[0] & 0x01) << 30;
4535                 unicode += ((string[1] & 0x3f) << 24);
4536                 unicode += ((string[2] & 0x3f) << 18);
4537                 unicode += ((string[3] & 0x3f) << 12);
4538                 unicode += ((string[4] & 0x3f) << 6);
4539                 unicode +=  (string[5] & 0x3f);
4540                 break;
4541         default:
4542                 die("Invalid unicode length");
4543         }
4545         /* Invalid characters could return the special 0xfffd value but NUL
4546          * should be just as good. */
4547         return unicode > 0xffff ? 0 : unicode;
4550 /* Calculates how much of string can be shown within the given maximum width
4551  * and sets trimmed parameter to non-zero value if all of string could not be
4552  * shown. If the reserve flag is TRUE, it will reserve at least one
4553  * trailing character, which can be useful when drawing a delimiter.
4554  *
4555  * Returns the number of bytes to output from string to satisfy max_width. */
4556 static size_t
4557 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4559         const char *start = string;
4560         const char *end = strchr(string, '\0');
4561         unsigned char last_bytes = 0;
4562         size_t width = 0;
4564         *trimmed = 0;
4566         while (string < end) {
4567                 int c = *(unsigned char *) string;
4568                 unsigned char bytes = utf8_bytes[c];
4569                 size_t ucwidth;
4570                 unsigned long unicode;
4572                 if (string + bytes > end)
4573                         break;
4575                 /* Change representation to figure out whether
4576                  * it is a single- or double-width character. */
4578                 unicode = utf8_to_unicode(string, bytes);
4579                 /* FIXME: Graceful handling of invalid unicode character. */
4580                 if (!unicode)
4581                         break;
4583                 ucwidth = unicode_width(unicode);
4584                 width  += ucwidth;
4585                 if (width > max_width) {
4586                         *trimmed = 1;
4587                         if (reserve && width - ucwidth == max_width) {
4588                                 string -= last_bytes;
4589                         }
4590                         break;
4591                 }
4593                 string  += bytes;
4594                 last_bytes = bytes;
4595         }
4597         return string - start;
4601 /*
4602  * Status management
4603  */
4605 /* Whether or not the curses interface has been initialized. */
4606 static bool cursed = FALSE;
4608 /* The status window is used for polling keystrokes. */
4609 static WINDOW *status_win;
4611 static bool status_empty = TRUE;
4613 /* Update status and title window. */
4614 static void
4615 report(const char *msg, ...)
4617         struct view *view = display[current_view];
4619         if (input_mode)
4620                 return;
4622         if (!view) {
4623                 char buf[SIZEOF_STR];
4624                 va_list args;
4626                 va_start(args, msg);
4627                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4628                         buf[sizeof(buf) - 1] = 0;
4629                         buf[sizeof(buf) - 2] = '.';
4630                         buf[sizeof(buf) - 3] = '.';
4631                         buf[sizeof(buf) - 4] = '.';
4632                 }
4633                 va_end(args);
4634                 die("%s", buf);
4635         }
4637         if (!status_empty || *msg) {
4638                 va_list args;
4640                 va_start(args, msg);
4642                 wmove(status_win, 0, 0);
4643                 if (*msg) {
4644                         vwprintw(status_win, msg, args);
4645                         status_empty = FALSE;
4646                 } else {
4647                         status_empty = TRUE;
4648                 }
4649                 wclrtoeol(status_win);
4650                 wrefresh(status_win);
4652                 va_end(args);
4653         }
4655         update_view_title(view);
4656         update_display_cursor(view);
4659 /* Controls when nodelay should be in effect when polling user input. */
4660 static void
4661 set_nonblocking_input(bool loading)
4663         static unsigned int loading_views;
4665         if ((loading == FALSE && loading_views-- == 1) ||
4666             (loading == TRUE  && loading_views++ == 0))
4667                 nodelay(status_win, loading);
4670 static void
4671 init_display(void)
4673         int x, y;
4675         /* Initialize the curses library */
4676         if (isatty(STDIN_FILENO)) {
4677                 cursed = !!initscr();
4678         } else {
4679                 /* Leave stdin and stdout alone when acting as a pager. */
4680                 FILE *io = fopen("/dev/tty", "r+");
4682                 if (!io)
4683                         die("Failed to open /dev/tty");
4684                 cursed = !!newterm(NULL, io, io);
4685         }
4687         if (!cursed)
4688                 die("Failed to initialize curses");
4690         nonl();         /* Tell curses not to do NL->CR/NL on output */
4691         cbreak();       /* Take input chars one at a time, no wait for \n */
4692         noecho();       /* Don't echo input */
4693         leaveok(stdscr, TRUE);
4695         if (has_colors())
4696                 init_colors();
4698         getmaxyx(stdscr, y, x);
4699         status_win = newwin(1, 0, y - 1, 0);
4700         if (!status_win)
4701                 die("Failed to create status window");
4703         /* Enable keyboard mapping */
4704         keypad(status_win, TRUE);
4705         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4708 static char *
4709 read_prompt(const char *prompt)
4711         enum { READING, STOP, CANCEL } status = READING;
4712         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4713         int pos = 0;
4715         while (status == READING) {
4716                 struct view *view;
4717                 int i, key;
4719                 input_mode = TRUE;
4721                 foreach_view (view, i)
4722                         update_view(view);
4724                 input_mode = FALSE;
4726                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4727                 wclrtoeol(status_win);
4729                 /* Refresh, accept single keystroke of input */
4730                 key = wgetch(status_win);
4731                 switch (key) {
4732                 case KEY_RETURN:
4733                 case KEY_ENTER:
4734                 case '\n':
4735                         status = pos ? STOP : CANCEL;
4736                         break;
4738                 case KEY_BACKSPACE:
4739                         if (pos > 0)
4740                                 pos--;
4741                         else
4742                                 status = CANCEL;
4743                         break;
4745                 case KEY_ESC:
4746                         status = CANCEL;
4747                         break;
4749                 case ERR:
4750                         break;
4752                 default:
4753                         if (pos >= sizeof(buf)) {
4754                                 report("Input string too long");
4755                                 return NULL;
4756                         }
4758                         if (isprint(key))
4759                                 buf[pos++] = (char) key;
4760                 }
4761         }
4763         /* Clear the status window */
4764         status_empty = FALSE;
4765         report("");
4767         if (status == CANCEL)
4768                 return NULL;
4770         buf[pos++] = 0;
4772         return buf;
4775 /*
4776  * Repository references
4777  */
4779 static struct ref *refs;
4780 static size_t refs_size;
4782 /* Id <-> ref store */
4783 static struct ref ***id_refs;
4784 static size_t id_refs_size;
4786 static struct ref **
4787 get_refs(char *id)
4789         struct ref ***tmp_id_refs;
4790         struct ref **ref_list = NULL;
4791         size_t ref_list_size = 0;
4792         size_t i;
4794         for (i = 0; i < id_refs_size; i++)
4795                 if (!strcmp(id, id_refs[i][0]->id))
4796                         return id_refs[i];
4798         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4799         if (!tmp_id_refs)
4800                 return NULL;
4802         id_refs = tmp_id_refs;
4804         for (i = 0; i < refs_size; i++) {
4805                 struct ref **tmp;
4807                 if (strcmp(id, refs[i].id))
4808                         continue;
4810                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4811                 if (!tmp) {
4812                         if (ref_list)
4813                                 free(ref_list);
4814                         return NULL;
4815                 }
4817                 ref_list = tmp;
4818                 if (ref_list_size > 0)
4819                         ref_list[ref_list_size - 1]->next = 1;
4820                 ref_list[ref_list_size] = &refs[i];
4822                 /* XXX: The properties of the commit chains ensures that we can
4823                  * safely modify the shared ref. The repo references will
4824                  * always be similar for the same id. */
4825                 ref_list[ref_list_size]->next = 0;
4826                 ref_list_size++;
4827         }
4829         if (ref_list)
4830                 id_refs[id_refs_size++] = ref_list;
4832         return ref_list;
4835 static int
4836 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4838         struct ref *ref;
4839         bool tag = FALSE;
4840         bool remote = FALSE;
4842         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4843                 /* Commits referenced by tags has "^{}" appended. */
4844                 if (name[namelen - 1] != '}')
4845                         return OK;
4847                 while (namelen > 0 && name[namelen] != '^')
4848                         namelen--;
4850                 tag = TRUE;
4851                 namelen -= STRING_SIZE("refs/tags/");
4852                 name    += STRING_SIZE("refs/tags/");
4854         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4855                 remote = TRUE;
4856                 namelen -= STRING_SIZE("refs/remotes/");
4857                 name    += STRING_SIZE("refs/remotes/");
4859         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4860                 namelen -= STRING_SIZE("refs/heads/");
4861                 name    += STRING_SIZE("refs/heads/");
4863         } else if (!strcmp(name, "HEAD")) {
4864                 return OK;
4865         }
4867         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4868         if (!refs)
4869                 return ERR;
4871         ref = &refs[refs_size++];
4872         ref->name = malloc(namelen + 1);
4873         if (!ref->name)
4874                 return ERR;
4876         strncpy(ref->name, name, namelen);
4877         ref->name[namelen] = 0;
4878         ref->tag = tag;
4879         ref->remote = remote;
4880         string_copy_rev(ref->id, id);
4882         return OK;
4885 static int
4886 load_refs(void)
4888         const char *cmd_env = getenv("TIG_LS_REMOTE");
4889         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4891         return read_properties(popen(cmd, "r"), "\t", read_ref);
4894 static int
4895 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4897         if (!strcmp(name, "i18n.commitencoding"))
4898                 string_ncopy(opt_encoding, value, valuelen);
4900         if (!strcmp(name, "core.editor"))
4901                 string_ncopy(opt_editor, value, valuelen);
4903         return OK;
4906 static int
4907 load_repo_config(void)
4909         return read_properties(popen(GIT_CONFIG " --list", "r"),
4910                                "=", read_repo_config_option);
4913 static int
4914 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4916         if (!opt_git_dir[0]) {
4917                 string_ncopy(opt_git_dir, name, namelen);
4919         } else if (opt_is_inside_work_tree == -1) {
4920                 /* This can be 3 different values depending on the
4921                  * version of git being used. If git-rev-parse does not
4922                  * understand --is-inside-work-tree it will simply echo
4923                  * the option else either "true" or "false" is printed.
4924                  * Default to true for the unknown case. */
4925                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4927         } else {
4928                 string_ncopy(opt_cdup, name, namelen);
4929         }
4931         return OK;
4934 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4935  * must be the last one! */
4936 static int
4937 load_repo_info(void)
4939         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4940                                "=", read_repo_info);
4943 static int
4944 read_properties(FILE *pipe, const char *separators,
4945                 int (*read_property)(char *, size_t, char *, size_t))
4947         char buffer[BUFSIZ];
4948         char *name;
4949         int state = OK;
4951         if (!pipe)
4952                 return ERR;
4954         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4955                 char *value;
4956                 size_t namelen;
4957                 size_t valuelen;
4959                 name = chomp_string(name);
4960                 namelen = strcspn(name, separators);
4962                 if (name[namelen]) {
4963                         name[namelen] = 0;
4964                         value = chomp_string(name + namelen + 1);
4965                         valuelen = strlen(value);
4967                 } else {
4968                         value = "";
4969                         valuelen = 0;
4970                 }
4972                 state = read_property(name, namelen, value, valuelen);
4973         }
4975         if (state != ERR && ferror(pipe))
4976                 state = ERR;
4978         pclose(pipe);
4980         return state;
4984 /*
4985  * Main
4986  */
4988 static void __NORETURN
4989 quit(int sig)
4991         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4992         if (cursed)
4993                 endwin();
4994         exit(0);
4997 static void __NORETURN
4998 die(const char *err, ...)
5000         va_list args;
5002         endwin();
5004         va_start(args, err);
5005         fputs("tig: ", stderr);
5006         vfprintf(stderr, err, args);
5007         fputs("\n", stderr);
5008         va_end(args);
5010         exit(1);
5013 static void
5014 warn(const char *msg, ...)
5016         va_list args;
5018         va_start(args, msg);
5019         fputs("tig warning: ", stderr);
5020         vfprintf(stderr, msg, args);
5021         fputs("\n", stderr);
5022         va_end(args);
5025 int
5026 main(int argc, char *argv[])
5028         struct view *view;
5029         enum request request;
5030         size_t i;
5032         signal(SIGINT, quit);
5034         if (setlocale(LC_ALL, "")) {
5035                 char *codeset = nl_langinfo(CODESET);
5037                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5038         }
5040         if (load_repo_info() == ERR)
5041                 die("Failed to load repo info.");
5043         if (load_options() == ERR)
5044                 die("Failed to load user config.");
5046         /* Load the repo config file so options can be overwritten from
5047          * the command line. */
5048         if (load_repo_config() == ERR)
5049                 die("Failed to load repo config.");
5051         if (!parse_options(argc, argv))
5052                 return 0;
5054         /* Require a git repository unless when running in pager mode. */
5055         if (!opt_git_dir[0])
5056                 die("Not a git repository");
5058         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5059                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5060                 if (opt_iconv == ICONV_NONE)
5061                         die("Failed to initialize character set conversion");
5062         }
5064         if (load_refs() == ERR)
5065                 die("Failed to load refs.");
5067         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5068                 view->cmd_env = getenv(view->cmd_env);
5070         request = opt_request;
5072         init_display();
5074         while (view_driver(display[current_view], request)) {
5075                 int key;
5076                 int i;
5078                 foreach_view (view, i)
5079                         update_view(view);
5081                 /* Refresh, accept single keystroke of input */
5082                 key = wgetch(status_win);
5084                 /* wgetch() with nodelay() enabled returns ERR when there's no
5085                  * input. */
5086                 if (key == ERR) {
5087                         request = REQ_NONE;
5088                         continue;
5089                 }
5091                 request = get_keybinding(display[current_view]->keymap, key);
5093                 /* Some low-level request handling. This keeps access to
5094                  * status_win restricted. */
5095                 switch (request) {
5096                 case REQ_PROMPT:
5097                 {
5098                         char *cmd = read_prompt(":");
5100                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5101                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5102                                         opt_request = REQ_VIEW_DIFF;
5103                                 } else {
5104                                         opt_request = REQ_VIEW_PAGER;
5105                                 }
5106                                 break;
5107                         }
5109                         request = REQ_NONE;
5110                         break;
5111                 }
5112                 case REQ_SEARCH:
5113                 case REQ_SEARCH_BACK:
5114                 {
5115                         const char *prompt = request == REQ_SEARCH
5116                                            ? "/" : "?";
5117                         char *search = read_prompt(prompt);
5119                         if (search)
5120                                 string_ncopy(opt_search, search, strlen(search));
5121                         else
5122                                 request = REQ_NONE;
5123                         break;
5124                 }
5125                 case REQ_SCREEN_RESIZE:
5126                 {
5127                         int height, width;
5129                         getmaxyx(stdscr, height, width);
5131                         /* Resize the status view and let the view driver take
5132                          * care of resizing the displayed views. */
5133                         wresize(status_win, 1, width);
5134                         mvwin(status_win, height - 1, 0);
5135                         wrefresh(status_win);
5136                         break;
5137                 }
5138                 default:
5139                         break;
5140                 }
5141         }
5143         quit(0);
5145         return 0;