Code

6cfe40e36b334ef6fdcddf3e76e48243da13c4d7
[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_DATE,       "Toggle date display"), \
358         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
359         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
360         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
361         REQ_(STATUS_UPDATE,     "Update file status"), \
362         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
363         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
364         REQ_(EDIT,              "Open in editor"), \
365         REQ_(NONE,              "Do nothing")
368 /* User action requests. */
369 enum request {
370 #define REQ_GROUP(help)
371 #define REQ_(req, help) REQ_##req
373         /* Offset all requests to avoid conflicts with ncurses getch values. */
374         REQ_OFFSET = KEY_MAX + 1,
375         REQ_INFO
377 #undef  REQ_GROUP
378 #undef  REQ_
379 };
381 struct request_info {
382         enum request request;
383         char *name;
384         int namelen;
385         char *help;
386 };
388 static struct request_info req_info[] = {
389 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
390 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
391         REQ_INFO
392 #undef  REQ_GROUP
393 #undef  REQ_
394 };
396 static enum request
397 get_request(const char *name)
399         int namelen = strlen(name);
400         int i;
402         for (i = 0; i < ARRAY_SIZE(req_info); i++)
403                 if (req_info[i].namelen == namelen &&
404                     !string_enum_compare(req_info[i].name, name, namelen))
405                         return req_info[i].request;
407         return REQ_NONE;
411 /*
412  * Options
413  */
415 static const char usage[] =
416 "tig " TIG_VERSION " (" __DATE__ ")\n"
417 "\n"
418 "Usage: tig        [options] [revs] [--] [paths]\n"
419 "   or: tig show   [options] [revs] [--] [paths]\n"
420 "   or: tig status\n"
421 "   or: tig <      [git command output]\n"
422 "\n"
423 "Options:\n"
424 "  -v, --version   Show version and exit\n"
425 "  -h, --help      Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_date                    = TRUE;
429 static bool opt_author                  = TRUE;
430 static bool opt_line_number             = FALSE;
431 static bool opt_rev_graph               = FALSE;
432 static bool opt_show_refs               = TRUE;
433 static int opt_num_interval             = NUMBER_INTERVAL;
434 static int opt_tab_size                 = TABSIZE;
435 static enum request opt_request         = REQ_VIEW_MAIN;
436 static char opt_cmd[SIZEOF_STR]         = "";
437 static char opt_path[SIZEOF_STR]        = "";
438 static FILE *opt_pipe                   = NULL;
439 static char opt_encoding[20]            = "UTF-8";
440 static bool opt_utf8                    = TRUE;
441 static char opt_codeset[20]             = "UTF-8";
442 static iconv_t opt_iconv                = ICONV_NONE;
443 static char opt_search[SIZEOF_STR]      = "";
444 static char opt_cdup[SIZEOF_STR]        = "";
445 static char opt_git_dir[SIZEOF_STR]     = "";
446 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
447 static char opt_editor[SIZEOF_STR]      = "";
449 enum option_type {
450         OPT_NONE,
451         OPT_INT,
452 };
454 static bool
455 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
457         va_list args;
458         char *value = "";
459         int *number;
461         if (opt[0] != '-')
462                 return FALSE;
464         if (opt[1] == '-') {
465                 int namelen = strlen(name);
467                 opt += 2;
469                 if (strncmp(opt, name, namelen))
470                         return FALSE;
472                 if (opt[namelen] == '=')
473                         value = opt + namelen + 1;
475         } else {
476                 if (!short_name || opt[1] != short_name)
477                         return FALSE;
478                 value = opt + 2;
479         }
481         va_start(args, type);
482         if (type == OPT_INT) {
483                 number = va_arg(args, int *);
484                 if (isdigit(*value))
485                         *number = atoi(value);
486         }
487         va_end(args);
489         return TRUE;
492 /* Returns the index of log or diff command or -1 to exit. */
493 static bool
494 parse_options(int argc, char *argv[])
496         char *altargv[1024];
497         int altargc = 0;
498         char *subcommand = NULL;
499         int i;
501         for (i = 1; i < argc; i++) {
502                 char *opt = argv[i];
504                 if (!strcmp(opt, "log") ||
505                     !strcmp(opt, "diff")) {
506                         subcommand = opt;
507                         opt_request = opt[0] == 'l'
508                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
509                         warn("`tig %s' has been deprecated", opt);
510                         break;
511                 }
513                 if (!strcmp(opt, "show")) {
514                         subcommand = opt;
515                         opt_request = REQ_VIEW_DIFF;
516                         break;
517                 }
519                 if (!strcmp(opt, "status")) {
520                         subcommand = opt;
521                         opt_request = REQ_VIEW_STATUS;
522                         break;
523                 }
525                 if (opt[0] && opt[0] != '-')
526                         break;
528                 if (!strcmp(opt, "--")) {
529                         i++;
530                         break;
531                 }
533                 if (check_option(opt, 'v', "version", OPT_NONE)) {
534                         printf("tig version %s\n", TIG_VERSION);
535                         return FALSE;
536                 }
538                 if (check_option(opt, 'h', "help", OPT_NONE)) {
539                         printf(usage);
540                         return FALSE;
541                 }
543                 if (!strcmp(opt, "-S")) {
544                         warn("`%s' has been deprecated; use `tig status' instead", opt);
545                         opt_request = REQ_VIEW_STATUS;
546                         continue;
547                 }
549                 if (!strcmp(opt, "-l")) {
550                         opt_request = REQ_VIEW_LOG;
551                 } else if (!strcmp(opt, "-d")) {
552                         opt_request = REQ_VIEW_DIFF;
553                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
554                         opt_line_number = TRUE;
555                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
556                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
557                 } else {
558                         if (altargc >= ARRAY_SIZE(altargv))
559                                 die("maximum number of arguments exceeded");
560                         altargv[altargc++] = opt;
561                         continue;
562                 }
564                 warn("`%s' has been deprecated", opt);
565         }
567         /* Check that no 'alt' arguments occured before a subcommand. */
568         if (subcommand && i < argc && altargc > 0)
569                 die("unknown arguments before `%s'", argv[i]);
571         if (!isatty(STDIN_FILENO)) {
572                 opt_request = REQ_VIEW_PAGER;
573                 opt_pipe = stdin;
575         } else if (opt_request == REQ_VIEW_STATUS) {
576                 if (argc - i > 1)
577                         warn("ignoring arguments after `%s'", argv[i]);
579         } else if (i < argc || altargc > 0) {
580                 int alti = 0;
581                 size_t buf_size;
583                 if (opt_request == REQ_VIEW_MAIN)
584                         /* XXX: This is vulnerable to the user overriding
585                          * options required for the main view parser. */
586                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
587                 else
588                         string_copy(opt_cmd, "git");
589                 buf_size = strlen(opt_cmd);
591                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
592                         opt_cmd[buf_size++] = ' ';
593                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
594                 }
596                 while (buf_size < sizeof(opt_cmd) && i < argc) {
597                         opt_cmd[buf_size++] = ' ';
598                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
599                 }
601                 if (buf_size >= sizeof(opt_cmd))
602                         die("command too long");
604                 opt_cmd[buf_size] = 0;
605         }
607         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
608                 opt_utf8 = FALSE;
610         return TRUE;
614 /*
615  * Line-oriented content detection.
616  */
618 #define LINE_INFO \
619 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
620 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
621 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
622 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
623 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
624 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
625 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
626 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
627 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
628 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
629 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
630 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
631 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
632 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
633 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
634 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
636 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
637 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
638 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
639 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
640 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
641 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
642 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
643 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
644 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
645 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
646 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
647 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
648 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
649 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
650 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
651 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
652 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
653 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
654 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
655 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
656 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
657 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
658 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
659 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
660 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
661 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
662 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
663 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
664 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
665 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
666 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
668 enum line_type {
669 #define LINE(type, line, fg, bg, attr) \
670         LINE_##type
671         LINE_INFO
672 #undef  LINE
673 };
675 struct line_info {
676         const char *name;       /* Option name. */
677         int namelen;            /* Size of option name. */
678         const char *line;       /* The start of line to match. */
679         int linelen;            /* Size of string to match. */
680         int fg, bg, attr;       /* Color and text attributes for the lines. */
681 };
683 static struct line_info line_info[] = {
684 #define LINE(type, line, fg, bg, attr) \
685         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
686         LINE_INFO
687 #undef  LINE
688 };
690 static enum line_type
691 get_line_type(char *line)
693         int linelen = strlen(line);
694         enum line_type type;
696         for (type = 0; type < ARRAY_SIZE(line_info); type++)
697                 /* Case insensitive search matches Signed-off-by lines better. */
698                 if (linelen >= line_info[type].linelen &&
699                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
700                         return type;
702         return LINE_DEFAULT;
705 static inline int
706 get_line_attr(enum line_type type)
708         assert(type < ARRAY_SIZE(line_info));
709         return COLOR_PAIR(type) | line_info[type].attr;
712 static struct line_info *
713 get_line_info(char *name, int namelen)
715         enum line_type type;
717         for (type = 0; type < ARRAY_SIZE(line_info); type++)
718                 if (namelen == line_info[type].namelen &&
719                     !string_enum_compare(line_info[type].name, name, namelen))
720                         return &line_info[type];
722         return NULL;
725 static void
726 init_colors(void)
728         int default_bg = line_info[LINE_DEFAULT].bg;
729         int default_fg = line_info[LINE_DEFAULT].fg;
730         enum line_type type;
732         start_color();
734         if (assume_default_colors(default_fg, default_bg) == ERR) {
735                 default_bg = COLOR_BLACK;
736                 default_fg = COLOR_WHITE;
737         }
739         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
740                 struct line_info *info = &line_info[type];
741                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
742                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
744                 init_pair(type, fg, bg);
745         }
748 struct line {
749         enum line_type type;
751         /* State flags */
752         unsigned int selected:1;
754         void *data;             /* User data */
755 };
758 /*
759  * Keys
760  */
762 struct keybinding {
763         int alias;
764         enum request request;
765         struct keybinding *next;
766 };
768 static struct keybinding default_keybindings[] = {
769         /* View switching */
770         { 'm',          REQ_VIEW_MAIN },
771         { 'd',          REQ_VIEW_DIFF },
772         { 'l',          REQ_VIEW_LOG },
773         { 't',          REQ_VIEW_TREE },
774         { 'f',          REQ_VIEW_BLOB },
775         { 'p',          REQ_VIEW_PAGER },
776         { 'h',          REQ_VIEW_HELP },
777         { 'S',          REQ_VIEW_STATUS },
778         { 'c',          REQ_VIEW_STAGE },
780         /* View manipulation */
781         { 'q',          REQ_VIEW_CLOSE },
782         { KEY_TAB,      REQ_VIEW_NEXT },
783         { KEY_RETURN,   REQ_ENTER },
784         { KEY_UP,       REQ_PREVIOUS },
785         { KEY_DOWN,     REQ_NEXT },
786         { 'R',          REQ_REFRESH },
788         /* Cursor navigation */
789         { 'k',          REQ_MOVE_UP },
790         { 'j',          REQ_MOVE_DOWN },
791         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
792         { KEY_END,      REQ_MOVE_LAST_LINE },
793         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
794         { ' ',          REQ_MOVE_PAGE_DOWN },
795         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
796         { 'b',          REQ_MOVE_PAGE_UP },
797         { '-',          REQ_MOVE_PAGE_UP },
799         /* Scrolling */
800         { KEY_IC,       REQ_SCROLL_LINE_UP },
801         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
802         { 'w',          REQ_SCROLL_PAGE_UP },
803         { 's',          REQ_SCROLL_PAGE_DOWN },
805         /* Searching */
806         { '/',          REQ_SEARCH },
807         { '?',          REQ_SEARCH_BACK },
808         { 'n',          REQ_FIND_NEXT },
809         { 'N',          REQ_FIND_PREV },
811         /* Misc */
812         { 'Q',          REQ_QUIT },
813         { 'z',          REQ_STOP_LOADING },
814         { 'v',          REQ_SHOW_VERSION },
815         { 'r',          REQ_SCREEN_REDRAW },
816         { '.',          REQ_TOGGLE_LINENO },
817         { 'D',          REQ_TOGGLE_DATE },
818         { 'A',          REQ_TOGGLE_AUTHOR },
819         { 'g',          REQ_TOGGLE_REV_GRAPH },
820         { 'F',          REQ_TOGGLE_REFS },
821         { ':',          REQ_PROMPT },
822         { 'u',          REQ_STATUS_UPDATE },
823         { 'M',          REQ_STATUS_MERGE },
824         { ',',          REQ_TREE_PARENT },
825         { 'e',          REQ_EDIT },
827         /* Using the ncurses SIGWINCH handler. */
828         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
829 };
831 #define KEYMAP_INFO \
832         KEYMAP_(GENERIC), \
833         KEYMAP_(MAIN), \
834         KEYMAP_(DIFF), \
835         KEYMAP_(LOG), \
836         KEYMAP_(TREE), \
837         KEYMAP_(BLOB), \
838         KEYMAP_(PAGER), \
839         KEYMAP_(HELP), \
840         KEYMAP_(STATUS), \
841         KEYMAP_(STAGE)
843 enum keymap {
844 #define KEYMAP_(name) KEYMAP_##name
845         KEYMAP_INFO
846 #undef  KEYMAP_
847 };
849 static struct int_map keymap_table[] = {
850 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
851         KEYMAP_INFO
852 #undef  KEYMAP_
853 };
855 #define set_keymap(map, name) \
856         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
858 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
860 static void
861 add_keybinding(enum keymap keymap, enum request request, int key)
863         struct keybinding *keybinding;
865         keybinding = calloc(1, sizeof(*keybinding));
866         if (!keybinding)
867                 die("Failed to allocate keybinding");
869         keybinding->alias = key;
870         keybinding->request = request;
871         keybinding->next = keybindings[keymap];
872         keybindings[keymap] = keybinding;
875 /* Looks for a key binding first in the given map, then in the generic map, and
876  * lastly in the default keybindings. */
877 static enum request
878 get_keybinding(enum keymap keymap, int key)
880         struct keybinding *kbd;
881         int i;
883         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
884                 if (kbd->alias == key)
885                         return kbd->request;
887         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
888                 if (kbd->alias == key)
889                         return kbd->request;
891         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
892                 if (default_keybindings[i].alias == key)
893                         return default_keybindings[i].request;
895         return (enum request) key;
899 struct key {
900         char *name;
901         int value;
902 };
904 static struct key key_table[] = {
905         { "Enter",      KEY_RETURN },
906         { "Space",      ' ' },
907         { "Backspace",  KEY_BACKSPACE },
908         { "Tab",        KEY_TAB },
909         { "Escape",     KEY_ESC },
910         { "Left",       KEY_LEFT },
911         { "Right",      KEY_RIGHT },
912         { "Up",         KEY_UP },
913         { "Down",       KEY_DOWN },
914         { "Insert",     KEY_IC },
915         { "Delete",     KEY_DC },
916         { "Hash",       '#' },
917         { "Home",       KEY_HOME },
918         { "End",        KEY_END },
919         { "PageUp",     KEY_PPAGE },
920         { "PageDown",   KEY_NPAGE },
921         { "F1",         KEY_F(1) },
922         { "F2",         KEY_F(2) },
923         { "F3",         KEY_F(3) },
924         { "F4",         KEY_F(4) },
925         { "F5",         KEY_F(5) },
926         { "F6",         KEY_F(6) },
927         { "F7",         KEY_F(7) },
928         { "F8",         KEY_F(8) },
929         { "F9",         KEY_F(9) },
930         { "F10",        KEY_F(10) },
931         { "F11",        KEY_F(11) },
932         { "F12",        KEY_F(12) },
933 };
935 static int
936 get_key_value(const char *name)
938         int i;
940         for (i = 0; i < ARRAY_SIZE(key_table); i++)
941                 if (!strcasecmp(key_table[i].name, name))
942                         return key_table[i].value;
944         if (strlen(name) == 1 && isprint(*name))
945                 return (int) *name;
947         return ERR;
950 static char *
951 get_key_name(int key_value)
953         static char key_char[] = "'X'";
954         char *seq = NULL;
955         int key;
957         for (key = 0; key < ARRAY_SIZE(key_table); key++)
958                 if (key_table[key].value == key_value)
959                         seq = key_table[key].name;
961         if (seq == NULL &&
962             key_value < 127 &&
963             isprint(key_value)) {
964                 key_char[1] = (char) key_value;
965                 seq = key_char;
966         }
968         return seq ? seq : "'?'";
971 static char *
972 get_key(enum request request)
974         static char buf[BUFSIZ];
975         size_t pos = 0;
976         char *sep = "";
977         int i;
979         buf[pos] = 0;
981         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
982                 struct keybinding *keybinding = &default_keybindings[i];
984                 if (keybinding->request != request)
985                         continue;
987                 if (!string_format_from(buf, &pos, "%s%s", sep,
988                                         get_key_name(keybinding->alias)))
989                         return "Too many keybindings!";
990                 sep = ", ";
991         }
993         return buf;
996 struct run_request {
997         enum keymap keymap;
998         int key;
999         char cmd[SIZEOF_STR];
1000 };
1002 static struct run_request *run_request;
1003 static size_t run_requests;
1005 static enum request
1006 add_run_request(enum keymap keymap, int key, int argc, char **argv)
1008         struct run_request *tmp;
1009         struct run_request req = { keymap, key };
1010         size_t bufpos;
1012         for (bufpos = 0; argc > 0; argc--, argv++)
1013                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1014                         return REQ_NONE;
1016         req.cmd[bufpos - 1] = 0;
1018         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1019         if (!tmp)
1020                 return REQ_NONE;
1022         run_request = tmp;
1023         run_request[run_requests++] = req;
1025         return REQ_NONE + run_requests;
1028 static struct run_request *
1029 get_run_request(enum request request)
1031         if (request <= REQ_NONE)
1032                 return NULL;
1033         return &run_request[request - REQ_NONE - 1];
1036 static void
1037 add_builtin_run_requests(void)
1039         struct {
1040                 enum keymap keymap;
1041                 int key;
1042                 char *argv[1];
1043         } reqs[] = {
1044                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1045                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1046         };
1047         int i;
1049         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1050                 enum request req;
1052                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1053                 if (req != REQ_NONE)
1054                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1055         }
1058 /*
1059  * User config file handling.
1060  */
1062 static struct int_map color_map[] = {
1063 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1064         COLOR_MAP(DEFAULT),
1065         COLOR_MAP(BLACK),
1066         COLOR_MAP(BLUE),
1067         COLOR_MAP(CYAN),
1068         COLOR_MAP(GREEN),
1069         COLOR_MAP(MAGENTA),
1070         COLOR_MAP(RED),
1071         COLOR_MAP(WHITE),
1072         COLOR_MAP(YELLOW),
1073 };
1075 #define set_color(color, name) \
1076         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1078 static struct int_map attr_map[] = {
1079 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1080         ATTR_MAP(NORMAL),
1081         ATTR_MAP(BLINK),
1082         ATTR_MAP(BOLD),
1083         ATTR_MAP(DIM),
1084         ATTR_MAP(REVERSE),
1085         ATTR_MAP(STANDOUT),
1086         ATTR_MAP(UNDERLINE),
1087 };
1089 #define set_attribute(attr, name) \
1090         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1092 static int   config_lineno;
1093 static bool  config_errors;
1094 static char *config_msg;
1096 /* Wants: object fgcolor bgcolor [attr] */
1097 static int
1098 option_color_command(int argc, char *argv[])
1100         struct line_info *info;
1102         if (argc != 3 && argc != 4) {
1103                 config_msg = "Wrong number of arguments given to color command";
1104                 return ERR;
1105         }
1107         info = get_line_info(argv[0], strlen(argv[0]));
1108         if (!info) {
1109                 config_msg = "Unknown color name";
1110                 return ERR;
1111         }
1113         if (set_color(&info->fg, argv[1]) == ERR ||
1114             set_color(&info->bg, argv[2]) == ERR) {
1115                 config_msg = "Unknown color";
1116                 return ERR;
1117         }
1119         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1120                 config_msg = "Unknown attribute";
1121                 return ERR;
1122         }
1124         return OK;
1127 /* Wants: name = value */
1128 static int
1129 option_set_command(int argc, char *argv[])
1131         if (argc != 3) {
1132                 config_msg = "Wrong number of arguments given to set command";
1133                 return ERR;
1134         }
1136         if (strcmp(argv[1], "=")) {
1137                 config_msg = "No value assigned";
1138                 return ERR;
1139         }
1141         if (!strcmp(argv[0], "show-rev-graph")) {
1142                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1143                                  !strcmp(argv[2], "true") ||
1144                                  !strcmp(argv[2], "yes"));
1145                 return OK;
1146         }
1148         if (!strcmp(argv[0], "line-number-interval")) {
1149                 opt_num_interval = atoi(argv[2]);
1150                 return OK;
1151         }
1153         if (!strcmp(argv[0], "tab-size")) {
1154                 opt_tab_size = atoi(argv[2]);
1155                 return OK;
1156         }
1158         if (!strcmp(argv[0], "commit-encoding")) {
1159                 char *arg = argv[2];
1160                 int delimiter = *arg;
1161                 int i;
1163                 switch (delimiter) {
1164                 case '"':
1165                 case '\'':
1166                         for (arg++, i = 0; arg[i]; i++)
1167                                 if (arg[i] == delimiter) {
1168                                         arg[i] = 0;
1169                                         break;
1170                                 }
1171                 default:
1172                         string_ncopy(opt_encoding, arg, strlen(arg));
1173                         return OK;
1174                 }
1175         }
1177         config_msg = "Unknown variable name";
1178         return ERR;
1181 /* Wants: mode request key */
1182 static int
1183 option_bind_command(int argc, char *argv[])
1185         enum request request;
1186         int keymap;
1187         int key;
1189         if (argc < 3) {
1190                 config_msg = "Wrong number of arguments given to bind command";
1191                 return ERR;
1192         }
1194         if (set_keymap(&keymap, argv[0]) == ERR) {
1195                 config_msg = "Unknown key map";
1196                 return ERR;
1197         }
1199         key = get_key_value(argv[1]);
1200         if (key == ERR) {
1201                 config_msg = "Unknown key";
1202                 return ERR;
1203         }
1205         request = get_request(argv[2]);
1206         if (request == REQ_NONE) {
1207                 const char *obsolete[] = { "cherry-pick" };
1208                 size_t namelen = strlen(argv[2]);
1209                 int i;
1211                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1212                         if (namelen == strlen(obsolete[i]) &&
1213                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1214                                 config_msg = "Obsolete request name";
1215                                 return ERR;
1216                         }
1217                 }
1218         }
1219         if (request == REQ_NONE && *argv[2]++ == '!')
1220                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1221         if (request == REQ_NONE) {
1222                 config_msg = "Unknown request name";
1223                 return ERR;
1224         }
1226         add_keybinding(keymap, request, key);
1228         return OK;
1231 static int
1232 set_option(char *opt, char *value)
1234         char *argv[16];
1235         int valuelen;
1236         int argc = 0;
1238         /* Tokenize */
1239         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1240                 argv[argc++] = value;
1241                 value += valuelen;
1243                 /* Nothing more to tokenize or last available token. */
1244                 if (!*value || argc >= ARRAY_SIZE(argv))
1245                         break;
1247                 *value++ = 0;
1248                 while (isspace(*value))
1249                         value++;
1250         }
1252         if (!strcmp(opt, "color"))
1253                 return option_color_command(argc, argv);
1255         if (!strcmp(opt, "set"))
1256                 return option_set_command(argc, argv);
1258         if (!strcmp(opt, "bind"))
1259                 return option_bind_command(argc, argv);
1261         config_msg = "Unknown option command";
1262         return ERR;
1265 static int
1266 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1268         int status = OK;
1270         config_lineno++;
1271         config_msg = "Internal error";
1273         /* Check for comment markers, since read_properties() will
1274          * only ensure opt and value are split at first " \t". */
1275         optlen = strcspn(opt, "#");
1276         if (optlen == 0)
1277                 return OK;
1279         if (opt[optlen] != 0) {
1280                 config_msg = "No option value";
1281                 status = ERR;
1283         }  else {
1284                 /* Look for comment endings in the value. */
1285                 size_t len = strcspn(value, "#");
1287                 if (len < valuelen) {
1288                         valuelen = len;
1289                         value[valuelen] = 0;
1290                 }
1292                 status = set_option(opt, value);
1293         }
1295         if (status == ERR) {
1296                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1297                         config_lineno, (int) optlen, opt, config_msg);
1298                 config_errors = TRUE;
1299         }
1301         /* Always keep going if errors are encountered. */
1302         return OK;
1305 static void
1306 load_option_file(const char *path)
1308         FILE *file;
1310         /* It's ok that the file doesn't exist. */
1311         file = fopen(path, "r");
1312         if (!file)
1313                 return;
1315         config_lineno = 0;
1316         config_errors = FALSE;
1318         if (read_properties(file, " \t", read_option) == ERR ||
1319             config_errors == TRUE)
1320                 fprintf(stderr, "Errors while loading %s.\n", path);
1323 static int
1324 load_options(void)
1326         char *home = getenv("HOME");
1327         char *tigrc_user = getenv("TIGRC_USER");
1328         char *tigrc_system = getenv("TIGRC_SYSTEM");
1329         char buf[SIZEOF_STR];
1331         add_builtin_run_requests();
1333         if (!tigrc_system) {
1334                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1335                         return ERR;
1336                 tigrc_system = buf;
1337         }
1338         load_option_file(tigrc_system);
1340         if (!tigrc_user) {
1341                 if (!home || !string_format(buf, "%s/.tigrc", home))
1342                         return ERR;
1343                 tigrc_user = buf;
1344         }
1345         load_option_file(tigrc_user);
1347         return OK;
1351 /*
1352  * The viewer
1353  */
1355 struct view;
1356 struct view_ops;
1358 /* The display array of active views and the index of the current view. */
1359 static struct view *display[2];
1360 static unsigned int current_view;
1362 /* Reading from the prompt? */
1363 static bool input_mode = FALSE;
1365 #define foreach_displayed_view(view, i) \
1366         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1368 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1370 /* Current head and commit ID */
1371 static char ref_blob[SIZEOF_REF]        = "";
1372 static char ref_commit[SIZEOF_REF]      = "HEAD";
1373 static char ref_head[SIZEOF_REF]        = "HEAD";
1375 struct view {
1376         const char *name;       /* View name */
1377         const char *cmd_fmt;    /* Default command line format */
1378         const char *cmd_env;    /* Command line set via environment */
1379         const char *id;         /* Points to either of ref_{head,commit,blob} */
1381         struct view_ops *ops;   /* View operations */
1383         enum keymap keymap;     /* What keymap does this view have */
1385         char cmd[SIZEOF_STR];   /* Command buffer */
1386         char ref[SIZEOF_REF];   /* Hovered commit reference */
1387         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1389         int height, width;      /* The width and height of the main window */
1390         WINDOW *win;            /* The main window */
1391         WINDOW *title;          /* The title window living below the main window */
1393         /* Navigation */
1394         unsigned long offset;   /* Offset of the window top */
1395         unsigned long lineno;   /* Current line number */
1397         /* Searching */
1398         char grep[SIZEOF_STR];  /* Search string */
1399         regex_t *regex;         /* Pre-compiled regex */
1401         /* If non-NULL, points to the view that opened this view. If this view
1402          * is closed tig will switch back to the parent view. */
1403         struct view *parent;
1405         /* Buffering */
1406         unsigned long lines;    /* Total number of lines */
1407         struct line *line;      /* Line index */
1408         unsigned long line_size;/* Total number of allocated lines */
1409         unsigned int digits;    /* Number of digits in the lines member. */
1411         /* Loading */
1412         FILE *pipe;
1413         time_t start_time;
1414 };
1416 struct view_ops {
1417         /* What type of content being displayed. Used in the title bar. */
1418         const char *type;
1419         /* Open and reads in all view content. */
1420         bool (*open)(struct view *view);
1421         /* Read one line; updates view->line. */
1422         bool (*read)(struct view *view, char *data);
1423         /* Draw one line; @lineno must be < view->height. */
1424         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1425         /* Depending on view handle a special requests. */
1426         enum request (*request)(struct view *view, enum request request, struct line *line);
1427         /* Search for regex in a line. */
1428         bool (*grep)(struct view *view, struct line *line);
1429         /* Select line */
1430         void (*select)(struct view *view, struct line *line);
1431 };
1433 static struct view_ops pager_ops;
1434 static struct view_ops main_ops;
1435 static struct view_ops tree_ops;
1436 static struct view_ops blob_ops;
1437 static struct view_ops help_ops;
1438 static struct view_ops status_ops;
1439 static struct view_ops stage_ops;
1441 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1442         { name, cmd, #env, ref, ops, map}
1444 #define VIEW_(id, name, ops, ref) \
1445         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1448 static struct view views[] = {
1449         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1450         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1451         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1452         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1453         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1454         VIEW_(HELP,   "help",   &help_ops,   ""),
1455         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1456         VIEW_(STATUS, "status", &status_ops, ""),
1457         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1458 };
1460 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1462 #define foreach_view(view, i) \
1463         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1465 #define view_is_displayed(view) \
1466         (view == display[0] || view == display[1])
1468 static int
1469 draw_text(struct view *view, const char *string, int max_len, int col,
1470           bool use_tilde, int tilde_attr)
1472         int len = 0;
1473         int trimmed = FALSE;
1475         if (max_len <= 0)
1476                 return 0;
1478         if (opt_utf8) {
1479                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1480         } else {
1481                 len = strlen(string);
1482                 if (len > max_len) {
1483                         if (use_tilde) {
1484                                 max_len -= 1;
1485                         }
1486                         len = max_len;
1487                         trimmed = TRUE;
1488                 }
1489         }
1491         waddnstr(view->win, string, len);
1492         if (trimmed && use_tilde) {
1493                 if (tilde_attr != -1)
1494                         wattrset(view->win, tilde_attr);
1495                 waddch(view->win, '~');
1496                 len++;
1497         }
1499         return len;
1502 static bool
1503 draw_view_line(struct view *view, unsigned int lineno)
1505         struct line *line;
1506         bool selected = (view->offset + lineno == view->lineno);
1507         bool draw_ok;
1509         assert(view_is_displayed(view));
1511         if (view->offset + lineno >= view->lines)
1512                 return FALSE;
1514         line = &view->line[view->offset + lineno];
1516         if (selected) {
1517                 line->selected = TRUE;
1518                 view->ops->select(view, line);
1519         } else if (line->selected) {
1520                 line->selected = FALSE;
1521                 wmove(view->win, lineno, 0);
1522                 wclrtoeol(view->win);
1523         }
1525         scrollok(view->win, FALSE);
1526         draw_ok = view->ops->draw(view, line, lineno, selected);
1527         scrollok(view->win, TRUE);
1529         return draw_ok;
1532 static void
1533 redraw_view_from(struct view *view, int lineno)
1535         assert(0 <= lineno && lineno < view->height);
1537         for (; lineno < view->height; lineno++) {
1538                 if (!draw_view_line(view, lineno))
1539                         break;
1540         }
1542         redrawwin(view->win);
1543         if (input_mode)
1544                 wnoutrefresh(view->win);
1545         else
1546                 wrefresh(view->win);
1549 static void
1550 redraw_view(struct view *view)
1552         wclear(view->win);
1553         redraw_view_from(view, 0);
1557 static void
1558 update_view_title(struct view *view)
1560         char buf[SIZEOF_STR];
1561         char state[SIZEOF_STR];
1562         size_t bufpos = 0, statelen = 0;
1564         assert(view_is_displayed(view));
1566         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1567                 unsigned int view_lines = view->offset + view->height;
1568                 unsigned int lines = view->lines
1569                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1570                                    : 0;
1572                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1573                                    view->ops->type,
1574                                    view->lineno + 1,
1575                                    view->lines,
1576                                    lines);
1578                 if (view->pipe) {
1579                         time_t secs = time(NULL) - view->start_time;
1581                         /* Three git seconds are a long time ... */
1582                         if (secs > 2)
1583                                 string_format_from(state, &statelen, " %lds", secs);
1584                 }
1585         }
1587         string_format_from(buf, &bufpos, "[%s]", view->name);
1588         if (*view->ref && bufpos < view->width) {
1589                 size_t refsize = strlen(view->ref);
1590                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1592                 if (minsize < view->width)
1593                         refsize = view->width - minsize + 7;
1594                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1595         }
1597         if (statelen && bufpos < view->width) {
1598                 string_format_from(buf, &bufpos, " %s", state);
1599         }
1601         if (view == display[current_view])
1602                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1603         else
1604                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1606         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1607         wclrtoeol(view->title);
1608         wmove(view->title, 0, view->width - 1);
1610         if (input_mode)
1611                 wnoutrefresh(view->title);
1612         else
1613                 wrefresh(view->title);
1616 static void
1617 resize_display(void)
1619         int offset, i;
1620         struct view *base = display[0];
1621         struct view *view = display[1] ? display[1] : display[0];
1623         /* Setup window dimensions */
1625         getmaxyx(stdscr, base->height, base->width);
1627         /* Make room for the status window. */
1628         base->height -= 1;
1630         if (view != base) {
1631                 /* Horizontal split. */
1632                 view->width   = base->width;
1633                 view->height  = SCALE_SPLIT_VIEW(base->height);
1634                 base->height -= view->height;
1636                 /* Make room for the title bar. */
1637                 view->height -= 1;
1638         }
1640         /* Make room for the title bar. */
1641         base->height -= 1;
1643         offset = 0;
1645         foreach_displayed_view (view, i) {
1646                 if (!view->win) {
1647                         view->win = newwin(view->height, 0, offset, 0);
1648                         if (!view->win)
1649                                 die("Failed to create %s view", view->name);
1651                         scrollok(view->win, TRUE);
1653                         view->title = newwin(1, 0, offset + view->height, 0);
1654                         if (!view->title)
1655                                 die("Failed to create title window");
1657                 } else {
1658                         wresize(view->win, view->height, view->width);
1659                         mvwin(view->win,   offset, 0);
1660                         mvwin(view->title, offset + view->height, 0);
1661                 }
1663                 offset += view->height + 1;
1664         }
1667 static void
1668 redraw_display(void)
1670         struct view *view;
1671         int i;
1673         foreach_displayed_view (view, i) {
1674                 redraw_view(view);
1675                 update_view_title(view);
1676         }
1679 static void
1680 update_display_cursor(struct view *view)
1682         /* Move the cursor to the right-most column of the cursor line.
1683          *
1684          * XXX: This could turn out to be a bit expensive, but it ensures that
1685          * the cursor does not jump around. */
1686         if (view->lines) {
1687                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1688                 wrefresh(view->win);
1689         }
1692 /*
1693  * Navigation
1694  */
1696 /* Scrolling backend */
1697 static void
1698 do_scroll_view(struct view *view, int lines)
1700         bool redraw_current_line = FALSE;
1702         /* The rendering expects the new offset. */
1703         view->offset += lines;
1705         assert(0 <= view->offset && view->offset < view->lines);
1706         assert(lines);
1708         /* Move current line into the view. */
1709         if (view->lineno < view->offset) {
1710                 view->lineno = view->offset;
1711                 redraw_current_line = TRUE;
1712         } else if (view->lineno >= view->offset + view->height) {
1713                 view->lineno = view->offset + view->height - 1;
1714                 redraw_current_line = TRUE;
1715         }
1717         assert(view->offset <= view->lineno && view->lineno < view->lines);
1719         /* Redraw the whole screen if scrolling is pointless. */
1720         if (view->height < ABS(lines)) {
1721                 redraw_view(view);
1723         } else {
1724                 int line = lines > 0 ? view->height - lines : 0;
1725                 int end = line + ABS(lines);
1727                 wscrl(view->win, lines);
1729                 for (; line < end; line++) {
1730                         if (!draw_view_line(view, line))
1731                                 break;
1732                 }
1734                 if (redraw_current_line)
1735                         draw_view_line(view, view->lineno - view->offset);
1736         }
1738         redrawwin(view->win);
1739         wrefresh(view->win);
1740         report("");
1743 /* Scroll frontend */
1744 static void
1745 scroll_view(struct view *view, enum request request)
1747         int lines = 1;
1749         assert(view_is_displayed(view));
1751         switch (request) {
1752         case REQ_SCROLL_PAGE_DOWN:
1753                 lines = view->height;
1754         case REQ_SCROLL_LINE_DOWN:
1755                 if (view->offset + lines > view->lines)
1756                         lines = view->lines - view->offset;
1758                 if (lines == 0 || view->offset + view->height >= view->lines) {
1759                         report("Cannot scroll beyond the last line");
1760                         return;
1761                 }
1762                 break;
1764         case REQ_SCROLL_PAGE_UP:
1765                 lines = view->height;
1766         case REQ_SCROLL_LINE_UP:
1767                 if (lines > view->offset)
1768                         lines = view->offset;
1770                 if (lines == 0) {
1771                         report("Cannot scroll beyond the first line");
1772                         return;
1773                 }
1775                 lines = -lines;
1776                 break;
1778         default:
1779                 die("request %d not handled in switch", request);
1780         }
1782         do_scroll_view(view, lines);
1785 /* Cursor moving */
1786 static void
1787 move_view(struct view *view, enum request request)
1789         int scroll_steps = 0;
1790         int steps;
1792         switch (request) {
1793         case REQ_MOVE_FIRST_LINE:
1794                 steps = -view->lineno;
1795                 break;
1797         case REQ_MOVE_LAST_LINE:
1798                 steps = view->lines - view->lineno - 1;
1799                 break;
1801         case REQ_MOVE_PAGE_UP:
1802                 steps = view->height > view->lineno
1803                       ? -view->lineno : -view->height;
1804                 break;
1806         case REQ_MOVE_PAGE_DOWN:
1807                 steps = view->lineno + view->height >= view->lines
1808                       ? view->lines - view->lineno - 1 : view->height;
1809                 break;
1811         case REQ_MOVE_UP:
1812                 steps = -1;
1813                 break;
1815         case REQ_MOVE_DOWN:
1816                 steps = 1;
1817                 break;
1819         default:
1820                 die("request %d not handled in switch", request);
1821         }
1823         if (steps <= 0 && view->lineno == 0) {
1824                 report("Cannot move beyond the first line");
1825                 return;
1827         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1828                 report("Cannot move beyond the last line");
1829                 return;
1830         }
1832         /* Move the current line */
1833         view->lineno += steps;
1834         assert(0 <= view->lineno && view->lineno < view->lines);
1836         /* Check whether the view needs to be scrolled */
1837         if (view->lineno < view->offset ||
1838             view->lineno >= view->offset + view->height) {
1839                 scroll_steps = steps;
1840                 if (steps < 0 && -steps > view->offset) {
1841                         scroll_steps = -view->offset;
1843                 } else if (steps > 0) {
1844                         if (view->lineno == view->lines - 1 &&
1845                             view->lines > view->height) {
1846                                 scroll_steps = view->lines - view->offset - 1;
1847                                 if (scroll_steps >= view->height)
1848                                         scroll_steps -= view->height - 1;
1849                         }
1850                 }
1851         }
1853         if (!view_is_displayed(view)) {
1854                 view->offset += scroll_steps;
1855                 assert(0 <= view->offset && view->offset < view->lines);
1856                 view->ops->select(view, &view->line[view->lineno]);
1857                 return;
1858         }
1860         /* Repaint the old "current" line if we be scrolling */
1861         if (ABS(steps) < view->height)
1862                 draw_view_line(view, view->lineno - steps - view->offset);
1864         if (scroll_steps) {
1865                 do_scroll_view(view, scroll_steps);
1866                 return;
1867         }
1869         /* Draw the current line */
1870         draw_view_line(view, view->lineno - view->offset);
1872         redrawwin(view->win);
1873         wrefresh(view->win);
1874         report("");
1878 /*
1879  * Searching
1880  */
1882 static void search_view(struct view *view, enum request request);
1884 static bool
1885 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1887         assert(view_is_displayed(view));
1889         if (!view->ops->grep(view, line))
1890                 return FALSE;
1892         if (lineno - view->offset >= view->height) {
1893                 view->offset = lineno;
1894                 view->lineno = lineno;
1895                 redraw_view(view);
1897         } else {
1898                 unsigned long old_lineno = view->lineno - view->offset;
1900                 view->lineno = lineno;
1901                 draw_view_line(view, old_lineno);
1903                 draw_view_line(view, view->lineno - view->offset);
1904                 redrawwin(view->win);
1905                 wrefresh(view->win);
1906         }
1908         report("Line %ld matches '%s'", lineno + 1, view->grep);
1909         return TRUE;
1912 static void
1913 find_next(struct view *view, enum request request)
1915         unsigned long lineno = view->lineno;
1916         int direction;
1918         if (!*view->grep) {
1919                 if (!*opt_search)
1920                         report("No previous search");
1921                 else
1922                         search_view(view, request);
1923                 return;
1924         }
1926         switch (request) {
1927         case REQ_SEARCH:
1928         case REQ_FIND_NEXT:
1929                 direction = 1;
1930                 break;
1932         case REQ_SEARCH_BACK:
1933         case REQ_FIND_PREV:
1934                 direction = -1;
1935                 break;
1937         default:
1938                 return;
1939         }
1941         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1942                 lineno += direction;
1944         /* Note, lineno is unsigned long so will wrap around in which case it
1945          * will become bigger than view->lines. */
1946         for (; lineno < view->lines; lineno += direction) {
1947                 struct line *line = &view->line[lineno];
1949                 if (find_next_line(view, lineno, line))
1950                         return;
1951         }
1953         report("No match found for '%s'", view->grep);
1956 static void
1957 search_view(struct view *view, enum request request)
1959         int regex_err;
1961         if (view->regex) {
1962                 regfree(view->regex);
1963                 *view->grep = 0;
1964         } else {
1965                 view->regex = calloc(1, sizeof(*view->regex));
1966                 if (!view->regex)
1967                         return;
1968         }
1970         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1971         if (regex_err != 0) {
1972                 char buf[SIZEOF_STR] = "unknown error";
1974                 regerror(regex_err, view->regex, buf, sizeof(buf));
1975                 report("Search failed: %s", buf);
1976                 return;
1977         }
1979         string_copy(view->grep, opt_search);
1981         find_next(view, request);
1984 /*
1985  * Incremental updating
1986  */
1988 static void
1989 end_update(struct view *view)
1991         if (!view->pipe)
1992                 return;
1993         set_nonblocking_input(FALSE);
1994         if (view->pipe == stdin)
1995                 fclose(view->pipe);
1996         else
1997                 pclose(view->pipe);
1998         view->pipe = NULL;
2001 static bool
2002 begin_update(struct view *view)
2004         if (view->pipe)
2005                 end_update(view);
2007         if (opt_cmd[0]) {
2008                 string_copy(view->cmd, opt_cmd);
2009                 opt_cmd[0] = 0;
2010                 /* When running random commands, initially show the
2011                  * command in the title. However, it maybe later be
2012                  * overwritten if a commit line is selected. */
2013                 if (view == VIEW(REQ_VIEW_PAGER))
2014                         string_copy(view->ref, view->cmd);
2015                 else
2016                         view->ref[0] = 0;
2018         } else if (view == VIEW(REQ_VIEW_TREE)) {
2019                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2020                 char path[SIZEOF_STR];
2022                 if (strcmp(view->vid, view->id))
2023                         opt_path[0] = path[0] = 0;
2024                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2025                         return FALSE;
2027                 if (!string_format(view->cmd, format, view->id, path))
2028                         return FALSE;
2030         } else {
2031                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2032                 const char *id = view->id;
2034                 if (!string_format(view->cmd, format, id, id, id, id, id))
2035                         return FALSE;
2037                 /* Put the current ref_* value to the view title ref
2038                  * member. This is needed by the blob view. Most other
2039                  * views sets it automatically after loading because the
2040                  * first line is a commit line. */
2041                 string_copy_rev(view->ref, view->id);
2042         }
2044         /* Special case for the pager view. */
2045         if (opt_pipe) {
2046                 view->pipe = opt_pipe;
2047                 opt_pipe = NULL;
2048         } else {
2049                 view->pipe = popen(view->cmd, "r");
2050         }
2052         if (!view->pipe)
2053                 return FALSE;
2055         set_nonblocking_input(TRUE);
2057         view->offset = 0;
2058         view->lines  = 0;
2059         view->lineno = 0;
2060         string_copy_rev(view->vid, view->id);
2062         if (view->line) {
2063                 int i;
2065                 for (i = 0; i < view->lines; i++)
2066                         if (view->line[i].data)
2067                                 free(view->line[i].data);
2069                 free(view->line);
2070                 view->line = NULL;
2071         }
2073         view->start_time = time(NULL);
2075         return TRUE;
2078 static struct line *
2079 realloc_lines(struct view *view, size_t line_size)
2081         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
2083         if (!tmp)
2084                 return NULL;
2086         view->line = tmp;
2087         view->line_size = line_size;
2088         return view->line;
2091 static bool
2092 update_view(struct view *view)
2094         char in_buffer[BUFSIZ];
2095         char out_buffer[BUFSIZ * 2];
2096         char *line;
2097         /* The number of lines to read. If too low it will cause too much
2098          * redrawing (and possible flickering), if too high responsiveness
2099          * will suffer. */
2100         unsigned long lines = view->height;
2101         int redraw_from = -1;
2103         if (!view->pipe)
2104                 return TRUE;
2106         /* Only redraw if lines are visible. */
2107         if (view->offset + view->height >= view->lines)
2108                 redraw_from = view->lines - view->offset;
2110         /* FIXME: This is probably not perfect for backgrounded views. */
2111         if (!realloc_lines(view, view->lines + lines))
2112                 goto alloc_error;
2114         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2115                 size_t linelen = strlen(line);
2117                 if (linelen)
2118                         line[linelen - 1] = 0;
2120                 if (opt_iconv != ICONV_NONE) {
2121                         ICONV_CONST char *inbuf = line;
2122                         size_t inlen = linelen;
2124                         char *outbuf = out_buffer;
2125                         size_t outlen = sizeof(out_buffer);
2127                         size_t ret;
2129                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2130                         if (ret != (size_t) -1) {
2131                                 line = out_buffer;
2132                                 linelen = strlen(out_buffer);
2133                         }
2134                 }
2136                 if (!view->ops->read(view, line))
2137                         goto alloc_error;
2139                 if (lines-- == 1)
2140                         break;
2141         }
2143         {
2144                 int digits;
2146                 lines = view->lines;
2147                 for (digits = 0; lines; digits++)
2148                         lines /= 10;
2150                 /* Keep the displayed view in sync with line number scaling. */
2151                 if (digits != view->digits) {
2152                         view->digits = digits;
2153                         redraw_from = 0;
2154                 }
2155         }
2157         if (!view_is_displayed(view))
2158                 goto check_pipe;
2160         if (view == VIEW(REQ_VIEW_TREE)) {
2161                 /* Clear the view and redraw everything since the tree sorting
2162                  * might have rearranged things. */
2163                 redraw_view(view);
2165         } else if (redraw_from >= 0) {
2166                 /* If this is an incremental update, redraw the previous line
2167                  * since for commits some members could have changed when
2168                  * loading the main view. */
2169                 if (redraw_from > 0)
2170                         redraw_from--;
2172                 /* Since revision graph visualization requires knowledge
2173                  * about the parent commit, it causes a further one-off
2174                  * needed to be redrawn for incremental updates. */
2175                 if (redraw_from > 0 && opt_rev_graph)
2176                         redraw_from--;
2178                 /* Incrementally draw avoids flickering. */
2179                 redraw_view_from(view, redraw_from);
2180         }
2182         /* Update the title _after_ the redraw so that if the redraw picks up a
2183          * commit reference in view->ref it'll be available here. */
2184         update_view_title(view);
2186 check_pipe:
2187         if (ferror(view->pipe)) {
2188                 report("Failed to read: %s", strerror(errno));
2189                 goto end;
2191         } else if (feof(view->pipe)) {
2192                 report("");
2193                 goto end;
2194         }
2196         return TRUE;
2198 alloc_error:
2199         report("Allocation failure");
2201 end:
2202         view->ops->read(view, NULL);
2203         end_update(view);
2204         return FALSE;
2207 static struct line *
2208 add_line_data(struct view *view, void *data, enum line_type type)
2210         struct line *line = &view->line[view->lines++];
2212         memset(line, 0, sizeof(*line));
2213         line->type = type;
2214         line->data = data;
2216         return line;
2219 static struct line *
2220 add_line_text(struct view *view, char *data, enum line_type type)
2222         if (data)
2223                 data = strdup(data);
2225         return data ? add_line_data(view, data, type) : NULL;
2229 /*
2230  * View opening
2231  */
2233 enum open_flags {
2234         OPEN_DEFAULT = 0,       /* Use default view switching. */
2235         OPEN_SPLIT = 1,         /* Split current view. */
2236         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2237         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2238 };
2240 static void
2241 open_view(struct view *prev, enum request request, enum open_flags flags)
2243         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2244         bool split = !!(flags & OPEN_SPLIT);
2245         bool reload = !!(flags & OPEN_RELOAD);
2246         struct view *view = VIEW(request);
2247         int nviews = displayed_views();
2248         struct view *base_view = display[0];
2250         if (view == prev && nviews == 1 && !reload) {
2251                 report("Already in %s view", view->name);
2252                 return;
2253         }
2255         if (view->ops->open) {
2256                 if (!view->ops->open(view)) {
2257                         report("Failed to load %s view", view->name);
2258                         return;
2259                 }
2261         } else if ((reload || strcmp(view->vid, view->id)) &&
2262                    !begin_update(view)) {
2263                 report("Failed to load %s view", view->name);
2264                 return;
2265         }
2267         if (split) {
2268                 display[1] = view;
2269                 if (!backgrounded)
2270                         current_view = 1;
2271         } else {
2272                 /* Maximize the current view. */
2273                 memset(display, 0, sizeof(display));
2274                 current_view = 0;
2275                 display[current_view] = view;
2276         }
2278         /* Resize the view when switching between split- and full-screen,
2279          * or when switching between two different full-screen views. */
2280         if (nviews != displayed_views() ||
2281             (nviews == 1 && base_view != display[0]))
2282                 resize_display();
2284         if (split && prev->lineno - prev->offset >= prev->height) {
2285                 /* Take the title line into account. */
2286                 int lines = prev->lineno - prev->offset - prev->height + 1;
2288                 /* Scroll the view that was split if the current line is
2289                  * outside the new limited view. */
2290                 do_scroll_view(prev, lines);
2291         }
2293         if (prev && view != prev) {
2294                 if (split && !backgrounded) {
2295                         /* "Blur" the previous view. */
2296                         update_view_title(prev);
2297                 }
2299                 view->parent = prev;
2300         }
2302         if (view->pipe && view->lines == 0) {
2303                 /* Clear the old view and let the incremental updating refill
2304                  * the screen. */
2305                 wclear(view->win);
2306                 report("");
2307         } else {
2308                 redraw_view(view);
2309                 report("");
2310         }
2312         /* If the view is backgrounded the above calls to report()
2313          * won't redraw the view title. */
2314         if (backgrounded)
2315                 update_view_title(view);
2318 static void
2319 open_external_viewer(const char *cmd)
2321         def_prog_mode();           /* save current tty modes */
2322         endwin();                  /* restore original tty modes */
2323         system(cmd);
2324         fprintf(stderr, "Press Enter to continue");
2325         getc(stdin);
2326         reset_prog_mode();
2327         redraw_display();
2330 static void
2331 open_mergetool(const char *file)
2333         char cmd[SIZEOF_STR];
2334         char file_sq[SIZEOF_STR];
2336         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2337             string_format(cmd, "git mergetool %s", file_sq)) {
2338                 open_external_viewer(cmd);
2339         }
2342 static void
2343 open_editor(bool from_root, const char *file)
2345         char cmd[SIZEOF_STR];
2346         char file_sq[SIZEOF_STR];
2347         char *editor;
2348         char *prefix = from_root ? opt_cdup : "";
2350         editor = getenv("GIT_EDITOR");
2351         if (!editor && *opt_editor)
2352                 editor = opt_editor;
2353         if (!editor)
2354                 editor = getenv("VISUAL");
2355         if (!editor)
2356                 editor = getenv("EDITOR");
2357         if (!editor)
2358                 editor = "vi";
2360         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2361             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2362                 open_external_viewer(cmd);
2363         }
2366 static void
2367 open_run_request(enum request request)
2369         struct run_request *req = get_run_request(request);
2370         char buf[SIZEOF_STR * 2];
2371         size_t bufpos;
2372         char *cmd;
2374         if (!req) {
2375                 report("Unknown run request");
2376                 return;
2377         }
2379         bufpos = 0;
2380         cmd = req->cmd;
2382         while (cmd) {
2383                 char *next = strstr(cmd, "%(");
2384                 int len = next - cmd;
2385                 char *value;
2387                 if (!next) {
2388                         len = strlen(cmd);
2389                         value = "";
2391                 } else if (!strncmp(next, "%(head)", 7)) {
2392                         value = ref_head;
2394                 } else if (!strncmp(next, "%(commit)", 9)) {
2395                         value = ref_commit;
2397                 } else if (!strncmp(next, "%(blob)", 7)) {
2398                         value = ref_blob;
2400                 } else {
2401                         report("Unknown replacement in run request: `%s`", req->cmd);
2402                         return;
2403                 }
2405                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2406                         return;
2408                 if (next)
2409                         next = strchr(next, ')') + 1;
2410                 cmd = next;
2411         }
2413         open_external_viewer(buf);
2416 /*
2417  * User request switch noodle
2418  */
2420 static int
2421 view_driver(struct view *view, enum request request)
2423         int i;
2425         if (request == REQ_NONE) {
2426                 doupdate();
2427                 return TRUE;
2428         }
2430         if (request > REQ_NONE) {
2431                 open_run_request(request);
2432                 return TRUE;
2433         }
2435         if (view && view->lines) {
2436                 request = view->ops->request(view, request, &view->line[view->lineno]);
2437                 if (request == REQ_NONE)
2438                         return TRUE;
2439         }
2441         switch (request) {
2442         case REQ_MOVE_UP:
2443         case REQ_MOVE_DOWN:
2444         case REQ_MOVE_PAGE_UP:
2445         case REQ_MOVE_PAGE_DOWN:
2446         case REQ_MOVE_FIRST_LINE:
2447         case REQ_MOVE_LAST_LINE:
2448                 move_view(view, request);
2449                 break;
2451         case REQ_SCROLL_LINE_DOWN:
2452         case REQ_SCROLL_LINE_UP:
2453         case REQ_SCROLL_PAGE_DOWN:
2454         case REQ_SCROLL_PAGE_UP:
2455                 scroll_view(view, request);
2456                 break;
2458         case REQ_VIEW_BLOB:
2459                 if (!ref_blob[0]) {
2460                         report("No file chosen, press %s to open tree view",
2461                                get_key(REQ_VIEW_TREE));
2462                         break;
2463                 }
2464                 open_view(view, request, OPEN_DEFAULT);
2465                 break;
2467         case REQ_VIEW_PAGER:
2468                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2469                         report("No pager content, press %s to run command from prompt",
2470                                get_key(REQ_PROMPT));
2471                         break;
2472                 }
2473                 open_view(view, request, OPEN_DEFAULT);
2474                 break;
2476         case REQ_VIEW_STAGE:
2477                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2478                         report("No stage content, press %s to open the status view and choose file",
2479                                get_key(REQ_VIEW_STATUS));
2480                         break;
2481                 }
2482                 open_view(view, request, OPEN_DEFAULT);
2483                 break;
2485         case REQ_VIEW_STATUS:
2486                 if (opt_is_inside_work_tree == FALSE) {
2487                         report("The status view requires a working tree");
2488                         break;
2489                 }
2490                 open_view(view, request, OPEN_DEFAULT);
2491                 break;
2493         case REQ_VIEW_MAIN:
2494         case REQ_VIEW_DIFF:
2495         case REQ_VIEW_LOG:
2496         case REQ_VIEW_TREE:
2497         case REQ_VIEW_HELP:
2498                 open_view(view, request, OPEN_DEFAULT);
2499                 break;
2501         case REQ_NEXT:
2502         case REQ_PREVIOUS:
2503                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2505                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2506                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2507                    (view == VIEW(REQ_VIEW_STAGE) &&
2508                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2509                    (view == VIEW(REQ_VIEW_BLOB) &&
2510                      view->parent == VIEW(REQ_VIEW_TREE))) {
2511                         int line;
2513                         view = view->parent;
2514                         line = view->lineno;
2515                         move_view(view, request);
2516                         if (view_is_displayed(view))
2517                                 update_view_title(view);
2518                         if (line != view->lineno)
2519                                 view->ops->request(view, REQ_ENTER,
2520                                                    &view->line[view->lineno]);
2522                 } else {
2523                         move_view(view, request);
2524                 }
2525                 break;
2527         case REQ_VIEW_NEXT:
2528         {
2529                 int nviews = displayed_views();
2530                 int next_view = (current_view + 1) % nviews;
2532                 if (next_view == current_view) {
2533                         report("Only one view is displayed");
2534                         break;
2535                 }
2537                 current_view = next_view;
2538                 /* Blur out the title of the previous view. */
2539                 update_view_title(view);
2540                 report("");
2541                 break;
2542         }
2543         case REQ_REFRESH:
2544                 report("Refreshing is not yet supported for the %s view", view->name);
2545                 break;
2547         case REQ_TOGGLE_LINENO:
2548                 opt_line_number = !opt_line_number;
2549                 redraw_display();
2550                 break;
2552         case REQ_TOGGLE_DATE:
2553                 opt_date = !opt_date;
2554                 redraw_display();
2555                 break;
2557         case REQ_TOGGLE_AUTHOR:
2558                 opt_author = !opt_author;
2559                 redraw_display();
2560                 break;
2562         case REQ_TOGGLE_REV_GRAPH:
2563                 opt_rev_graph = !opt_rev_graph;
2564                 redraw_display();
2565                 break;
2567         case REQ_TOGGLE_REFS:
2568                 opt_show_refs = !opt_show_refs;
2569                 redraw_display();
2570                 break;
2572         case REQ_PROMPT:
2573                 /* Always reload^Wrerun commands from the prompt. */
2574                 open_view(view, opt_request, OPEN_RELOAD);
2575                 break;
2577         case REQ_SEARCH:
2578         case REQ_SEARCH_BACK:
2579                 search_view(view, request);
2580                 break;
2582         case REQ_FIND_NEXT:
2583         case REQ_FIND_PREV:
2584                 find_next(view, request);
2585                 break;
2587         case REQ_STOP_LOADING:
2588                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2589                         view = &views[i];
2590                         if (view->pipe)
2591                                 report("Stopped loading the %s view", view->name),
2592                         end_update(view);
2593                 }
2594                 break;
2596         case REQ_SHOW_VERSION:
2597                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2598                 return TRUE;
2600         case REQ_SCREEN_RESIZE:
2601                 resize_display();
2602                 /* Fall-through */
2603         case REQ_SCREEN_REDRAW:
2604                 redraw_display();
2605                 break;
2607         case REQ_EDIT:
2608                 report("Nothing to edit");
2609                 break;
2612         case REQ_ENTER:
2613                 report("Nothing to enter");
2614                 break;
2617         case REQ_VIEW_CLOSE:
2618                 /* XXX: Mark closed views by letting view->parent point to the
2619                  * view itself. Parents to closed view should never be
2620                  * followed. */
2621                 if (view->parent &&
2622                     view->parent->parent != view->parent) {
2623                         memset(display, 0, sizeof(display));
2624                         current_view = 0;
2625                         display[current_view] = view->parent;
2626                         view->parent = view;
2627                         resize_display();
2628                         redraw_display();
2629                         break;
2630                 }
2631                 /* Fall-through */
2632         case REQ_QUIT:
2633                 return FALSE;
2635         default:
2636                 /* An unknown key will show most commonly used commands. */
2637                 report("Unknown key, press 'h' for help");
2638                 return TRUE;
2639         }
2641         return TRUE;
2645 /*
2646  * Pager backend
2647  */
2649 static bool
2650 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2652         char *text = line->data;
2653         enum line_type type = line->type;
2654         int attr;
2656         wmove(view->win, lineno, 0);
2658         if (selected) {
2659                 type = LINE_CURSOR;
2660                 wchgat(view->win, -1, 0, type, NULL);
2661         }
2663         attr = get_line_attr(type);
2664         wattrset(view->win, attr);
2666         if (opt_line_number || opt_tab_size < TABSIZE) {
2667                 static char spaces[] = "                    ";
2668                 int col_offset = 0, col = 0;
2670                 if (opt_line_number) {
2671                         unsigned long real_lineno = view->offset + lineno + 1;
2673                         if (real_lineno == 1 ||
2674                             (real_lineno % opt_num_interval) == 0) {
2675                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2677                         } else {
2678                                 waddnstr(view->win, spaces,
2679                                          MIN(view->digits, STRING_SIZE(spaces)));
2680                         }
2681                         waddstr(view->win, ": ");
2682                         col_offset = view->digits + 2;
2683                 }
2685                 while (text && col_offset + col < view->width) {
2686                         int cols_max = view->width - col_offset - col;
2687                         char *pos = text;
2688                         int cols;
2690                         if (*text == '\t') {
2691                                 text++;
2692                                 assert(sizeof(spaces) > TABSIZE);
2693                                 pos = spaces;
2694                                 cols = opt_tab_size - (col % opt_tab_size);
2696                         } else {
2697                                 text = strchr(text, '\t');
2698                                 cols = line ? text - pos : strlen(pos);
2699                         }
2701                         waddnstr(view->win, pos, MIN(cols, cols_max));
2702                         col += cols;
2703                 }
2705         } else {
2706                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2708                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
2709         }
2711         return TRUE;
2714 static bool
2715 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2717         char refbuf[SIZEOF_STR];
2718         char *ref = NULL;
2719         FILE *pipe;
2721         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2722                 return TRUE;
2724         pipe = popen(refbuf, "r");
2725         if (!pipe)
2726                 return TRUE;
2728         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2729                 ref = chomp_string(ref);
2730         pclose(pipe);
2732         if (!ref || !*ref)
2733                 return TRUE;
2735         /* This is the only fatal call, since it can "corrupt" the buffer. */
2736         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2737                 return FALSE;
2739         return TRUE;
2742 static void
2743 add_pager_refs(struct view *view, struct line *line)
2745         char buf[SIZEOF_STR];
2746         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2747         struct ref **refs;
2748         size_t bufpos = 0, refpos = 0;
2749         const char *sep = "Refs: ";
2750         bool is_tag = FALSE;
2752         assert(line->type == LINE_COMMIT);
2754         refs = get_refs(commit_id);
2755         if (!refs) {
2756                 if (view == VIEW(REQ_VIEW_DIFF))
2757                         goto try_add_describe_ref;
2758                 return;
2759         }
2761         do {
2762                 struct ref *ref = refs[refpos];
2763                 char *fmt = ref->tag    ? "%s[%s]" :
2764                             ref->remote ? "%s<%s>" : "%s%s";
2766                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2767                         return;
2768                 sep = ", ";
2769                 if (ref->tag)
2770                         is_tag = TRUE;
2771         } while (refs[refpos++]->next);
2773         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2774 try_add_describe_ref:
2775                 /* Add <tag>-g<commit_id> "fake" reference. */
2776                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2777                         return;
2778         }
2780         if (bufpos == 0)
2781                 return;
2783         if (!realloc_lines(view, view->line_size + 1))
2784                 return;
2786         add_line_text(view, buf, LINE_PP_REFS);
2789 static bool
2790 pager_read(struct view *view, char *data)
2792         struct line *line;
2794         if (!data)
2795                 return TRUE;
2797         line = add_line_text(view, data, get_line_type(data));
2798         if (!line)
2799                 return FALSE;
2801         if (line->type == LINE_COMMIT &&
2802             (view == VIEW(REQ_VIEW_DIFF) ||
2803              view == VIEW(REQ_VIEW_LOG)))
2804                 add_pager_refs(view, line);
2806         return TRUE;
2809 static enum request
2810 pager_request(struct view *view, enum request request, struct line *line)
2812         int split = 0;
2814         if (request != REQ_ENTER)
2815                 return request;
2817         if (line->type == LINE_COMMIT &&
2818            (view == VIEW(REQ_VIEW_LOG) ||
2819             view == VIEW(REQ_VIEW_PAGER))) {
2820                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2821                 split = 1;
2822         }
2824         /* Always scroll the view even if it was split. That way
2825          * you can use Enter to scroll through the log view and
2826          * split open each commit diff. */
2827         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2829         /* FIXME: A minor workaround. Scrolling the view will call report("")
2830          * but if we are scrolling a non-current view this won't properly
2831          * update the view title. */
2832         if (split)
2833                 update_view_title(view);
2835         return REQ_NONE;
2838 static bool
2839 pager_grep(struct view *view, struct line *line)
2841         regmatch_t pmatch;
2842         char *text = line->data;
2844         if (!*text)
2845                 return FALSE;
2847         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2848                 return FALSE;
2850         return TRUE;
2853 static void
2854 pager_select(struct view *view, struct line *line)
2856         if (line->type == LINE_COMMIT) {
2857                 char *text = (char *)line->data + STRING_SIZE("commit ");
2859                 if (view != VIEW(REQ_VIEW_PAGER))
2860                         string_copy_rev(view->ref, text);
2861                 string_copy_rev(ref_commit, text);
2862         }
2865 static struct view_ops pager_ops = {
2866         "line",
2867         NULL,
2868         pager_read,
2869         pager_draw,
2870         pager_request,
2871         pager_grep,
2872         pager_select,
2873 };
2876 /*
2877  * Help backend
2878  */
2880 static bool
2881 help_open(struct view *view)
2883         char buf[BUFSIZ];
2884         int lines = ARRAY_SIZE(req_info) + 2;
2885         int i;
2887         if (view->lines > 0)
2888                 return TRUE;
2890         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2891                 if (!req_info[i].request)
2892                         lines++;
2894         lines += run_requests + 1;
2896         view->line = calloc(lines, sizeof(*view->line));
2897         if (!view->line)
2898                 return FALSE;
2900         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2902         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2903                 char *key;
2905                 if (req_info[i].request == REQ_NONE)
2906                         continue;
2908                 if (!req_info[i].request) {
2909                         add_line_text(view, "", LINE_DEFAULT);
2910                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2911                         continue;
2912                 }
2914                 key = get_key(req_info[i].request);
2915                 if (!*key)
2916                         key = "(no key defined)";
2918                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2919                         continue;
2921                 add_line_text(view, buf, LINE_DEFAULT);
2922         }
2924         if (run_requests) {
2925                 add_line_text(view, "", LINE_DEFAULT);
2926                 add_line_text(view, "External commands:", LINE_DEFAULT);
2927         }
2929         for (i = 0; i < run_requests; i++) {
2930                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2931                 char *key;
2933                 if (!req)
2934                         continue;
2936                 key = get_key_name(req->key);
2937                 if (!*key)
2938                         key = "(no key defined)";
2940                 if (!string_format(buf, "    %-10s %-14s `%s`",
2941                                    keymap_table[req->keymap].name,
2942                                    key, req->cmd))
2943                         continue;
2945                 add_line_text(view, buf, LINE_DEFAULT);
2946         }
2948         return TRUE;
2951 static struct view_ops help_ops = {
2952         "line",
2953         help_open,
2954         NULL,
2955         pager_draw,
2956         pager_request,
2957         pager_grep,
2958         pager_select,
2959 };
2962 /*
2963  * Tree backend
2964  */
2966 struct tree_stack_entry {
2967         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2968         unsigned long lineno;           /* Line number to restore */
2969         char *name;                     /* Position of name in opt_path */
2970 };
2972 /* The top of the path stack. */
2973 static struct tree_stack_entry *tree_stack = NULL;
2974 unsigned long tree_lineno = 0;
2976 static void
2977 pop_tree_stack_entry(void)
2979         struct tree_stack_entry *entry = tree_stack;
2981         tree_lineno = entry->lineno;
2982         entry->name[0] = 0;
2983         tree_stack = entry->prev;
2984         free(entry);
2987 static void
2988 push_tree_stack_entry(char *name, unsigned long lineno)
2990         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2991         size_t pathlen = strlen(opt_path);
2993         if (!entry)
2994                 return;
2996         entry->prev = tree_stack;
2997         entry->name = opt_path + pathlen;
2998         tree_stack = entry;
3000         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3001                 pop_tree_stack_entry();
3002                 return;
3003         }
3005         /* Move the current line to the first tree entry. */
3006         tree_lineno = 1;
3007         entry->lineno = lineno;
3010 /* Parse output from git-ls-tree(1):
3011  *
3012  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3013  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3014  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3015  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3016  */
3018 #define SIZEOF_TREE_ATTR \
3019         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3021 #define TREE_UP_FORMAT "040000 tree %s\t.."
3023 static int
3024 tree_compare_entry(enum line_type type1, char *name1,
3025                    enum line_type type2, char *name2)
3027         if (type1 != type2) {
3028                 if (type1 == LINE_TREE_DIR)
3029                         return -1;
3030                 return 1;
3031         }
3033         return strcmp(name1, name2);
3036 static bool
3037 tree_read(struct view *view, char *text)
3039         size_t textlen = text ? strlen(text) : 0;
3040         char buf[SIZEOF_STR];
3041         unsigned long pos;
3042         enum line_type type;
3043         bool first_read = view->lines == 0;
3045         if (textlen <= SIZEOF_TREE_ATTR)
3046                 return FALSE;
3048         type = text[STRING_SIZE("100644 ")] == 't'
3049              ? LINE_TREE_DIR : LINE_TREE_FILE;
3051         if (first_read) {
3052                 /* Add path info line */
3053                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3054                     !realloc_lines(view, view->line_size + 1) ||
3055                     !add_line_text(view, buf, LINE_DEFAULT))
3056                         return FALSE;
3058                 /* Insert "link" to parent directory. */
3059                 if (*opt_path) {
3060                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3061                             !realloc_lines(view, view->line_size + 1) ||
3062                             !add_line_text(view, buf, LINE_TREE_DIR))
3063                                 return FALSE;
3064                 }
3065         }
3067         /* Strip the path part ... */
3068         if (*opt_path) {
3069                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3070                 size_t striplen = strlen(opt_path);
3071                 char *path = text + SIZEOF_TREE_ATTR;
3073                 if (pathlen > striplen)
3074                         memmove(path, path + striplen,
3075                                 pathlen - striplen + 1);
3076         }
3078         /* Skip "Directory ..." and ".." line. */
3079         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3080                 struct line *line = &view->line[pos];
3081                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3082                 char *path2 = text + SIZEOF_TREE_ATTR;
3083                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3085                 if (cmp <= 0)
3086                         continue;
3088                 text = strdup(text);
3089                 if (!text)
3090                         return FALSE;
3092                 if (view->lines > pos)
3093                         memmove(&view->line[pos + 1], &view->line[pos],
3094                                 (view->lines - pos) * sizeof(*line));
3096                 line = &view->line[pos];
3097                 line->data = text;
3098                 line->type = type;
3099                 view->lines++;
3100                 return TRUE;
3101         }
3103         if (!add_line_text(view, text, type))
3104                 return FALSE;
3106         if (tree_lineno > view->lineno) {
3107                 view->lineno = tree_lineno;
3108                 tree_lineno = 0;
3109         }
3111         return TRUE;
3114 static enum request
3115 tree_request(struct view *view, enum request request, struct line *line)
3117         enum open_flags flags;
3119         if (request == REQ_TREE_PARENT) {
3120                 if (*opt_path) {
3121                         /* fake 'cd  ..' */
3122                         request = REQ_ENTER;
3123                         line = &view->line[1];
3124                 } else {
3125                         /* quit view if at top of tree */
3126                         return REQ_VIEW_CLOSE;
3127                 }
3128         }
3129         if (request != REQ_ENTER)
3130                 return request;
3132         /* Cleanup the stack if the tree view is at a different tree. */
3133         while (!*opt_path && tree_stack)
3134                 pop_tree_stack_entry();
3136         switch (line->type) {
3137         case LINE_TREE_DIR:
3138                 /* Depending on whether it is a subdir or parent (updir?) link
3139                  * mangle the path buffer. */
3140                 if (line == &view->line[1] && *opt_path) {
3141                         pop_tree_stack_entry();
3143                 } else {
3144                         char *data = line->data;
3145                         char *basename = data + SIZEOF_TREE_ATTR;
3147                         push_tree_stack_entry(basename, view->lineno);
3148                 }
3150                 /* Trees and subtrees share the same ID, so they are not not
3151                  * unique like blobs. */
3152                 flags = OPEN_RELOAD;
3153                 request = REQ_VIEW_TREE;
3154                 break;
3156         case LINE_TREE_FILE:
3157                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3158                 request = REQ_VIEW_BLOB;
3159                 break;
3161         default:
3162                 return TRUE;
3163         }
3165         open_view(view, request, flags);
3166         if (request == REQ_VIEW_TREE) {
3167                 view->lineno = tree_lineno;
3168         }
3170         return REQ_NONE;
3173 static void
3174 tree_select(struct view *view, struct line *line)
3176         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3178         if (line->type == LINE_TREE_FILE) {
3179                 string_copy_rev(ref_blob, text);
3181         } else if (line->type != LINE_TREE_DIR) {
3182                 return;
3183         }
3185         string_copy_rev(view->ref, text);
3188 static struct view_ops tree_ops = {
3189         "file",
3190         NULL,
3191         tree_read,
3192         pager_draw,
3193         tree_request,
3194         pager_grep,
3195         tree_select,
3196 };
3198 static bool
3199 blob_read(struct view *view, char *line)
3201         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3204 static struct view_ops blob_ops = {
3205         "line",
3206         NULL,
3207         blob_read,
3208         pager_draw,
3209         pager_request,
3210         pager_grep,
3211         pager_select,
3212 };
3215 /*
3216  * Status backend
3217  */
3219 struct status {
3220         char status;
3221         struct {
3222                 mode_t mode;
3223                 char rev[SIZEOF_REV];
3224                 char name[SIZEOF_STR];
3225         } old;
3226         struct {
3227                 mode_t mode;
3228                 char rev[SIZEOF_REV];
3229                 char name[SIZEOF_STR];
3230         } new;
3231 };
3233 static struct status stage_status;
3234 static enum line_type stage_line_type;
3236 /* Get fields from the diff line:
3237  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3238  */
3239 static inline bool
3240 status_get_diff(struct status *file, char *buf, size_t bufsize)
3242         char *old_mode = buf +  1;
3243         char *new_mode = buf +  8;
3244         char *old_rev  = buf + 15;
3245         char *new_rev  = buf + 56;
3246         char *status   = buf + 97;
3248         if (bufsize < 99 ||
3249             old_mode[-1] != ':' ||
3250             new_mode[-1] != ' ' ||
3251             old_rev[-1]  != ' ' ||
3252             new_rev[-1]  != ' ' ||
3253             status[-1]   != ' ')
3254                 return FALSE;
3256         file->status = *status;
3258         string_copy_rev(file->old.rev, old_rev);
3259         string_copy_rev(file->new.rev, new_rev);
3261         file->old.mode = strtoul(old_mode, NULL, 8);
3262         file->new.mode = strtoul(new_mode, NULL, 8);
3264         file->old.name[0] = file->new.name[0] = 0;
3266         return TRUE;
3269 static bool
3270 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3272         struct status *file = NULL;
3273         struct status *unmerged = NULL;
3274         char buf[SIZEOF_STR * 4];
3275         size_t bufsize = 0;
3276         FILE *pipe;
3278         pipe = popen(cmd, "r");
3279         if (!pipe)
3280                 return FALSE;
3282         add_line_data(view, NULL, type);
3284         while (!feof(pipe) && !ferror(pipe)) {
3285                 char *sep;
3286                 size_t readsize;
3288                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3289                 if (!readsize)
3290                         break;
3291                 bufsize += readsize;
3293                 /* Process while we have NUL chars. */
3294                 while ((sep = memchr(buf, 0, bufsize))) {
3295                         size_t sepsize = sep - buf + 1;
3297                         if (!file) {
3298                                 if (!realloc_lines(view, view->line_size + 1))
3299                                         goto error_out;
3301                                 file = calloc(1, sizeof(*file));
3302                                 if (!file)
3303                                         goto error_out;
3305                                 add_line_data(view, file, type);
3306                         }
3308                         /* Parse diff info part. */
3309                         if (!diff) {
3310                                 file->status = '?';
3312                         } else if (!file->status) {
3313                                 if (!status_get_diff(file, buf, sepsize))
3314                                         goto error_out;
3316                                 bufsize -= sepsize;
3317                                 memmove(buf, sep + 1, bufsize);
3319                                 sep = memchr(buf, 0, bufsize);
3320                                 if (!sep)
3321                                         break;
3322                                 sepsize = sep - buf + 1;
3324                                 /* Collapse all 'M'odified entries that
3325                                  * follow a associated 'U'nmerged entry.
3326                                  */
3327                                 if (file->status == 'U') {
3328                                         unmerged = file;
3330                                 } else if (unmerged) {
3331                                         int collapse = !strcmp(buf, unmerged->new.name);
3333                                         unmerged = NULL;
3334                                         if (collapse) {
3335                                                 free(file);
3336                                                 view->lines--;
3337                                                 continue;
3338                                         }
3339                                 }
3340                         }
3342                         /* Grab the old name for rename/copy. */
3343                         if (!*file->old.name &&
3344                             (file->status == 'R' || file->status == 'C')) {
3345                                 sepsize = sep - buf + 1;
3346                                 string_ncopy(file->old.name, buf, sepsize);
3347                                 bufsize -= sepsize;
3348                                 memmove(buf, sep + 1, bufsize);
3350                                 sep = memchr(buf, 0, bufsize);
3351                                 if (!sep)
3352                                         break;
3353                                 sepsize = sep - buf + 1;
3354                         }
3356                         /* git-ls-files just delivers a NUL separated
3357                          * list of file names similar to the second half
3358                          * of the git-diff-* output. */
3359                         string_ncopy(file->new.name, buf, sepsize);
3360                         if (!*file->old.name)
3361                                 string_copy(file->old.name, file->new.name);
3362                         bufsize -= sepsize;
3363                         memmove(buf, sep + 1, bufsize);
3364                         file = NULL;
3365                 }
3366         }
3368         if (ferror(pipe)) {
3369 error_out:
3370                 pclose(pipe);
3371                 return FALSE;
3372         }
3374         if (!view->line[view->lines - 1].data)
3375                 add_line_data(view, NULL, LINE_STAT_NONE);
3377         pclose(pipe);
3378         return TRUE;
3381 /* Don't show unmerged entries in the staged section. */
3382 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3383 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3384 #define STATUS_LIST_OTHER_CMD \
3385         "git ls-files -z --others --exclude-per-directory=.gitignore"
3387 #define STATUS_DIFF_INDEX_SHOW_CMD \
3388         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3390 #define STATUS_DIFF_FILES_SHOW_CMD \
3391         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3393 /* First parse staged info using git-diff-index(1), then parse unstaged
3394  * info using git-diff-files(1), and finally untracked files using
3395  * git-ls-files(1). */
3396 static bool
3397 status_open(struct view *view)
3399         struct stat statbuf;
3400         char exclude[SIZEOF_STR];
3401         char cmd[SIZEOF_STR];
3402         unsigned long prev_lineno = view->lineno;
3403         size_t i;
3405         for (i = 0; i < view->lines; i++)
3406                 free(view->line[i].data);
3407         free(view->line);
3408         view->lines = view->line_size = view->lineno = 0;
3409         view->line = NULL;
3411         if (!realloc_lines(view, view->line_size + 6))
3412                 return FALSE;
3414         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3415                 return FALSE;
3417         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3419         if (stat(exclude, &statbuf) >= 0) {
3420                 size_t cmdsize = strlen(cmd);
3422                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3423                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3424                         return FALSE;
3425         }
3427         system("git update-index -q --refresh");
3429         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3430             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3431             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3432                 return FALSE;
3434         /* If all went well restore the previous line number to stay in
3435          * the context. */
3436         if (prev_lineno < view->lines)
3437                 view->lineno = prev_lineno;
3438         else
3439                 view->lineno = view->lines - 1;
3441         return TRUE;
3444 static bool
3445 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3447         struct status *status = line->data;
3448         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3450         wmove(view->win, lineno, 0);
3452         if (selected) {
3453                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3454                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3455                 tilde_attr = -1;
3457         } else if (!status && line->type != LINE_STAT_NONE) {
3458                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3459                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3461         } else {
3462                 wattrset(view->win, get_line_attr(line->type));
3463         }
3465         if (!status) {
3466                 char *text;
3468                 switch (line->type) {
3469                 case LINE_STAT_STAGED:
3470                         text = "Changes to be committed:";
3471                         break;
3473                 case LINE_STAT_UNSTAGED:
3474                         text = "Changed but not updated:";
3475                         break;
3477                 case LINE_STAT_UNTRACKED:
3478                         text = "Untracked files:";
3479                         break;
3481                 case LINE_STAT_NONE:
3482                         text = "    (no files)";
3483                         break;
3485                 default:
3486                         return FALSE;
3487                 }
3489                 draw_text(view, text, view->width, 0, TRUE, tilde_attr);
3490                 return TRUE;
3491         }
3493         waddch(view->win, status->status);
3494         if (!selected)
3495                 wattrset(view->win, A_NORMAL);
3496         wmove(view->win, lineno, 4);
3497         if (view->width < 5)
3498                 return TRUE;
3500         draw_text(view, status->new.name, view->width - 5, 5, TRUE, tilde_attr);
3501         return TRUE;
3504 static enum request
3505 status_enter(struct view *view, struct line *line)
3507         struct status *status = line->data;
3508         char oldpath[SIZEOF_STR] = "";
3509         char newpath[SIZEOF_STR] = "";
3510         char *info;
3511         size_t cmdsize = 0;
3513         if (line->type == LINE_STAT_NONE ||
3514             (!status && line[1].type == LINE_STAT_NONE)) {
3515                 report("No file to diff");
3516                 return REQ_NONE;
3517         }
3519         if (status) {
3520                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
3521                         return REQ_QUIT;
3522                 /* Diffs for unmerged entries are empty when pasing the
3523                  * new path, so leave it empty. */
3524                 if (status->status != 'U' &&
3525                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
3526                         return REQ_QUIT;
3527         }
3529         if (opt_cdup[0] &&
3530             line->type != LINE_STAT_UNTRACKED &&
3531             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3532                 return REQ_QUIT;
3534         switch (line->type) {
3535         case LINE_STAT_STAGED:
3536                 if (!string_format_from(opt_cmd, &cmdsize,
3537                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
3538                         return REQ_QUIT;
3539                 if (status)
3540                         info = "Staged changes to %s";
3541                 else
3542                         info = "Staged changes";
3543                 break;
3545         case LINE_STAT_UNSTAGED:
3546                 if (!string_format_from(opt_cmd, &cmdsize,
3547                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
3548                         return REQ_QUIT;
3549                 if (status)
3550                         info = "Unstaged changes to %s";
3551                 else
3552                         info = "Unstaged changes";
3553                 break;
3555         case LINE_STAT_UNTRACKED:
3556                 if (opt_pipe)
3557                         return REQ_QUIT;
3560                 if (!status) {
3561                         report("No file to show");
3562                         return REQ_NONE;
3563                 }
3565                 opt_pipe = fopen(status->new.name, "r");
3566                 info = "Untracked file %s";
3567                 break;
3569         default:
3570                 die("line type %d not handled in switch", line->type);
3571         }
3573         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3574         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3575                 if (status) {
3576                         stage_status = *status;
3577                 } else {
3578                         memset(&stage_status, 0, sizeof(stage_status));
3579                 }
3581                 stage_line_type = line->type;
3582                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
3583         }
3585         return REQ_NONE;
3589 static bool
3590 status_update_file(struct view *view, struct status *status, enum line_type type)
3592         char cmd[SIZEOF_STR];
3593         char buf[SIZEOF_STR];
3594         size_t cmdsize = 0;
3595         size_t bufsize = 0;
3596         size_t written = 0;
3597         FILE *pipe;
3599         if (opt_cdup[0] &&
3600             type != LINE_STAT_UNTRACKED &&
3601             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3602                 return FALSE;
3604         switch (type) {
3605         case LINE_STAT_STAGED:
3606                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3607                                         status->old.mode,
3608                                         status->old.rev,
3609                                         status->old.name, 0))
3610                         return FALSE;
3612                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3613                 break;
3615         case LINE_STAT_UNSTAGED:
3616         case LINE_STAT_UNTRACKED:
3617                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
3618                         return FALSE;
3620                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3621                 break;
3623         default:
3624                 die("line type %d not handled in switch", type);
3625         }
3627         pipe = popen(cmd, "w");
3628         if (!pipe)
3629                 return FALSE;
3631         while (!ferror(pipe) && written < bufsize) {
3632                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3633         }
3635         pclose(pipe);
3637         if (written != bufsize)
3638                 return FALSE;
3640         return TRUE;
3643 static void
3644 status_update(struct view *view)
3646         struct line *line = &view->line[view->lineno];
3648         assert(view->lines);
3650         if (!line->data) {
3651                 while (++line < view->line + view->lines && line->data) {
3652                         if (!status_update_file(view, line->data, line->type))
3653                                 report("Failed to update file status");
3654                 }
3656                 if (!line[-1].data) {
3657                         report("Nothing to update");
3658                         return;
3659                 }
3661         } else if (!status_update_file(view, line->data, line->type)) {
3662                 report("Failed to update file status");
3663         }
3666 static enum request
3667 status_request(struct view *view, enum request request, struct line *line)
3669         struct status *status = line->data;
3671         switch (request) {
3672         case REQ_STATUS_UPDATE:
3673                 status_update(view);
3674                 break;
3676         case REQ_STATUS_MERGE:
3677                 if (!status || status->status != 'U') {
3678                         report("Merging only possible for files with unmerged status ('U').");
3679                         return REQ_NONE;
3680                 }
3681                 open_mergetool(status->new.name);
3682                 break;
3684         case REQ_EDIT:
3685                 if (!status)
3686                         return request;
3688                 open_editor(status->status != '?', status->new.name);
3689                 break;
3691         case REQ_ENTER:
3692                 /* After returning the status view has been split to
3693                  * show the stage view. No further reloading is
3694                  * necessary. */
3695                 status_enter(view, line);
3696                 return REQ_NONE;
3698         case REQ_REFRESH:
3699                 /* Simply reload the view. */
3700                 break;
3702         default:
3703                 return request;
3704         }
3706         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3708         return REQ_NONE;
3711 static void
3712 status_select(struct view *view, struct line *line)
3714         struct status *status = line->data;
3715         char file[SIZEOF_STR] = "all files";
3716         char *text;
3717         char *key;
3719         if (status && !string_format(file, "'%s'", status->new.name))
3720                 return;
3722         if (!status && line[1].type == LINE_STAT_NONE)
3723                 line++;
3725         switch (line->type) {
3726         case LINE_STAT_STAGED:
3727                 text = "Press %s to unstage %s for commit";
3728                 break;
3730         case LINE_STAT_UNSTAGED:
3731                 text = "Press %s to stage %s for commit";
3732                 break;
3734         case LINE_STAT_UNTRACKED:
3735                 text = "Press %s to stage %s for addition";
3736                 break;
3738         case LINE_STAT_NONE:
3739                 text = "Nothing to update";
3740                 break;
3742         default:
3743                 die("line type %d not handled in switch", line->type);
3744         }
3746         if (status && status->status == 'U') {
3747                 text = "Press %s to resolve conflict in %s";
3748                 key = get_key(REQ_STATUS_MERGE);
3750         } else {
3751                 key = get_key(REQ_STATUS_UPDATE);
3752         }
3754         string_format(view->ref, text, key, file);
3757 static bool
3758 status_grep(struct view *view, struct line *line)
3760         struct status *status = line->data;
3761         enum { S_STATUS, S_NAME, S_END } state;
3762         char buf[2] = "?";
3763         regmatch_t pmatch;
3765         if (!status)
3766                 return FALSE;
3768         for (state = S_STATUS; state < S_END; state++) {
3769                 char *text;
3771                 switch (state) {
3772                 case S_NAME:    text = status->new.name;        break;
3773                 case S_STATUS:
3774                         buf[0] = status->status;
3775                         text = buf;
3776                         break;
3778                 default:
3779                         return FALSE;
3780                 }
3782                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3783                         return TRUE;
3784         }
3786         return FALSE;
3789 static struct view_ops status_ops = {
3790         "file",
3791         status_open,
3792         NULL,
3793         status_draw,
3794         status_request,
3795         status_grep,
3796         status_select,
3797 };
3800 static bool
3801 stage_diff_line(FILE *pipe, struct line *line)
3803         char *buf = line->data;
3804         size_t bufsize = strlen(buf);
3805         size_t written = 0;
3807         while (!ferror(pipe) && written < bufsize) {
3808                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3809         }
3811         fputc('\n', pipe);
3813         return written == bufsize;
3816 static struct line *
3817 stage_diff_hdr(struct view *view, struct line *line)
3819         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3820         struct line *diff_hdr;
3822         if (line->type == LINE_DIFF_CHUNK)
3823                 diff_hdr = line - 1;
3824         else
3825                 diff_hdr = view->line + 1;
3827         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3828                 if (diff_hdr->type == LINE_DIFF_HEADER)
3829                         return diff_hdr;
3831                 diff_hdr += diff_hdr_dir;
3832         }
3834         return NULL;
3837 static bool
3838 stage_update_chunk(struct view *view, struct line *line)
3840         char cmd[SIZEOF_STR];
3841         size_t cmdsize = 0;
3842         struct line *diff_hdr, *diff_chunk, *diff_end;
3843         FILE *pipe;
3845         diff_hdr = stage_diff_hdr(view, line);
3846         if (!diff_hdr)
3847                 return FALSE;
3849         if (opt_cdup[0] &&
3850             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3851                 return FALSE;
3853         if (!string_format_from(cmd, &cmdsize,
3854                                 "git apply --cached %s - && "
3855                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3856                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3857                 return FALSE;
3859         pipe = popen(cmd, "w");
3860         if (!pipe)
3861                 return FALSE;
3863         diff_end = view->line + view->lines;
3864         if (line->type != LINE_DIFF_CHUNK) {
3865                 diff_chunk = diff_hdr;
3867         } else {
3868                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3869                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3870                             diff_chunk->type == LINE_DIFF_HEADER)
3871                                 diff_end = diff_chunk;
3873                 diff_chunk = line;
3875                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3876                         switch (diff_hdr->type) {
3877                         case LINE_DIFF_HEADER:
3878                         case LINE_DIFF_INDEX:
3879                         case LINE_DIFF_ADD:
3880                         case LINE_DIFF_DEL:
3881                                 break;
3883                         default:
3884                                 diff_hdr++;
3885                                 continue;
3886                         }
3888                         if (!stage_diff_line(pipe, diff_hdr++)) {
3889                                 pclose(pipe);
3890                                 return FALSE;
3891                         }
3892                 }
3893         }
3895         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3896                 diff_chunk++;
3898         pclose(pipe);
3900         if (diff_chunk != diff_end)
3901                 return FALSE;
3903         return TRUE;
3906 static void
3907 stage_update(struct view *view, struct line *line)
3909         if (stage_line_type != LINE_STAT_UNTRACKED &&
3910             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3911                 if (!stage_update_chunk(view, line)) {
3912                         report("Failed to apply chunk");
3913                         return;
3914                 }
3916         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3917                 report("Failed to update file");
3918                 return;
3919         }
3921         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3923         view = VIEW(REQ_VIEW_STATUS);
3924         if (view_is_displayed(view))
3925                 status_enter(view, &view->line[view->lineno]);
3928 static enum request
3929 stage_request(struct view *view, enum request request, struct line *line)
3931         switch (request) {
3932         case REQ_STATUS_UPDATE:
3933                 stage_update(view, line);
3934                 break;
3936         case REQ_EDIT:
3937                 if (!stage_status.new.name[0])
3938                         return request;
3940                 open_editor(stage_status.status != '?', stage_status.new.name);
3941                 break;
3943         case REQ_ENTER:
3944                 pager_request(view, request, line);
3945                 break;
3947         default:
3948                 return request;
3949         }
3951         return REQ_NONE;
3954 static struct view_ops stage_ops = {
3955         "line",
3956         NULL,
3957         pager_read,
3958         pager_draw,
3959         stage_request,
3960         pager_grep,
3961         pager_select,
3962 };
3965 /*
3966  * Revision graph
3967  */
3969 struct commit {
3970         char id[SIZEOF_REV];            /* SHA1 ID. */
3971         char title[128];                /* First line of the commit message. */
3972         char author[75];                /* Author of the commit. */
3973         struct tm time;                 /* Date from the author ident. */
3974         struct ref **refs;              /* Repository references. */
3975         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3976         size_t graph_size;              /* The width of the graph array. */
3977 };
3979 /* Size of rev graph with no  "padding" columns */
3980 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3982 struct rev_graph {
3983         struct rev_graph *prev, *next, *parents;
3984         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3985         size_t size;
3986         struct commit *commit;
3987         size_t pos;
3988         unsigned int boundary:1;
3989 };
3991 /* Parents of the commit being visualized. */
3992 static struct rev_graph graph_parents[4];
3994 /* The current stack of revisions on the graph. */
3995 static struct rev_graph graph_stacks[4] = {
3996         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3997         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3998         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3999         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4000 };
4002 static inline bool
4003 graph_parent_is_merge(struct rev_graph *graph)
4005         return graph->parents->size > 1;
4008 static inline void
4009 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4011         struct commit *commit = graph->commit;
4013         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4014                 commit->graph[commit->graph_size++] = symbol;
4017 static void
4018 done_rev_graph(struct rev_graph *graph)
4020         if (graph_parent_is_merge(graph) &&
4021             graph->pos < graph->size - 1 &&
4022             graph->next->size == graph->size + graph->parents->size - 1) {
4023                 size_t i = graph->pos + graph->parents->size - 1;
4025                 graph->commit->graph_size = i * 2;
4026                 while (i < graph->next->size - 1) {
4027                         append_to_rev_graph(graph, ' ');
4028                         append_to_rev_graph(graph, '\\');
4029                         i++;
4030                 }
4031         }
4033         graph->size = graph->pos = 0;
4034         graph->commit = NULL;
4035         memset(graph->parents, 0, sizeof(*graph->parents));
4038 static void
4039 push_rev_graph(struct rev_graph *graph, char *parent)
4041         int i;
4043         /* "Collapse" duplicate parents lines.
4044          *
4045          * FIXME: This needs to also update update the drawn graph but
4046          * for now it just serves as a method for pruning graph lines. */
4047         for (i = 0; i < graph->size; i++)
4048                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4049                         return;
4051         if (graph->size < SIZEOF_REVITEMS) {
4052                 string_copy_rev(graph->rev[graph->size++], parent);
4053         }
4056 static chtype
4057 get_rev_graph_symbol(struct rev_graph *graph)
4059         chtype symbol;
4061         if (graph->boundary)
4062                 symbol = REVGRAPH_BOUND;
4063         else if (graph->parents->size == 0)
4064                 symbol = REVGRAPH_INIT;
4065         else if (graph_parent_is_merge(graph))
4066                 symbol = REVGRAPH_MERGE;
4067         else if (graph->pos >= graph->size)
4068                 symbol = REVGRAPH_BRANCH;
4069         else
4070                 symbol = REVGRAPH_COMMIT;
4072         return symbol;
4075 static void
4076 draw_rev_graph(struct rev_graph *graph)
4078         struct rev_filler {
4079                 chtype separator, line;
4080         };
4081         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4082         static struct rev_filler fillers[] = {
4083                 { ' ',  REVGRAPH_LINE },
4084                 { '`',  '.' },
4085                 { '\'', ' ' },
4086                 { '/',  ' ' },
4087         };
4088         chtype symbol = get_rev_graph_symbol(graph);
4089         struct rev_filler *filler;
4090         size_t i;
4092         filler = &fillers[DEFAULT];
4094         for (i = 0; i < graph->pos; i++) {
4095                 append_to_rev_graph(graph, filler->line);
4096                 if (graph_parent_is_merge(graph->prev) &&
4097                     graph->prev->pos == i)
4098                         filler = &fillers[RSHARP];
4100                 append_to_rev_graph(graph, filler->separator);
4101         }
4103         /* Place the symbol for this revision. */
4104         append_to_rev_graph(graph, symbol);
4106         if (graph->prev->size > graph->size)
4107                 filler = &fillers[RDIAG];
4108         else
4109                 filler = &fillers[DEFAULT];
4111         i++;
4113         for (; i < graph->size; i++) {
4114                 append_to_rev_graph(graph, filler->separator);
4115                 append_to_rev_graph(graph, filler->line);
4116                 if (graph_parent_is_merge(graph->prev) &&
4117                     i < graph->prev->pos + graph->parents->size)
4118                         filler = &fillers[RSHARP];
4119                 if (graph->prev->size > graph->size)
4120                         filler = &fillers[LDIAG];
4121         }
4123         if (graph->prev->size > graph->size) {
4124                 append_to_rev_graph(graph, filler->separator);
4125                 if (filler->line != ' ')
4126                         append_to_rev_graph(graph, filler->line);
4127         }
4130 /* Prepare the next rev graph */
4131 static void
4132 prepare_rev_graph(struct rev_graph *graph)
4134         size_t i;
4136         /* First, traverse all lines of revisions up to the active one. */
4137         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4138                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4139                         break;
4141                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4142         }
4144         /* Interleave the new revision parent(s). */
4145         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4146                 push_rev_graph(graph->next, graph->parents->rev[i]);
4148         /* Lastly, put any remaining revisions. */
4149         for (i = graph->pos + 1; i < graph->size; i++)
4150                 push_rev_graph(graph->next, graph->rev[i]);
4153 static void
4154 update_rev_graph(struct rev_graph *graph)
4156         /* If this is the finalizing update ... */
4157         if (graph->commit)
4158                 prepare_rev_graph(graph);
4160         /* Graph visualization needs a one rev look-ahead,
4161          * so the first update doesn't visualize anything. */
4162         if (!graph->prev->commit)
4163                 return;
4165         draw_rev_graph(graph->prev);
4166         done_rev_graph(graph->prev->prev);
4170 /*
4171  * Main view backend
4172  */
4174 static bool
4175 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4177         char buf[DATE_COLS + 1];
4178         struct commit *commit = line->data;
4179         enum line_type type;
4180         int col = 0;
4181         size_t timelen;
4182         int tilde_attr;
4183         int space;
4185         if (!*commit->author)
4186                 return FALSE;
4188         space = view->width;
4189         wmove(view->win, lineno, col);
4191         if (selected) {
4192                 type = LINE_CURSOR;
4193                 wattrset(view->win, get_line_attr(type));
4194                 wchgat(view->win, -1, 0, type, NULL);
4195                 tilde_attr = -1;
4196         } else {
4197                 type = LINE_MAIN_COMMIT;
4198                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4199                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4200         }
4202         if (opt_date) {
4203                 int n;
4205                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4206                 n = draw_text(
4207                         view, buf, view->width - col, col, FALSE, tilde_attr);
4208                 draw_text(
4209                         view, " ", view->width - col - n, col + n, FALSE,
4210                         tilde_attr);
4212                 col += DATE_COLS;
4213                 wmove(view->win, lineno, col);
4214                 if (col >= view->width)
4215                         return TRUE;
4216         }
4217         if (type != LINE_CURSOR)
4218                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4220         if (opt_author) {
4221                 int max_len;
4223                 max_len = view->width - col;
4224                 if (max_len > AUTHOR_COLS - 1)
4225                         max_len = AUTHOR_COLS - 1;
4226                 draw_text(
4227                         view, commit->author, max_len, col, TRUE, tilde_attr);
4228                 col += AUTHOR_COLS;
4229                 if (col >= view->width)
4230                         return TRUE;
4231         }
4233         if (opt_rev_graph && commit->graph_size) {
4234                 size_t graph_size = view->width - col;
4235                 size_t i;
4237                 if (type != LINE_CURSOR)
4238                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4239                 wmove(view->win, lineno, col);
4240                 if (graph_size > commit->graph_size)
4241                         graph_size = commit->graph_size;
4242                 /* Using waddch() instead of waddnstr() ensures that
4243                  * they'll be rendered correctly for the cursor line. */
4244                 for (i = 0; i < graph_size; i++)
4245                         waddch(view->win, commit->graph[i]);
4247                 col += commit->graph_size + 1;
4248                 if (col >= view->width)
4249                         return TRUE;
4250                 waddch(view->win, ' ');
4251         }
4252         if (type != LINE_CURSOR)
4253                 wattrset(view->win, A_NORMAL);
4255         wmove(view->win, lineno, col);
4257         if (opt_show_refs && commit->refs) {
4258                 size_t i = 0;
4260                 do {
4261                         if (type == LINE_CURSOR)
4262                                 ;
4263                         else if (commit->refs[i]->tag)
4264                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4265                         else if (commit->refs[i]->remote)
4266                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4267                         else
4268                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4270                         col += draw_text(
4271                                 view, "[", view->width - col, col, TRUE,
4272                                 tilde_attr);
4273                         col += draw_text(
4274                                 view, commit->refs[i]->name, view->width - col,
4275                                 col, TRUE, tilde_attr);
4276                         col += draw_text(
4277                                 view, "]", view->width - col, col, TRUE,
4278                                 tilde_attr);
4279                         if (type != LINE_CURSOR)
4280                                 wattrset(view->win, A_NORMAL);
4281                         col += draw_text(
4282                                 view, " ", view->width - col, col, TRUE,
4283                                 tilde_attr);
4284                         if (col >= view->width)
4285                                 return TRUE;
4286                 } while (commit->refs[i++]->next);
4287         }
4289         if (type != LINE_CURSOR)
4290                 wattrset(view->win, get_line_attr(type));
4292         col += draw_text(
4293                 view, commit->title, view->width - col, col, TRUE, tilde_attr);
4295         return TRUE;
4298 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4299 static bool
4300 main_read(struct view *view, char *line)
4302         static struct rev_graph *graph = graph_stacks;
4303         enum line_type type;
4304         struct commit *commit;
4306         if (!line) {
4307                 update_rev_graph(graph);
4308                 return TRUE;
4309         }
4311         type = get_line_type(line);
4312         if (type == LINE_COMMIT) {
4313                 commit = calloc(1, sizeof(struct commit));
4314                 if (!commit)
4315                         return FALSE;
4317                 line += STRING_SIZE("commit ");
4318                 if (*line == '-') {
4319                         graph->boundary = 1;
4320                         line++;
4321                 }
4323                 string_copy_rev(commit->id, line);
4324                 commit->refs = get_refs(commit->id);
4325                 graph->commit = commit;
4326                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4327                 return TRUE;
4328         }
4330         if (!view->lines)
4331                 return TRUE;
4332         commit = view->line[view->lines - 1].data;
4334         switch (type) {
4335         case LINE_PARENT:
4336                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4337                 break;
4339         case LINE_AUTHOR:
4340         {
4341                 /* Parse author lines where the name may be empty:
4342                  *      author  <email@address.tld> 1138474660 +0100
4343                  */
4344                 char *ident = line + STRING_SIZE("author ");
4345                 char *nameend = strchr(ident, '<');
4346                 char *emailend = strchr(ident, '>');
4348                 if (!nameend || !emailend)
4349                         break;
4351                 update_rev_graph(graph);
4352                 graph = graph->next;
4354                 *nameend = *emailend = 0;
4355                 ident = chomp_string(ident);
4356                 if (!*ident) {
4357                         ident = chomp_string(nameend + 1);
4358                         if (!*ident)
4359                                 ident = "Unknown";
4360                 }
4362                 string_ncopy(commit->author, ident, strlen(ident));
4364                 /* Parse epoch and timezone */
4365                 if (emailend[1] == ' ') {
4366                         char *secs = emailend + 2;
4367                         char *zone = strchr(secs, ' ');
4368                         time_t time = (time_t) atol(secs);
4370                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4371                                 long tz;
4373                                 zone++;
4374                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4375                                 tz += ('0' - zone[2]) * 60 * 60;
4376                                 tz += ('0' - zone[3]) * 60;
4377                                 tz += ('0' - zone[4]) * 60;
4379                                 if (zone[0] == '-')
4380                                         tz = -tz;
4382                                 time -= tz;
4383                         }
4385                         gmtime_r(&time, &commit->time);
4386                 }
4387                 break;
4388         }
4389         default:
4390                 /* Fill in the commit title if it has not already been set. */
4391                 if (commit->title[0])
4392                         break;
4394                 /* Require titles to start with a non-space character at the
4395                  * offset used by git log. */
4396                 if (strncmp(line, "    ", 4))
4397                         break;
4398                 line += 4;
4399                 /* Well, if the title starts with a whitespace character,
4400                  * try to be forgiving.  Otherwise we end up with no title. */
4401                 while (isspace(*line))
4402                         line++;
4403                 if (*line == '\0')
4404                         break;
4405                 /* FIXME: More graceful handling of titles; append "..." to
4406                  * shortened titles, etc. */
4408                 string_ncopy(commit->title, line, strlen(line));
4409         }
4411         return TRUE;
4414 static enum request
4415 main_request(struct view *view, enum request request, struct line *line)
4417         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4419         if (request == REQ_ENTER)
4420                 open_view(view, REQ_VIEW_DIFF, flags);
4421         else
4422                 return request;
4424         return REQ_NONE;
4427 static bool
4428 main_grep(struct view *view, struct line *line)
4430         struct commit *commit = line->data;
4431         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4432         char buf[DATE_COLS + 1];
4433         regmatch_t pmatch;
4435         for (state = S_TITLE; state < S_END; state++) {
4436                 char *text;
4438                 switch (state) {
4439                 case S_TITLE:   text = commit->title;   break;
4440                 case S_AUTHOR:  text = commit->author;  break;
4441                 case S_DATE:
4442                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4443                                 continue;
4444                         text = buf;
4445                         break;
4447                 default:
4448                         return FALSE;
4449                 }
4451                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4452                         return TRUE;
4453         }
4455         return FALSE;
4458 static void
4459 main_select(struct view *view, struct line *line)
4461         struct commit *commit = line->data;
4463         string_copy_rev(view->ref, commit->id);
4464         string_copy_rev(ref_commit, view->ref);
4467 static struct view_ops main_ops = {
4468         "commit",
4469         NULL,
4470         main_read,
4471         main_draw,
4472         main_request,
4473         main_grep,
4474         main_select,
4475 };
4478 /*
4479  * Unicode / UTF-8 handling
4480  *
4481  * NOTE: Much of the following code for dealing with unicode is derived from
4482  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4483  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4484  */
4486 /* I've (over)annotated a lot of code snippets because I am not entirely
4487  * confident that the approach taken by this small UTF-8 interface is correct.
4488  * --jonas */
4490 static inline int
4491 unicode_width(unsigned long c)
4493         if (c >= 0x1100 &&
4494            (c <= 0x115f                         /* Hangul Jamo */
4495             || c == 0x2329
4496             || c == 0x232a
4497             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4498                                                 /* CJK ... Yi */
4499             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4500             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4501             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4502             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4503             || (c >= 0xffe0  && c <= 0xffe6)
4504             || (c >= 0x20000 && c <= 0x2fffd)
4505             || (c >= 0x30000 && c <= 0x3fffd)))
4506                 return 2;
4508         if (c == '\t')
4509                 return opt_tab_size;
4511         return 1;
4514 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4515  * Illegal bytes are set one. */
4516 static const unsigned char utf8_bytes[256] = {
4517         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,
4518         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,
4519         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,
4520         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,
4521         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,
4522         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,
4523         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,
4524         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,
4525 };
4527 /* Decode UTF-8 multi-byte representation into a unicode character. */
4528 static inline unsigned long
4529 utf8_to_unicode(const char *string, size_t length)
4531         unsigned long unicode;
4533         switch (length) {
4534         case 1:
4535                 unicode  =   string[0];
4536                 break;
4537         case 2:
4538                 unicode  =  (string[0] & 0x1f) << 6;
4539                 unicode +=  (string[1] & 0x3f);
4540                 break;
4541         case 3:
4542                 unicode  =  (string[0] & 0x0f) << 12;
4543                 unicode += ((string[1] & 0x3f) << 6);
4544                 unicode +=  (string[2] & 0x3f);
4545                 break;
4546         case 4:
4547                 unicode  =  (string[0] & 0x0f) << 18;
4548                 unicode += ((string[1] & 0x3f) << 12);
4549                 unicode += ((string[2] & 0x3f) << 6);
4550                 unicode +=  (string[3] & 0x3f);
4551                 break;
4552         case 5:
4553                 unicode  =  (string[0] & 0x0f) << 24;
4554                 unicode += ((string[1] & 0x3f) << 18);
4555                 unicode += ((string[2] & 0x3f) << 12);
4556                 unicode += ((string[3] & 0x3f) << 6);
4557                 unicode +=  (string[4] & 0x3f);
4558                 break;
4559         case 6:
4560                 unicode  =  (string[0] & 0x01) << 30;
4561                 unicode += ((string[1] & 0x3f) << 24);
4562                 unicode += ((string[2] & 0x3f) << 18);
4563                 unicode += ((string[3] & 0x3f) << 12);
4564                 unicode += ((string[4] & 0x3f) << 6);
4565                 unicode +=  (string[5] & 0x3f);
4566                 break;
4567         default:
4568                 die("Invalid unicode length");
4569         }
4571         /* Invalid characters could return the special 0xfffd value but NUL
4572          * should be just as good. */
4573         return unicode > 0xffff ? 0 : unicode;
4576 /* Calculates how much of string can be shown within the given maximum width
4577  * and sets trimmed parameter to non-zero value if all of string could not be
4578  * shown. If the reserve flag is TRUE, it will reserve at least one
4579  * trailing character, which can be useful when drawing a delimiter.
4580  *
4581  * Returns the number of bytes to output from string to satisfy max_width. */
4582 static size_t
4583 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
4585         const char *start = string;
4586         const char *end = strchr(string, '\0');
4587         unsigned char last_bytes = 0;
4588         size_t width = 0;
4590         *trimmed = 0;
4592         while (string < end) {
4593                 int c = *(unsigned char *) string;
4594                 unsigned char bytes = utf8_bytes[c];
4595                 size_t ucwidth;
4596                 unsigned long unicode;
4598                 if (string + bytes > end)
4599                         break;
4601                 /* Change representation to figure out whether
4602                  * it is a single- or double-width character. */
4604                 unicode = utf8_to_unicode(string, bytes);
4605                 /* FIXME: Graceful handling of invalid unicode character. */
4606                 if (!unicode)
4607                         break;
4609                 ucwidth = unicode_width(unicode);
4610                 width  += ucwidth;
4611                 if (width > max_width) {
4612                         *trimmed = 1;
4613                         if (reserve && width - ucwidth == max_width) {
4614                                 string -= last_bytes;
4615                         }
4616                         break;
4617                 }
4619                 string  += bytes;
4620                 last_bytes = bytes;
4621         }
4623         return string - start;
4627 /*
4628  * Status management
4629  */
4631 /* Whether or not the curses interface has been initialized. */
4632 static bool cursed = FALSE;
4634 /* The status window is used for polling keystrokes. */
4635 static WINDOW *status_win;
4637 static bool status_empty = TRUE;
4639 /* Update status and title window. */
4640 static void
4641 report(const char *msg, ...)
4643         struct view *view = display[current_view];
4645         if (input_mode)
4646                 return;
4648         if (!view) {
4649                 char buf[SIZEOF_STR];
4650                 va_list args;
4652                 va_start(args, msg);
4653                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4654                         buf[sizeof(buf) - 1] = 0;
4655                         buf[sizeof(buf) - 2] = '.';
4656                         buf[sizeof(buf) - 3] = '.';
4657                         buf[sizeof(buf) - 4] = '.';
4658                 }
4659                 va_end(args);
4660                 die("%s", buf);
4661         }
4663         if (!status_empty || *msg) {
4664                 va_list args;
4666                 va_start(args, msg);
4668                 wmove(status_win, 0, 0);
4669                 if (*msg) {
4670                         vwprintw(status_win, msg, args);
4671                         status_empty = FALSE;
4672                 } else {
4673                         status_empty = TRUE;
4674                 }
4675                 wclrtoeol(status_win);
4676                 wrefresh(status_win);
4678                 va_end(args);
4679         }
4681         update_view_title(view);
4682         update_display_cursor(view);
4685 /* Controls when nodelay should be in effect when polling user input. */
4686 static void
4687 set_nonblocking_input(bool loading)
4689         static unsigned int loading_views;
4691         if ((loading == FALSE && loading_views-- == 1) ||
4692             (loading == TRUE  && loading_views++ == 0))
4693                 nodelay(status_win, loading);
4696 static void
4697 init_display(void)
4699         int x, y;
4701         /* Initialize the curses library */
4702         if (isatty(STDIN_FILENO)) {
4703                 cursed = !!initscr();
4704         } else {
4705                 /* Leave stdin and stdout alone when acting as a pager. */
4706                 FILE *io = fopen("/dev/tty", "r+");
4708                 if (!io)
4709                         die("Failed to open /dev/tty");
4710                 cursed = !!newterm(NULL, io, io);
4711         }
4713         if (!cursed)
4714                 die("Failed to initialize curses");
4716         nonl();         /* Tell curses not to do NL->CR/NL on output */
4717         cbreak();       /* Take input chars one at a time, no wait for \n */
4718         noecho();       /* Don't echo input */
4719         leaveok(stdscr, TRUE);
4721         if (has_colors())
4722                 init_colors();
4724         getmaxyx(stdscr, y, x);
4725         status_win = newwin(1, 0, y - 1, 0);
4726         if (!status_win)
4727                 die("Failed to create status window");
4729         /* Enable keyboard mapping */
4730         keypad(status_win, TRUE);
4731         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4734 static char *
4735 read_prompt(const char *prompt)
4737         enum { READING, STOP, CANCEL } status = READING;
4738         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4739         int pos = 0;
4741         while (status == READING) {
4742                 struct view *view;
4743                 int i, key;
4745                 input_mode = TRUE;
4747                 foreach_view (view, i)
4748                         update_view(view);
4750                 input_mode = FALSE;
4752                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4753                 wclrtoeol(status_win);
4755                 /* Refresh, accept single keystroke of input */
4756                 key = wgetch(status_win);
4757                 switch (key) {
4758                 case KEY_RETURN:
4759                 case KEY_ENTER:
4760                 case '\n':
4761                         status = pos ? STOP : CANCEL;
4762                         break;
4764                 case KEY_BACKSPACE:
4765                         if (pos > 0)
4766                                 pos--;
4767                         else
4768                                 status = CANCEL;
4769                         break;
4771                 case KEY_ESC:
4772                         status = CANCEL;
4773                         break;
4775                 case ERR:
4776                         break;
4778                 default:
4779                         if (pos >= sizeof(buf)) {
4780                                 report("Input string too long");
4781                                 return NULL;
4782                         }
4784                         if (isprint(key))
4785                                 buf[pos++] = (char) key;
4786                 }
4787         }
4789         /* Clear the status window */
4790         status_empty = FALSE;
4791         report("");
4793         if (status == CANCEL)
4794                 return NULL;
4796         buf[pos++] = 0;
4798         return buf;
4801 /*
4802  * Repository references
4803  */
4805 static struct ref *refs;
4806 static size_t refs_size;
4808 /* Id <-> ref store */
4809 static struct ref ***id_refs;
4810 static size_t id_refs_size;
4812 static struct ref **
4813 get_refs(char *id)
4815         struct ref ***tmp_id_refs;
4816         struct ref **ref_list = NULL;
4817         size_t ref_list_size = 0;
4818         size_t i;
4820         for (i = 0; i < id_refs_size; i++)
4821                 if (!strcmp(id, id_refs[i][0]->id))
4822                         return id_refs[i];
4824         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4825         if (!tmp_id_refs)
4826                 return NULL;
4828         id_refs = tmp_id_refs;
4830         for (i = 0; i < refs_size; i++) {
4831                 struct ref **tmp;
4833                 if (strcmp(id, refs[i].id))
4834                         continue;
4836                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4837                 if (!tmp) {
4838                         if (ref_list)
4839                                 free(ref_list);
4840                         return NULL;
4841                 }
4843                 ref_list = tmp;
4844                 if (ref_list_size > 0)
4845                         ref_list[ref_list_size - 1]->next = 1;
4846                 ref_list[ref_list_size] = &refs[i];
4848                 /* XXX: The properties of the commit chains ensures that we can
4849                  * safely modify the shared ref. The repo references will
4850                  * always be similar for the same id. */
4851                 ref_list[ref_list_size]->next = 0;
4852                 ref_list_size++;
4853         }
4855         if (ref_list)
4856                 id_refs[id_refs_size++] = ref_list;
4858         return ref_list;
4861 static int
4862 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4864         struct ref *ref;
4865         bool tag = FALSE;
4866         bool remote = FALSE;
4868         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4869                 /* Commits referenced by tags has "^{}" appended. */
4870                 if (name[namelen - 1] != '}')
4871                         return OK;
4873                 while (namelen > 0 && name[namelen] != '^')
4874                         namelen--;
4876                 tag = TRUE;
4877                 namelen -= STRING_SIZE("refs/tags/");
4878                 name    += STRING_SIZE("refs/tags/");
4880         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4881                 remote = TRUE;
4882                 namelen -= STRING_SIZE("refs/remotes/");
4883                 name    += STRING_SIZE("refs/remotes/");
4885         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4886                 namelen -= STRING_SIZE("refs/heads/");
4887                 name    += STRING_SIZE("refs/heads/");
4889         } else if (!strcmp(name, "HEAD")) {
4890                 return OK;
4891         }
4893         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4894         if (!refs)
4895                 return ERR;
4897         ref = &refs[refs_size++];
4898         ref->name = malloc(namelen + 1);
4899         if (!ref->name)
4900                 return ERR;
4902         strncpy(ref->name, name, namelen);
4903         ref->name[namelen] = 0;
4904         ref->tag = tag;
4905         ref->remote = remote;
4906         string_copy_rev(ref->id, id);
4908         return OK;
4911 static int
4912 load_refs(void)
4914         const char *cmd_env = getenv("TIG_LS_REMOTE");
4915         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4917         return read_properties(popen(cmd, "r"), "\t", read_ref);
4920 static int
4921 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4923         if (!strcmp(name, "i18n.commitencoding"))
4924                 string_ncopy(opt_encoding, value, valuelen);
4926         if (!strcmp(name, "core.editor"))
4927                 string_ncopy(opt_editor, value, valuelen);
4929         return OK;
4932 static int
4933 load_repo_config(void)
4935         return read_properties(popen(GIT_CONFIG " --list", "r"),
4936                                "=", read_repo_config_option);
4939 static int
4940 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4942         if (!opt_git_dir[0]) {
4943                 string_ncopy(opt_git_dir, name, namelen);
4945         } else if (opt_is_inside_work_tree == -1) {
4946                 /* This can be 3 different values depending on the
4947                  * version of git being used. If git-rev-parse does not
4948                  * understand --is-inside-work-tree it will simply echo
4949                  * the option else either "true" or "false" is printed.
4950                  * Default to true for the unknown case. */
4951                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4953         } else {
4954                 string_ncopy(opt_cdup, name, namelen);
4955         }
4957         return OK;
4960 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4961  * must be the last one! */
4962 static int
4963 load_repo_info(void)
4965         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4966                                "=", read_repo_info);
4969 static int
4970 read_properties(FILE *pipe, const char *separators,
4971                 int (*read_property)(char *, size_t, char *, size_t))
4973         char buffer[BUFSIZ];
4974         char *name;
4975         int state = OK;
4977         if (!pipe)
4978                 return ERR;
4980         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4981                 char *value;
4982                 size_t namelen;
4983                 size_t valuelen;
4985                 name = chomp_string(name);
4986                 namelen = strcspn(name, separators);
4988                 if (name[namelen]) {
4989                         name[namelen] = 0;
4990                         value = chomp_string(name + namelen + 1);
4991                         valuelen = strlen(value);
4993                 } else {
4994                         value = "";
4995                         valuelen = 0;
4996                 }
4998                 state = read_property(name, namelen, value, valuelen);
4999         }
5001         if (state != ERR && ferror(pipe))
5002                 state = ERR;
5004         pclose(pipe);
5006         return state;
5010 /*
5011  * Main
5012  */
5014 static void __NORETURN
5015 quit(int sig)
5017         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5018         if (cursed)
5019                 endwin();
5020         exit(0);
5023 static void __NORETURN
5024 die(const char *err, ...)
5026         va_list args;
5028         endwin();
5030         va_start(args, err);
5031         fputs("tig: ", stderr);
5032         vfprintf(stderr, err, args);
5033         fputs("\n", stderr);
5034         va_end(args);
5036         exit(1);
5039 static void
5040 warn(const char *msg, ...)
5042         va_list args;
5044         va_start(args, msg);
5045         fputs("tig warning: ", stderr);
5046         vfprintf(stderr, msg, args);
5047         fputs("\n", stderr);
5048         va_end(args);
5051 int
5052 main(int argc, char *argv[])
5054         struct view *view;
5055         enum request request;
5056         size_t i;
5058         signal(SIGINT, quit);
5060         if (setlocale(LC_ALL, "")) {
5061                 char *codeset = nl_langinfo(CODESET);
5063                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5064         }
5066         if (load_repo_info() == ERR)
5067                 die("Failed to load repo info.");
5069         if (load_options() == ERR)
5070                 die("Failed to load user config.");
5072         /* Load the repo config file so options can be overwritten from
5073          * the command line. */
5074         if (load_repo_config() == ERR)
5075                 die("Failed to load repo config.");
5077         if (!parse_options(argc, argv))
5078                 return 0;
5080         /* Require a git repository unless when running in pager mode. */
5081         if (!opt_git_dir[0])
5082                 die("Not a git repository");
5084         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5085                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5086                 if (opt_iconv == ICONV_NONE)
5087                         die("Failed to initialize character set conversion");
5088         }
5090         if (load_refs() == ERR)
5091                 die("Failed to load refs.");
5093         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5094                 view->cmd_env = getenv(view->cmd_env);
5096         request = opt_request;
5098         init_display();
5100         while (view_driver(display[current_view], request)) {
5101                 int key;
5102                 int i;
5104                 foreach_view (view, i)
5105                         update_view(view);
5107                 /* Refresh, accept single keystroke of input */
5108                 key = wgetch(status_win);
5110                 /* wgetch() with nodelay() enabled returns ERR when there's no
5111                  * input. */
5112                 if (key == ERR) {
5113                         request = REQ_NONE;
5114                         continue;
5115                 }
5117                 request = get_keybinding(display[current_view]->keymap, key);
5119                 /* Some low-level request handling. This keeps access to
5120                  * status_win restricted. */
5121                 switch (request) {
5122                 case REQ_PROMPT:
5123                 {
5124                         char *cmd = read_prompt(":");
5126                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5127                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5128                                         opt_request = REQ_VIEW_DIFF;
5129                                 } else {
5130                                         opt_request = REQ_VIEW_PAGER;
5131                                 }
5132                                 break;
5133                         }
5135                         request = REQ_NONE;
5136                         break;
5137                 }
5138                 case REQ_SEARCH:
5139                 case REQ_SEARCH_BACK:
5140                 {
5141                         const char *prompt = request == REQ_SEARCH
5142                                            ? "/" : "?";
5143                         char *search = read_prompt(prompt);
5145                         if (search)
5146                                 string_ncopy(opt_search, search, strlen(search));
5147                         else
5148                                 request = REQ_NONE;
5149                         break;
5150                 }
5151                 case REQ_SCREEN_RESIZE:
5152                 {
5153                         int height, width;
5155                         getmaxyx(stdscr, height, width);
5157                         /* Resize the status view and let the view driver take
5158                          * care of resizing the displayed views. */
5159                         wresize(status_win, 1, width);
5160                         mvwin(status_win, height - 1, 0);
5161                         wrefresh(status_win);
5162                         break;
5163                 }
5164                 default:
5165                         break;
5166                 }
5167         }
5169         quit(0);
5171         return 0;