Code

Remove outdated comment
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 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 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD     \
129         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132         "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135         TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD    \
138         "git ls-tree %s %s"
140 #define TIG_BLOB_CMD    \
141         "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD    ""
145 #define TIG_PAGER_CMD   ""
146 #define TIG_STATUS_CMD  ""
147 #define TIG_STAGE_CMD   ""
148 #define TIG_BLAME_CMD   ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB         '\t'
152 #define KEY_RETURN      '\r'
153 #define KEY_ESC         27
156 struct ref {
157         char *name;             /* Ref name; tag or head names are shortened. */
158         char id[SIZEOF_REV];    /* Commit SHA1 ID */
159         unsigned int head:1;    /* Is it the current HEAD? */
160         unsigned int tag:1;     /* Is it a tag? */
161         unsigned int ltag:1;    /* If so, is the tag local? */
162         unsigned int remote:1;  /* Is it a remote ref? */
163         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164         unsigned int next:1;    /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170         const char *name;
171         int namelen;
172         int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177                  int *value, const char *name, int namelen)
180         int i;
182         for (i = 0; i < map_size; i++)
183                 if (namelen == map[i].namelen &&
184                     !strncasecmp(name, map[i].name, namelen)) {
185                         *value = map[i].value;
186                         return OK;
187                 }
189         return ERR;
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224         int namelen;
226         while (isspace(*name))
227                 name++;
229         namelen = strlen(name) - 1;
230         while (namelen > 0 && isspace(name[namelen]))
231                 name[namelen--] = 0;
233         return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239         va_list args;
240         size_t pos = bufpos ? *bufpos : 0;
242         va_start(args, fmt);
243         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244         va_end(args);
246         if (bufpos)
247                 *bufpos = pos;
249         return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253         string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256         string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261         size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265         /* Diff-Header == DIFF_HEADER */
266         for (i = 0; i < len; i++) {
267                 if (toupper(str1[i]) == toupper(str2[i]))
268                         continue;
270                 if (string_enum_sep(str1[i]) &&
271                     string_enum_sep(str2[i]))
272                         continue;
274                 return str1[i] - str2[i];
275         }
277         return 0;
280 /* Shell quoting
281  *
282  * NOTE: The following is a slightly modified copy of the git project's shell
283  * quoting routines found in the quote.c file.
284  *
285  * Help to copy the thing properly quoted for the shell safety.  any single
286  * quote is replaced with '\'', any exclamation point is replaced with '\!',
287  * and the whole thing is enclosed in a
288  *
289  * E.g.
290  *  original     sq_quote     result
291  *  name     ==> name      ==> 'name'
292  *  a b      ==> a b       ==> 'a b'
293  *  a'b      ==> a'\''b    ==> 'a'\''b'
294  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
295  */
297 static size_t
298 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
300         char c;
302 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
304         BUFPUT('\'');
305         while ((c = *src++)) {
306                 if (c == '\'' || c == '!') {
307                         BUFPUT('\'');
308                         BUFPUT('\\');
309                         BUFPUT(c);
310                         BUFPUT('\'');
311                 } else {
312                         BUFPUT(c);
313                 }
314         }
315         BUFPUT('\'');
317         if (bufsize < SIZEOF_STR)
318                 buf[bufsize] = 0;
320         return bufsize;
324 /*
325  * User requests
326  */
328 #define REQ_INFO \
329         /* XXX: Keep the view request first and in sync with views[]. */ \
330         REQ_GROUP("View switching") \
331         REQ_(VIEW_MAIN,         "Show main view"), \
332         REQ_(VIEW_DIFF,         "Show diff view"), \
333         REQ_(VIEW_LOG,          "Show log view"), \
334         REQ_(VIEW_TREE,         "Show tree view"), \
335         REQ_(VIEW_BLOB,         "Show blob view"), \
336         REQ_(VIEW_BLAME,        "Show blame view"), \
337         REQ_(VIEW_HELP,         "Show help page"), \
338         REQ_(VIEW_PAGER,        "Show pager view"), \
339         REQ_(VIEW_STATUS,       "Show status view"), \
340         REQ_(VIEW_STAGE,        "Show stage view"), \
341         \
342         REQ_GROUP("View manipulation") \
343         REQ_(ENTER,             "Enter current line and scroll"), \
344         REQ_(NEXT,              "Move to next"), \
345         REQ_(PREVIOUS,          "Move to previous"), \
346         REQ_(VIEW_NEXT,         "Move focus to next view"), \
347         REQ_(REFRESH,           "Reload and refresh"), \
348         REQ_(MAXIMIZE,          "Maximize the current view"), \
349         REQ_(VIEW_CLOSE,        "Close the current view"), \
350         REQ_(QUIT,              "Close all views and quit"), \
351         \
352         REQ_GROUP("View specific requests") \
353         REQ_(STATUS_UPDATE,     "Update file status"), \
354         REQ_(STATUS_REVERT,     "Revert file changes"), \
355         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
356         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
357         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
358         \
359         REQ_GROUP("Cursor navigation") \
360         REQ_(MOVE_UP,           "Move cursor one line up"), \
361         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
362         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
363         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
364         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
365         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
366         \
367         REQ_GROUP("Scrolling") \
368         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
369         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
370         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
371         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
372         \
373         REQ_GROUP("Searching") \
374         REQ_(SEARCH,            "Search the view"), \
375         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
376         REQ_(FIND_NEXT,         "Find next search match"), \
377         REQ_(FIND_PREV,         "Find previous search match"), \
378         \
379         REQ_GROUP("Option manipulation") \
380         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
381         REQ_(TOGGLE_DATE,       "Toggle date display"), \
382         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
383         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
384         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
385         \
386         REQ_GROUP("Misc") \
387         REQ_(PROMPT,            "Bring up the prompt"), \
388         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
389         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
390         REQ_(SHOW_VERSION,      "Show version information"), \
391         REQ_(STOP_LOADING,      "Stop all loading views"), \
392         REQ_(EDIT,              "Open in editor"), \
393         REQ_(NONE,              "Do nothing")
396 /* User action requests. */
397 enum request {
398 #define REQ_GROUP(help)
399 #define REQ_(req, help) REQ_##req
401         /* Offset all requests to avoid conflicts with ncurses getch values. */
402         REQ_OFFSET = KEY_MAX + 1,
403         REQ_INFO
405 #undef  REQ_GROUP
406 #undef  REQ_
407 };
409 struct request_info {
410         enum request request;
411         const char *name;
412         int namelen;
413         const char *help;
414 };
416 static struct request_info req_info[] = {
417 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
418 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
419         REQ_INFO
420 #undef  REQ_GROUP
421 #undef  REQ_
422 };
424 static enum request
425 get_request(const char *name)
427         int namelen = strlen(name);
428         int i;
430         for (i = 0; i < ARRAY_SIZE(req_info); i++)
431                 if (req_info[i].namelen == namelen &&
432                     !string_enum_compare(req_info[i].name, name, namelen))
433                         return req_info[i].request;
435         return REQ_NONE;
439 /*
440  * Options
441  */
443 static const char usage[] =
444 "tig " TIG_VERSION " (" __DATE__ ")\n"
445 "\n"
446 "Usage: tig        [options] [revs] [--] [paths]\n"
447 "   or: tig show   [options] [revs] [--] [paths]\n"
448 "   or: tig blame  [rev] path\n"
449 "   or: tig status\n"
450 "   or: tig <      [git command output]\n"
451 "\n"
452 "Options:\n"
453 "  -v, --version   Show version and exit\n"
454 "  -h, --help      Show help message and exit";
456 /* Option and state variables. */
457 static bool opt_date                    = TRUE;
458 static bool opt_author                  = TRUE;
459 static bool opt_line_number             = FALSE;
460 static bool opt_line_graphics           = TRUE;
461 static bool opt_rev_graph               = FALSE;
462 static bool opt_show_refs               = TRUE;
463 static int opt_num_interval             = NUMBER_INTERVAL;
464 static int opt_tab_size                 = TAB_SIZE;
465 static int opt_author_cols              = AUTHOR_COLS-1;
466 static char opt_cmd[SIZEOF_STR]         = "";
467 static char opt_path[SIZEOF_STR]        = "";
468 static char opt_file[SIZEOF_STR]        = "";
469 static char opt_ref[SIZEOF_REF]         = "";
470 static char opt_head[SIZEOF_REF]        = "";
471 static char opt_remote[SIZEOF_REF]      = "";
472 static bool opt_no_head                 = TRUE;
473 static FILE *opt_pipe                   = NULL;
474 static char opt_encoding[20]            = "UTF-8";
475 static bool opt_utf8                    = TRUE;
476 static char opt_codeset[20]             = "UTF-8";
477 static iconv_t opt_iconv                = ICONV_NONE;
478 static char opt_search[SIZEOF_STR]      = "";
479 static char opt_cdup[SIZEOF_STR]        = "";
480 static char opt_git_dir[SIZEOF_STR]     = "";
481 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
482 static char opt_editor[SIZEOF_STR]      = "";
484 static enum request
485 parse_options(int argc, const char *argv[])
487         enum request request = REQ_VIEW_MAIN;
488         size_t buf_size;
489         const char *subcommand;
490         bool seen_dashdash = FALSE;
491         int i;
493         if (!isatty(STDIN_FILENO)) {
494                 opt_pipe = stdin;
495                 return REQ_VIEW_PAGER;
496         }
498         if (argc <= 1)
499                 return REQ_VIEW_MAIN;
501         subcommand = argv[1];
502         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
503                 if (!strcmp(subcommand, "-S"))
504                         warn("`-S' has been deprecated; use `tig status' instead");
505                 if (argc > 2)
506                         warn("ignoring arguments after `%s'", subcommand);
507                 return REQ_VIEW_STATUS;
509         } else if (!strcmp(subcommand, "blame")) {
510                 if (argc <= 2 || argc > 4)
511                         die("invalid number of options to blame\n\n%s", usage);
513                 i = 2;
514                 if (argc == 4) {
515                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
516                         i++;
517                 }
519                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
520                 return REQ_VIEW_BLAME;
522         } else if (!strcmp(subcommand, "show")) {
523                 request = REQ_VIEW_DIFF;
525         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
526                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
527                 warn("`tig %s' has been deprecated", subcommand);
529         } else {
530                 subcommand = NULL;
531         }
533         if (!subcommand)
534                 /* XXX: This is vulnerable to the user overriding
535                  * options required for the main view parser. */
536                 string_copy(opt_cmd, TIG_MAIN_BASE);
537         else
538                 string_format(opt_cmd, "git %s", subcommand);
540         buf_size = strlen(opt_cmd);
542         for (i = 1 + !!subcommand; i < argc; i++) {
543                 const char *opt = argv[i];
545                 if (seen_dashdash || !strcmp(opt, "--")) {
546                         seen_dashdash = TRUE;
548                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
549                         printf("tig version %s\n", TIG_VERSION);
550                         return REQ_NONE;
552                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
553                         printf("%s\n", usage);
554                         return REQ_NONE;
555                 }
557                 opt_cmd[buf_size++] = ' ';
558                 buf_size = sq_quote(opt_cmd, buf_size, opt);
559                 if (buf_size >= sizeof(opt_cmd))
560                         die("command too long");
561         }
563         opt_cmd[buf_size] = 0;
565         return request;
569 /*
570  * Line-oriented content detection.
571  */
573 #define LINE_INFO \
574 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
576 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
577 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
579 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
580 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
581 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
582 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
583 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
584 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
585 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
586 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
587 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
588 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
589 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
590 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
591 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
593 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
594 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
595 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
596 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
597 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
598 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
599 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
600 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
601 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
602 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
604 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
605 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
606 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
607 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
608 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
609 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
610 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
612 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
613 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
614 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
615 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
616 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
617 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
618 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
619 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
620 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
621 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
622 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
623 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
624 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
625 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
626 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
627 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
629 enum line_type {
630 #define LINE(type, line, fg, bg, attr) \
631         LINE_##type
632         LINE_INFO,
633         LINE_NONE
634 #undef  LINE
635 };
637 struct line_info {
638         const char *name;       /* Option name. */
639         int namelen;            /* Size of option name. */
640         const char *line;       /* The start of line to match. */
641         int linelen;            /* Size of string to match. */
642         int fg, bg, attr;       /* Color and text attributes for the lines. */
643 };
645 static struct line_info line_info[] = {
646 #define LINE(type, line, fg, bg, attr) \
647         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
648         LINE_INFO
649 #undef  LINE
650 };
652 static enum line_type
653 get_line_type(const char *line)
655         int linelen = strlen(line);
656         enum line_type type;
658         for (type = 0; type < ARRAY_SIZE(line_info); type++)
659                 /* Case insensitive search matches Signed-off-by lines better. */
660                 if (linelen >= line_info[type].linelen &&
661                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
662                         return type;
664         return LINE_DEFAULT;
667 static inline int
668 get_line_attr(enum line_type type)
670         assert(type < ARRAY_SIZE(line_info));
671         return COLOR_PAIR(type) | line_info[type].attr;
674 static struct line_info *
675 get_line_info(const char *name)
677         size_t namelen = strlen(name);
678         enum line_type type;
680         for (type = 0; type < ARRAY_SIZE(line_info); type++)
681                 if (namelen == line_info[type].namelen &&
682                     !string_enum_compare(line_info[type].name, name, namelen))
683                         return &line_info[type];
685         return NULL;
688 static void
689 init_colors(void)
691         int default_bg = line_info[LINE_DEFAULT].bg;
692         int default_fg = line_info[LINE_DEFAULT].fg;
693         enum line_type type;
695         start_color();
697         if (assume_default_colors(default_fg, default_bg) == ERR) {
698                 default_bg = COLOR_BLACK;
699                 default_fg = COLOR_WHITE;
700         }
702         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
703                 struct line_info *info = &line_info[type];
704                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
705                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
707                 init_pair(type, fg, bg);
708         }
711 struct line {
712         enum line_type type;
714         /* State flags */
715         unsigned int selected:1;
716         unsigned int dirty:1;
718         void *data;             /* User data */
719 };
722 /*
723  * Keys
724  */
726 struct keybinding {
727         int alias;
728         enum request request;
729         struct keybinding *next;
730 };
732 static struct keybinding default_keybindings[] = {
733         /* View switching */
734         { 'm',          REQ_VIEW_MAIN },
735         { 'd',          REQ_VIEW_DIFF },
736         { 'l',          REQ_VIEW_LOG },
737         { 't',          REQ_VIEW_TREE },
738         { 'f',          REQ_VIEW_BLOB },
739         { 'B',          REQ_VIEW_BLAME },
740         { 'p',          REQ_VIEW_PAGER },
741         { 'h',          REQ_VIEW_HELP },
742         { 'S',          REQ_VIEW_STATUS },
743         { 'c',          REQ_VIEW_STAGE },
745         /* View manipulation */
746         { 'q',          REQ_VIEW_CLOSE },
747         { KEY_TAB,      REQ_VIEW_NEXT },
748         { KEY_RETURN,   REQ_ENTER },
749         { KEY_UP,       REQ_PREVIOUS },
750         { KEY_DOWN,     REQ_NEXT },
751         { 'R',          REQ_REFRESH },
752         { KEY_F(5),     REQ_REFRESH },
753         { 'O',          REQ_MAXIMIZE },
755         /* Cursor navigation */
756         { 'k',          REQ_MOVE_UP },
757         { 'j',          REQ_MOVE_DOWN },
758         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
759         { KEY_END,      REQ_MOVE_LAST_LINE },
760         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
761         { ' ',          REQ_MOVE_PAGE_DOWN },
762         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
763         { 'b',          REQ_MOVE_PAGE_UP },
764         { '-',          REQ_MOVE_PAGE_UP },
766         /* Scrolling */
767         { KEY_IC,       REQ_SCROLL_LINE_UP },
768         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
769         { 'w',          REQ_SCROLL_PAGE_UP },
770         { 's',          REQ_SCROLL_PAGE_DOWN },
772         /* Searching */
773         { '/',          REQ_SEARCH },
774         { '?',          REQ_SEARCH_BACK },
775         { 'n',          REQ_FIND_NEXT },
776         { 'N',          REQ_FIND_PREV },
778         /* Misc */
779         { 'Q',          REQ_QUIT },
780         { 'z',          REQ_STOP_LOADING },
781         { 'v',          REQ_SHOW_VERSION },
782         { 'r',          REQ_SCREEN_REDRAW },
783         { '.',          REQ_TOGGLE_LINENO },
784         { 'D',          REQ_TOGGLE_DATE },
785         { 'A',          REQ_TOGGLE_AUTHOR },
786         { 'g',          REQ_TOGGLE_REV_GRAPH },
787         { 'F',          REQ_TOGGLE_REFS },
788         { ':',          REQ_PROMPT },
789         { 'u',          REQ_STATUS_UPDATE },
790         { '!',          REQ_STATUS_REVERT },
791         { 'M',          REQ_STATUS_MERGE },
792         { '@',          REQ_STAGE_NEXT },
793         { ',',          REQ_TREE_PARENT },
794         { 'e',          REQ_EDIT },
796         /* Using the ncurses SIGWINCH handler. */
797         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
798 };
800 #define KEYMAP_INFO \
801         KEYMAP_(GENERIC), \
802         KEYMAP_(MAIN), \
803         KEYMAP_(DIFF), \
804         KEYMAP_(LOG), \
805         KEYMAP_(TREE), \
806         KEYMAP_(BLOB), \
807         KEYMAP_(BLAME), \
808         KEYMAP_(PAGER), \
809         KEYMAP_(HELP), \
810         KEYMAP_(STATUS), \
811         KEYMAP_(STAGE)
813 enum keymap {
814 #define KEYMAP_(name) KEYMAP_##name
815         KEYMAP_INFO
816 #undef  KEYMAP_
817 };
819 static struct int_map keymap_table[] = {
820 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
821         KEYMAP_INFO
822 #undef  KEYMAP_
823 };
825 #define set_keymap(map, name) \
826         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
828 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
830 static void
831 add_keybinding(enum keymap keymap, enum request request, int key)
833         struct keybinding *keybinding;
835         keybinding = calloc(1, sizeof(*keybinding));
836         if (!keybinding)
837                 die("Failed to allocate keybinding");
839         keybinding->alias = key;
840         keybinding->request = request;
841         keybinding->next = keybindings[keymap];
842         keybindings[keymap] = keybinding;
845 /* Looks for a key binding first in the given map, then in the generic map, and
846  * lastly in the default keybindings. */
847 static enum request
848 get_keybinding(enum keymap keymap, int key)
850         struct keybinding *kbd;
851         int i;
853         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
854                 if (kbd->alias == key)
855                         return kbd->request;
857         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
858                 if (kbd->alias == key)
859                         return kbd->request;
861         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
862                 if (default_keybindings[i].alias == key)
863                         return default_keybindings[i].request;
865         return (enum request) key;
869 struct key {
870         const char *name;
871         int value;
872 };
874 static struct key key_table[] = {
875         { "Enter",      KEY_RETURN },
876         { "Space",      ' ' },
877         { "Backspace",  KEY_BACKSPACE },
878         { "Tab",        KEY_TAB },
879         { "Escape",     KEY_ESC },
880         { "Left",       KEY_LEFT },
881         { "Right",      KEY_RIGHT },
882         { "Up",         KEY_UP },
883         { "Down",       KEY_DOWN },
884         { "Insert",     KEY_IC },
885         { "Delete",     KEY_DC },
886         { "Hash",       '#' },
887         { "Home",       KEY_HOME },
888         { "End",        KEY_END },
889         { "PageUp",     KEY_PPAGE },
890         { "PageDown",   KEY_NPAGE },
891         { "F1",         KEY_F(1) },
892         { "F2",         KEY_F(2) },
893         { "F3",         KEY_F(3) },
894         { "F4",         KEY_F(4) },
895         { "F5",         KEY_F(5) },
896         { "F6",         KEY_F(6) },
897         { "F7",         KEY_F(7) },
898         { "F8",         KEY_F(8) },
899         { "F9",         KEY_F(9) },
900         { "F10",        KEY_F(10) },
901         { "F11",        KEY_F(11) },
902         { "F12",        KEY_F(12) },
903 };
905 static int
906 get_key_value(const char *name)
908         int i;
910         for (i = 0; i < ARRAY_SIZE(key_table); i++)
911                 if (!strcasecmp(key_table[i].name, name))
912                         return key_table[i].value;
914         if (strlen(name) == 1 && isprint(*name))
915                 return (int) *name;
917         return ERR;
920 static const char *
921 get_key_name(int key_value)
923         static char key_char[] = "'X'";
924         const char *seq = NULL;
925         int key;
927         for (key = 0; key < ARRAY_SIZE(key_table); key++)
928                 if (key_table[key].value == key_value)
929                         seq = key_table[key].name;
931         if (seq == NULL &&
932             key_value < 127 &&
933             isprint(key_value)) {
934                 key_char[1] = (char) key_value;
935                 seq = key_char;
936         }
938         return seq ? seq : "(no key)";
941 static const char *
942 get_key(enum request request)
944         static char buf[BUFSIZ];
945         size_t pos = 0;
946         char *sep = "";
947         int i;
949         buf[pos] = 0;
951         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
952                 struct keybinding *keybinding = &default_keybindings[i];
954                 if (keybinding->request != request)
955                         continue;
957                 if (!string_format_from(buf, &pos, "%s%s", sep,
958                                         get_key_name(keybinding->alias)))
959                         return "Too many keybindings!";
960                 sep = ", ";
961         }
963         return buf;
966 struct run_request {
967         enum keymap keymap;
968         int key;
969         char cmd[SIZEOF_STR];
970 };
972 static struct run_request *run_request;
973 static size_t run_requests;
975 static enum request
976 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
978         struct run_request *req;
979         char cmd[SIZEOF_STR];
980         size_t bufpos;
982         for (bufpos = 0; argc > 0; argc--, argv++)
983                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
984                         return REQ_NONE;
986         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
987         if (!req)
988                 return REQ_NONE;
990         run_request = req;
991         req = &run_request[run_requests++];
992         string_copy(req->cmd, cmd);
993         req->keymap = keymap;
994         req->key = key;
996         return REQ_NONE + run_requests;
999 static struct run_request *
1000 get_run_request(enum request request)
1002         if (request <= REQ_NONE)
1003                 return NULL;
1004         return &run_request[request - REQ_NONE - 1];
1007 static void
1008 add_builtin_run_requests(void)
1010         struct {
1011                 enum keymap keymap;
1012                 int key;
1013                 const char *argv[1];
1014         } reqs[] = {
1015                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1016                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1017         };
1018         int i;
1020         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1021                 enum request req;
1023                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1024                 if (req != REQ_NONE)
1025                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1026         }
1029 /*
1030  * User config file handling.
1031  */
1033 static struct int_map color_map[] = {
1034 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1035         COLOR_MAP(DEFAULT),
1036         COLOR_MAP(BLACK),
1037         COLOR_MAP(BLUE),
1038         COLOR_MAP(CYAN),
1039         COLOR_MAP(GREEN),
1040         COLOR_MAP(MAGENTA),
1041         COLOR_MAP(RED),
1042         COLOR_MAP(WHITE),
1043         COLOR_MAP(YELLOW),
1044 };
1046 #define set_color(color, name) \
1047         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1049 static struct int_map attr_map[] = {
1050 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1051         ATTR_MAP(NORMAL),
1052         ATTR_MAP(BLINK),
1053         ATTR_MAP(BOLD),
1054         ATTR_MAP(DIM),
1055         ATTR_MAP(REVERSE),
1056         ATTR_MAP(STANDOUT),
1057         ATTR_MAP(UNDERLINE),
1058 };
1060 #define set_attribute(attr, name) \
1061         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1063 static int   config_lineno;
1064 static bool  config_errors;
1065 static const char *config_msg;
1067 /* Wants: object fgcolor bgcolor [attr] */
1068 static int
1069 option_color_command(int argc, const char *argv[])
1071         struct line_info *info;
1073         if (argc != 3 && argc != 4) {
1074                 config_msg = "Wrong number of arguments given to color command";
1075                 return ERR;
1076         }
1078         info = get_line_info(argv[0]);
1079         if (!info) {
1080                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1081                         info = get_line_info("delimiter");
1083                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1084                         info = get_line_info("date");
1086                 } else {
1087                         config_msg = "Unknown color name";
1088                         return ERR;
1089                 }
1090         }
1092         if (set_color(&info->fg, argv[1]) == ERR ||
1093             set_color(&info->bg, argv[2]) == ERR) {
1094                 config_msg = "Unknown color";
1095                 return ERR;
1096         }
1098         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1099                 config_msg = "Unknown attribute";
1100                 return ERR;
1101         }
1103         return OK;
1106 static bool parse_bool(const char *s)
1108         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1109                 !strcmp(s, "yes")) ? TRUE : FALSE;
1112 static int
1113 parse_int(const char *s, int default_value, int min, int max)
1115         int value = atoi(s);
1117         return (value < min || value > max) ? default_value : value;
1120 /* Wants: name = value */
1121 static int
1122 option_set_command(int argc, const char *argv[])
1124         if (argc != 3) {
1125                 config_msg = "Wrong number of arguments given to set command";
1126                 return ERR;
1127         }
1129         if (strcmp(argv[1], "=")) {
1130                 config_msg = "No value assigned";
1131                 return ERR;
1132         }
1134         if (!strcmp(argv[0], "show-author")) {
1135                 opt_author = parse_bool(argv[2]);
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "show-date")) {
1140                 opt_date = parse_bool(argv[2]);
1141                 return OK;
1142         }
1144         if (!strcmp(argv[0], "show-rev-graph")) {
1145                 opt_rev_graph = parse_bool(argv[2]);
1146                 return OK;
1147         }
1149         if (!strcmp(argv[0], "show-refs")) {
1150                 opt_show_refs = parse_bool(argv[2]);
1151                 return OK;
1152         }
1154         if (!strcmp(argv[0], "show-line-numbers")) {
1155                 opt_line_number = parse_bool(argv[2]);
1156                 return OK;
1157         }
1159         if (!strcmp(argv[0], "line-graphics")) {
1160                 opt_line_graphics = parse_bool(argv[2]);
1161                 return OK;
1162         }
1164         if (!strcmp(argv[0], "line-number-interval")) {
1165                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1166                 return OK;
1167         }
1169         if (!strcmp(argv[0], "author-width")) {
1170                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1171                 return OK;
1172         }
1174         if (!strcmp(argv[0], "tab-size")) {
1175                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1176                 return OK;
1177         }
1179         if (!strcmp(argv[0], "commit-encoding")) {
1180                 const char *arg = argv[2];
1181                 int arglen = strlen(arg);
1183                 switch (arg[0]) {
1184                 case '"':
1185                 case '\'':
1186                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1187                                 config_msg = "Unmatched quotation";
1188                                 return ERR;
1189                         }
1190                         arg += 1; arglen -= 2;
1191                 default:
1192                         string_ncopy(opt_encoding, arg, strlen(arg));
1193                         return OK;
1194                 }
1195         }
1197         config_msg = "Unknown variable name";
1198         return ERR;
1201 /* Wants: mode request key */
1202 static int
1203 option_bind_command(int argc, const char *argv[])
1205         enum request request;
1206         int keymap;
1207         int key;
1209         if (argc < 3) {
1210                 config_msg = "Wrong number of arguments given to bind command";
1211                 return ERR;
1212         }
1214         if (set_keymap(&keymap, argv[0]) == ERR) {
1215                 config_msg = "Unknown key map";
1216                 return ERR;
1217         }
1219         key = get_key_value(argv[1]);
1220         if (key == ERR) {
1221                 config_msg = "Unknown key";
1222                 return ERR;
1223         }
1225         request = get_request(argv[2]);
1226         if (request == REQ_NONE) {
1227                 const char *obsolete[] = { "cherry-pick" };
1228                 size_t namelen = strlen(argv[2]);
1229                 int i;
1231                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1232                         if (namelen == strlen(obsolete[i]) &&
1233                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1234                                 config_msg = "Obsolete request name";
1235                                 return ERR;
1236                         }
1237                 }
1238         }
1239         if (request == REQ_NONE && *argv[2]++ == '!')
1240                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1241         if (request == REQ_NONE) {
1242                 config_msg = "Unknown request name";
1243                 return ERR;
1244         }
1246         add_keybinding(keymap, request, key);
1248         return OK;
1251 static int
1252 set_option(const char *opt, char *value)
1254         const char *argv[SIZEOF_ARG];
1255         int valuelen;
1256         int argc = 0;
1258         /* Tokenize */
1259         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1260                 argv[argc++] = value;
1261                 value += valuelen;
1263                 /* Nothing more to tokenize or last available token. */
1264                 if (!*value || argc >= ARRAY_SIZE(argv))
1265                         break;
1267                 *value++ = 0;
1268                 while (isspace(*value))
1269                         value++;
1270         }
1272         if (!strcmp(opt, "color"))
1273                 return option_color_command(argc, argv);
1275         if (!strcmp(opt, "set"))
1276                 return option_set_command(argc, argv);
1278         if (!strcmp(opt, "bind"))
1279                 return option_bind_command(argc, argv);
1281         config_msg = "Unknown option command";
1282         return ERR;
1285 static int
1286 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1288         int status = OK;
1290         config_lineno++;
1291         config_msg = "Internal error";
1293         /* Check for comment markers, since read_properties() will
1294          * only ensure opt and value are split at first " \t". */
1295         optlen = strcspn(opt, "#");
1296         if (optlen == 0)
1297                 return OK;
1299         if (opt[optlen] != 0) {
1300                 config_msg = "No option value";
1301                 status = ERR;
1303         }  else {
1304                 /* Look for comment endings in the value. */
1305                 size_t len = strcspn(value, "#");
1307                 if (len < valuelen) {
1308                         valuelen = len;
1309                         value[valuelen] = 0;
1310                 }
1312                 status = set_option(opt, value);
1313         }
1315         if (status == ERR) {
1316                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1317                         config_lineno, (int) optlen, opt, config_msg);
1318                 config_errors = TRUE;
1319         }
1321         /* Always keep going if errors are encountered. */
1322         return OK;
1325 static void
1326 load_option_file(const char *path)
1328         FILE *file;
1330         /* It's ok that the file doesn't exist. */
1331         file = fopen(path, "r");
1332         if (!file)
1333                 return;
1335         config_lineno = 0;
1336         config_errors = FALSE;
1338         if (read_properties(file, " \t", read_option) == ERR ||
1339             config_errors == TRUE)
1340                 fprintf(stderr, "Errors while loading %s.\n", path);
1343 static int
1344 load_options(void)
1346         const char *home = getenv("HOME");
1347         const char *tigrc_user = getenv("TIGRC_USER");
1348         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1349         char buf[SIZEOF_STR];
1351         add_builtin_run_requests();
1353         if (!tigrc_system) {
1354                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1355                         return ERR;
1356                 tigrc_system = buf;
1357         }
1358         load_option_file(tigrc_system);
1360         if (!tigrc_user) {
1361                 if (!home || !string_format(buf, "%s/.tigrc", home))
1362                         return ERR;
1363                 tigrc_user = buf;
1364         }
1365         load_option_file(tigrc_user);
1367         return OK;
1371 /*
1372  * The viewer
1373  */
1375 struct view;
1376 struct view_ops;
1378 /* The display array of active views and the index of the current view. */
1379 static struct view *display[2];
1380 static unsigned int current_view;
1382 /* Reading from the prompt? */
1383 static bool input_mode = FALSE;
1385 #define foreach_displayed_view(view, i) \
1386         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1388 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1390 /* Current head and commit ID */
1391 static char ref_blob[SIZEOF_REF]        = "";
1392 static char ref_commit[SIZEOF_REF]      = "HEAD";
1393 static char ref_head[SIZEOF_REF]        = "HEAD";
1395 struct view {
1396         const char *name;       /* View name */
1397         const char *cmd_fmt;    /* Default command line format */
1398         const char *cmd_env;    /* Command line set via environment */
1399         const char *id;         /* Points to either of ref_{head,commit,blob} */
1401         struct view_ops *ops;   /* View operations */
1403         enum keymap keymap;     /* What keymap does this view have */
1404         bool git_dir;           /* Whether the view requires a git directory. */
1406         char cmd[SIZEOF_STR];   /* Command buffer */
1407         char ref[SIZEOF_REF];   /* Hovered commit reference */
1408         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1410         int height, width;      /* The width and height of the main window */
1411         WINDOW *win;            /* The main window */
1412         WINDOW *title;          /* The title window living below the main window */
1414         /* Navigation */
1415         unsigned long offset;   /* Offset of the window top */
1416         unsigned long lineno;   /* Current line number */
1418         /* Searching */
1419         char grep[SIZEOF_STR];  /* Search string */
1420         regex_t *regex;         /* Pre-compiled regex */
1422         /* If non-NULL, points to the view that opened this view. If this view
1423          * is closed tig will switch back to the parent view. */
1424         struct view *parent;
1426         /* Buffering */
1427         size_t lines;           /* Total number of lines */
1428         struct line *line;      /* Line index */
1429         size_t line_alloc;      /* Total number of allocated lines */
1430         size_t line_size;       /* Total number of used lines */
1431         unsigned int digits;    /* Number of digits in the lines member. */
1433         /* Drawing */
1434         struct line *curline;   /* Line currently being drawn. */
1435         enum line_type curtype; /* Attribute currently used for drawing. */
1436         unsigned long col;      /* Column when drawing. */
1438         /* Loading */
1439         FILE *pipe;
1440         time_t start_time;
1441 };
1443 struct view_ops {
1444         /* What type of content being displayed. Used in the title bar. */
1445         const char *type;
1446         /* Open and reads in all view content. */
1447         bool (*open)(struct view *view);
1448         /* Read one line; updates view->line. */
1449         bool (*read)(struct view *view, char *data);
1450         /* Draw one line; @lineno must be < view->height. */
1451         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1452         /* Depending on view handle a special requests. */
1453         enum request (*request)(struct view *view, enum request request, struct line *line);
1454         /* Search for regex in a line. */
1455         bool (*grep)(struct view *view, struct line *line);
1456         /* Select line */
1457         void (*select)(struct view *view, struct line *line);
1458 };
1460 static struct view_ops blame_ops;
1461 static struct view_ops blob_ops;
1462 static struct view_ops help_ops;
1463 static struct view_ops log_ops;
1464 static struct view_ops main_ops;
1465 static struct view_ops pager_ops;
1466 static struct view_ops stage_ops;
1467 static struct view_ops status_ops;
1468 static struct view_ops tree_ops;
1470 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1471         { name, cmd, #env, ref, ops, map, git }
1473 #define VIEW_(id, name, ops, git, ref) \
1474         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1477 static struct view views[] = {
1478         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1479         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1480         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1481         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1482         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1483         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1484         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1485         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1486         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1487         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1488 };
1490 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1491 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1493 #define foreach_view(view, i) \
1494         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1496 #define view_is_displayed(view) \
1497         (view == display[0] || view == display[1])
1500 enum line_graphic {
1501         LINE_GRAPHIC_VLINE
1502 };
1504 static int line_graphics[] = {
1505         /* LINE_GRAPHIC_VLINE: */ '|'
1506 };
1508 static inline void
1509 set_view_attr(struct view *view, enum line_type type)
1511         if (!view->curline->selected && view->curtype != type) {
1512                 wattrset(view->win, get_line_attr(type));
1513                 wchgat(view->win, -1, 0, type, NULL);
1514                 view->curtype = type;
1515         }
1518 static int
1519 draw_chars(struct view *view, enum line_type type, const char *string,
1520            int max_len, bool use_tilde)
1522         int len = 0;
1523         int col = 0;
1524         int trimmed = FALSE;
1526         if (max_len <= 0)
1527                 return 0;
1529         if (opt_utf8) {
1530                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1531         } else {
1532                 col = len = strlen(string);
1533                 if (len > max_len) {
1534                         if (use_tilde) {
1535                                 max_len -= 1;
1536                         }
1537                         col = len = max_len;
1538                         trimmed = TRUE;
1539                 }
1540         }
1542         set_view_attr(view, type);
1543         waddnstr(view->win, string, len);
1544         if (trimmed && use_tilde) {
1545                 set_view_attr(view, LINE_DELIMITER);
1546                 waddch(view->win, '~');
1547                 col++;
1548         }
1550         return col;
1553 static int
1554 draw_space(struct view *view, enum line_type type, int max, int spaces)
1556         static char space[] = "                    ";
1557         int col = 0;
1559         spaces = MIN(max, spaces);
1561         while (spaces > 0) {
1562                 int len = MIN(spaces, sizeof(space) - 1);
1564                 col += draw_chars(view, type, space, spaces, FALSE);
1565                 spaces -= len;
1566         }
1568         return col;
1571 static bool
1572 draw_lineno(struct view *view, unsigned int lineno)
1574         char number[10];
1575         int digits3 = view->digits < 3 ? 3 : view->digits;
1576         int max_number = MIN(digits3, STRING_SIZE(number));
1577         int max = view->width - view->col;
1578         int col;
1580         if (max < max_number)
1581                 max_number = max;
1583         lineno += view->offset + 1;
1584         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1585                 static char fmt[] = "%1ld";
1587                 if (view->digits <= 9)
1588                         fmt[1] = '0' + digits3;
1590                 if (!string_format(number, fmt, lineno))
1591                         number[0] = 0;
1592                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1593         } else {
1594                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1595         }
1597         if (col < max) {
1598                 set_view_attr(view, LINE_DEFAULT);
1599                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1600                 col++;
1601         }
1603         if (col < max)
1604                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1605         view->col += col;
1607         return view->width - view->col <= 0;
1610 static bool
1611 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1613         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1614         return view->width - view->col <= 0;
1617 static bool
1618 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1620         int max = view->width - view->col;
1621         int i;
1623         if (max < size)
1624                 size = max;
1626         set_view_attr(view, type);
1627         /* Using waddch() instead of waddnstr() ensures that
1628          * they'll be rendered correctly for the cursor line. */
1629         for (i = 0; i < size; i++)
1630                 waddch(view->win, graphic[i]);
1632         view->col += size;
1633         if (size < max) {
1634                 waddch(view->win, ' ');
1635                 view->col++;
1636         }
1638         return view->width - view->col <= 0;
1641 static bool
1642 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1644         int max = MIN(view->width - view->col, len);
1645         int col;
1647         if (text)
1648                 col = draw_chars(view, type, text, max - 1, trim);
1649         else
1650                 col = draw_space(view, type, max - 1, max - 1);
1652         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1653         return view->width - view->col <= 0;
1656 static bool
1657 draw_date(struct view *view, struct tm *time)
1659         char buf[DATE_COLS];
1660         char *date;
1661         int timelen = 0;
1663         if (time)
1664                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1665         date = timelen ? buf : NULL;
1667         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1670 static bool
1671 draw_view_line(struct view *view, unsigned int lineno)
1673         struct line *line;
1674         bool selected = (view->offset + lineno == view->lineno);
1675         bool draw_ok;
1677         assert(view_is_displayed(view));
1679         if (view->offset + lineno >= view->lines)
1680                 return FALSE;
1682         line = &view->line[view->offset + lineno];
1684         wmove(view->win, lineno, 0);
1685         view->col = 0;
1686         view->curline = line;
1687         view->curtype = LINE_NONE;
1688         line->selected = FALSE;
1690         if (selected) {
1691                 set_view_attr(view, LINE_CURSOR);
1692                 line->selected = TRUE;
1693                 view->ops->select(view, line);
1694         } else if (line->selected) {
1695                 wclrtoeol(view->win);
1696         }
1698         scrollok(view->win, FALSE);
1699         draw_ok = view->ops->draw(view, line, lineno);
1700         scrollok(view->win, TRUE);
1702         return draw_ok;
1705 static void
1706 redraw_view_dirty(struct view *view)
1708         bool dirty = FALSE;
1709         int lineno;
1711         for (lineno = 0; lineno < view->height; lineno++) {
1712                 struct line *line = &view->line[view->offset + lineno];
1714                 if (!line->dirty)
1715                         continue;
1716                 line->dirty = 0;
1717                 dirty = TRUE;
1718                 if (!draw_view_line(view, lineno))
1719                         break;
1720         }
1722         if (!dirty)
1723                 return;
1724         redrawwin(view->win);
1725         if (input_mode)
1726                 wnoutrefresh(view->win);
1727         else
1728                 wrefresh(view->win);
1731 static void
1732 redraw_view_from(struct view *view, int lineno)
1734         assert(0 <= lineno && lineno < view->height);
1736         for (; lineno < view->height; lineno++) {
1737                 if (!draw_view_line(view, lineno))
1738                         break;
1739         }
1741         redrawwin(view->win);
1742         if (input_mode)
1743                 wnoutrefresh(view->win);
1744         else
1745                 wrefresh(view->win);
1748 static void
1749 redraw_view(struct view *view)
1751         wclear(view->win);
1752         redraw_view_from(view, 0);
1756 static void
1757 update_view_title(struct view *view)
1759         char buf[SIZEOF_STR];
1760         char state[SIZEOF_STR];
1761         size_t bufpos = 0, statelen = 0;
1763         assert(view_is_displayed(view));
1765         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1766                 unsigned int view_lines = view->offset + view->height;
1767                 unsigned int lines = view->lines
1768                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1769                                    : 0;
1771                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1772                                    view->ops->type,
1773                                    view->lineno + 1,
1774                                    view->lines,
1775                                    lines);
1777                 if (view->pipe) {
1778                         time_t secs = time(NULL) - view->start_time;
1780                         /* Three git seconds are a long time ... */
1781                         if (secs > 2)
1782                                 string_format_from(state, &statelen, " %lds", secs);
1783                 }
1784         }
1786         string_format_from(buf, &bufpos, "[%s]", view->name);
1787         if (*view->ref && bufpos < view->width) {
1788                 size_t refsize = strlen(view->ref);
1789                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1791                 if (minsize < view->width)
1792                         refsize = view->width - minsize + 7;
1793                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1794         }
1796         if (statelen && bufpos < view->width) {
1797                 string_format_from(buf, &bufpos, " %s", state);
1798         }
1800         if (view == display[current_view])
1801                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1802         else
1803                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1805         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1806         wclrtoeol(view->title);
1807         wmove(view->title, 0, view->width - 1);
1809         if (input_mode)
1810                 wnoutrefresh(view->title);
1811         else
1812                 wrefresh(view->title);
1815 static void
1816 resize_display(void)
1818         int offset, i;
1819         struct view *base = display[0];
1820         struct view *view = display[1] ? display[1] : display[0];
1822         /* Setup window dimensions */
1824         getmaxyx(stdscr, base->height, base->width);
1826         /* Make room for the status window. */
1827         base->height -= 1;
1829         if (view != base) {
1830                 /* Horizontal split. */
1831                 view->width   = base->width;
1832                 view->height  = SCALE_SPLIT_VIEW(base->height);
1833                 base->height -= view->height;
1835                 /* Make room for the title bar. */
1836                 view->height -= 1;
1837         }
1839         /* Make room for the title bar. */
1840         base->height -= 1;
1842         offset = 0;
1844         foreach_displayed_view (view, i) {
1845                 if (!view->win) {
1846                         view->win = newwin(view->height, 0, offset, 0);
1847                         if (!view->win)
1848                                 die("Failed to create %s view", view->name);
1850                         scrollok(view->win, TRUE);
1852                         view->title = newwin(1, 0, offset + view->height, 0);
1853                         if (!view->title)
1854                                 die("Failed to create title window");
1856                 } else {
1857                         wresize(view->win, view->height, view->width);
1858                         mvwin(view->win,   offset, 0);
1859                         mvwin(view->title, offset + view->height, 0);
1860                 }
1862                 offset += view->height + 1;
1863         }
1866 static void
1867 redraw_display(void)
1869         struct view *view;
1870         int i;
1872         foreach_displayed_view (view, i) {
1873                 redraw_view(view);
1874                 update_view_title(view);
1875         }
1878 static void
1879 update_display_cursor(struct view *view)
1881         /* Move the cursor to the right-most column of the cursor line.
1882          *
1883          * XXX: This could turn out to be a bit expensive, but it ensures that
1884          * the cursor does not jump around. */
1885         if (view->lines) {
1886                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1887                 wrefresh(view->win);
1888         }
1891 /*
1892  * Navigation
1893  */
1895 /* Scrolling backend */
1896 static void
1897 do_scroll_view(struct view *view, int lines)
1899         bool redraw_current_line = FALSE;
1901         /* The rendering expects the new offset. */
1902         view->offset += lines;
1904         assert(0 <= view->offset && view->offset < view->lines);
1905         assert(lines);
1907         /* Move current line into the view. */
1908         if (view->lineno < view->offset) {
1909                 view->lineno = view->offset;
1910                 redraw_current_line = TRUE;
1911         } else if (view->lineno >= view->offset + view->height) {
1912                 view->lineno = view->offset + view->height - 1;
1913                 redraw_current_line = TRUE;
1914         }
1916         assert(view->offset <= view->lineno && view->lineno < view->lines);
1918         /* Redraw the whole screen if scrolling is pointless. */
1919         if (view->height < ABS(lines)) {
1920                 redraw_view(view);
1922         } else {
1923                 int line = lines > 0 ? view->height - lines : 0;
1924                 int end = line + ABS(lines);
1926                 wscrl(view->win, lines);
1928                 for (; line < end; line++) {
1929                         if (!draw_view_line(view, line))
1930                                 break;
1931                 }
1933                 if (redraw_current_line)
1934                         draw_view_line(view, view->lineno - view->offset);
1935         }
1937         redrawwin(view->win);
1938         wrefresh(view->win);
1939         report("");
1942 /* Scroll frontend */
1943 static void
1944 scroll_view(struct view *view, enum request request)
1946         int lines = 1;
1948         assert(view_is_displayed(view));
1950         switch (request) {
1951         case REQ_SCROLL_PAGE_DOWN:
1952                 lines = view->height;
1953         case REQ_SCROLL_LINE_DOWN:
1954                 if (view->offset + lines > view->lines)
1955                         lines = view->lines - view->offset;
1957                 if (lines == 0 || view->offset + view->height >= view->lines) {
1958                         report("Cannot scroll beyond the last line");
1959                         return;
1960                 }
1961                 break;
1963         case REQ_SCROLL_PAGE_UP:
1964                 lines = view->height;
1965         case REQ_SCROLL_LINE_UP:
1966                 if (lines > view->offset)
1967                         lines = view->offset;
1969                 if (lines == 0) {
1970                         report("Cannot scroll beyond the first line");
1971                         return;
1972                 }
1974                 lines = -lines;
1975                 break;
1977         default:
1978                 die("request %d not handled in switch", request);
1979         }
1981         do_scroll_view(view, lines);
1984 /* Cursor moving */
1985 static void
1986 move_view(struct view *view, enum request request)
1988         int scroll_steps = 0;
1989         int steps;
1991         switch (request) {
1992         case REQ_MOVE_FIRST_LINE:
1993                 steps = -view->lineno;
1994                 break;
1996         case REQ_MOVE_LAST_LINE:
1997                 steps = view->lines - view->lineno - 1;
1998                 break;
2000         case REQ_MOVE_PAGE_UP:
2001                 steps = view->height > view->lineno
2002                       ? -view->lineno : -view->height;
2003                 break;
2005         case REQ_MOVE_PAGE_DOWN:
2006                 steps = view->lineno + view->height >= view->lines
2007                       ? view->lines - view->lineno - 1 : view->height;
2008                 break;
2010         case REQ_MOVE_UP:
2011                 steps = -1;
2012                 break;
2014         case REQ_MOVE_DOWN:
2015                 steps = 1;
2016                 break;
2018         default:
2019                 die("request %d not handled in switch", request);
2020         }
2022         if (steps <= 0 && view->lineno == 0) {
2023                 report("Cannot move beyond the first line");
2024                 return;
2026         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2027                 report("Cannot move beyond the last line");
2028                 return;
2029         }
2031         /* Move the current line */
2032         view->lineno += steps;
2033         assert(0 <= view->lineno && view->lineno < view->lines);
2035         /* Check whether the view needs to be scrolled */
2036         if (view->lineno < view->offset ||
2037             view->lineno >= view->offset + view->height) {
2038                 scroll_steps = steps;
2039                 if (steps < 0 && -steps > view->offset) {
2040                         scroll_steps = -view->offset;
2042                 } else if (steps > 0) {
2043                         if (view->lineno == view->lines - 1 &&
2044                             view->lines > view->height) {
2045                                 scroll_steps = view->lines - view->offset - 1;
2046                                 if (scroll_steps >= view->height)
2047                                         scroll_steps -= view->height - 1;
2048                         }
2049                 }
2050         }
2052         if (!view_is_displayed(view)) {
2053                 view->offset += scroll_steps;
2054                 assert(0 <= view->offset && view->offset < view->lines);
2055                 view->ops->select(view, &view->line[view->lineno]);
2056                 return;
2057         }
2059         /* Repaint the old "current" line if we be scrolling */
2060         if (ABS(steps) < view->height)
2061                 draw_view_line(view, view->lineno - steps - view->offset);
2063         if (scroll_steps) {
2064                 do_scroll_view(view, scroll_steps);
2065                 return;
2066         }
2068         /* Draw the current line */
2069         draw_view_line(view, view->lineno - view->offset);
2071         redrawwin(view->win);
2072         wrefresh(view->win);
2073         report("");
2077 /*
2078  * Searching
2079  */
2081 static void search_view(struct view *view, enum request request);
2083 static bool
2084 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2086         assert(view_is_displayed(view));
2088         if (!view->ops->grep(view, line))
2089                 return FALSE;
2091         if (lineno - view->offset >= view->height) {
2092                 view->offset = lineno;
2093                 view->lineno = lineno;
2094                 redraw_view(view);
2096         } else {
2097                 unsigned long old_lineno = view->lineno - view->offset;
2099                 view->lineno = lineno;
2100                 draw_view_line(view, old_lineno);
2102                 draw_view_line(view, view->lineno - view->offset);
2103                 redrawwin(view->win);
2104                 wrefresh(view->win);
2105         }
2107         report("Line %ld matches '%s'", lineno + 1, view->grep);
2108         return TRUE;
2111 static void
2112 find_next(struct view *view, enum request request)
2114         unsigned long lineno = view->lineno;
2115         int direction;
2117         if (!*view->grep) {
2118                 if (!*opt_search)
2119                         report("No previous search");
2120                 else
2121                         search_view(view, request);
2122                 return;
2123         }
2125         switch (request) {
2126         case REQ_SEARCH:
2127         case REQ_FIND_NEXT:
2128                 direction = 1;
2129                 break;
2131         case REQ_SEARCH_BACK:
2132         case REQ_FIND_PREV:
2133                 direction = -1;
2134                 break;
2136         default:
2137                 return;
2138         }
2140         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2141                 lineno += direction;
2143         /* Note, lineno is unsigned long so will wrap around in which case it
2144          * will become bigger than view->lines. */
2145         for (; lineno < view->lines; lineno += direction) {
2146                 struct line *line = &view->line[lineno];
2148                 if (find_next_line(view, lineno, line))
2149                         return;
2150         }
2152         report("No match found for '%s'", view->grep);
2155 static void
2156 search_view(struct view *view, enum request request)
2158         int regex_err;
2160         if (view->regex) {
2161                 regfree(view->regex);
2162                 *view->grep = 0;
2163         } else {
2164                 view->regex = calloc(1, sizeof(*view->regex));
2165                 if (!view->regex)
2166                         return;
2167         }
2169         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2170         if (regex_err != 0) {
2171                 char buf[SIZEOF_STR] = "unknown error";
2173                 regerror(regex_err, view->regex, buf, sizeof(buf));
2174                 report("Search failed: %s", buf);
2175                 return;
2176         }
2178         string_copy(view->grep, opt_search);
2180         find_next(view, request);
2183 /*
2184  * Incremental updating
2185  */
2187 static void
2188 reset_view(struct view *view)
2190         int i;
2192         for (i = 0; i < view->lines; i++)
2193                 free(view->line[i].data);
2194         free(view->line);
2196         view->line = NULL;
2197         view->offset = 0;
2198         view->lines  = 0;
2199         view->lineno = 0;
2200         view->line_size = 0;
2201         view->line_alloc = 0;
2202         view->vid[0] = 0;
2205 static void
2206 end_update(struct view *view, bool force)
2208         if (!view->pipe)
2209                 return;
2210         while (!view->ops->read(view, NULL))
2211                 if (!force)
2212                         return;
2213         set_nonblocking_input(FALSE);
2214         if (view->pipe == stdin)
2215                 fclose(view->pipe);
2216         else
2217                 pclose(view->pipe);
2218         view->pipe = NULL;
2221 static bool
2222 begin_update(struct view *view, bool refresh)
2224         if (opt_cmd[0]) {
2225                 string_copy(view->cmd, opt_cmd);
2226                 opt_cmd[0] = 0;
2227                 /* When running random commands, initially show the
2228                  * command in the title. However, it maybe later be
2229                  * overwritten if a commit line is selected. */
2230                 if (view == VIEW(REQ_VIEW_PAGER))
2231                         string_copy(view->ref, view->cmd);
2232                 else
2233                         view->ref[0] = 0;
2235         } else if (view == VIEW(REQ_VIEW_TREE)) {
2236                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2237                 char path[SIZEOF_STR];
2239                 if (strcmp(view->vid, view->id))
2240                         opt_path[0] = path[0] = 0;
2241                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2242                         return FALSE;
2244                 if (!string_format(view->cmd, format, view->id, path))
2245                         return FALSE;
2247         } else if (!refresh) {
2248                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2249                 const char *id = view->id;
2251                 if (!string_format(view->cmd, format, id, id, id, id, id))
2252                         return FALSE;
2254                 /* Put the current ref_* value to the view title ref
2255                  * member. This is needed by the blob view. Most other
2256                  * views sets it automatically after loading because the
2257                  * first line is a commit line. */
2258                 string_copy_rev(view->ref, view->id);
2259         }
2261         /* Special case for the pager view. */
2262         if (opt_pipe) {
2263                 view->pipe = opt_pipe;
2264                 opt_pipe = NULL;
2265         } else {
2266                 view->pipe = popen(view->cmd, "r");
2267         }
2269         if (!view->pipe)
2270                 return FALSE;
2272         set_nonblocking_input(TRUE);
2273         reset_view(view);
2274         string_copy_rev(view->vid, view->id);
2276         view->start_time = time(NULL);
2278         return TRUE;
2281 #define ITEM_CHUNK_SIZE 256
2282 static void *
2283 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2285         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2286         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2288         if (mem == NULL || num_chunks != num_chunks_new) {
2289                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2290                 mem = realloc(mem, *size * item_size);
2291         }
2293         return mem;
2296 static struct line *
2297 realloc_lines(struct view *view, size_t line_size)
2299         size_t alloc = view->line_alloc;
2300         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2301                                          sizeof(*view->line));
2303         if (!tmp)
2304                 return NULL;
2306         view->line = tmp;
2307         view->line_alloc = alloc;
2308         view->line_size = line_size;
2309         return view->line;
2312 static bool
2313 update_view(struct view *view)
2315         char in_buffer[BUFSIZ];
2316         char out_buffer[BUFSIZ * 2];
2317         char *line;
2318         /* The number of lines to read. If too low it will cause too much
2319          * redrawing (and possible flickering), if too high responsiveness
2320          * will suffer. */
2321         unsigned long lines = view->height;
2322         int redraw_from = -1;
2324         if (!view->pipe)
2325                 return TRUE;
2327         /* Only redraw if lines are visible. */
2328         if (view->offset + view->height >= view->lines)
2329                 redraw_from = view->lines - view->offset;
2331         /* FIXME: This is probably not perfect for backgrounded views. */
2332         if (!realloc_lines(view, view->lines + lines))
2333                 goto alloc_error;
2335         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2336                 size_t linelen = strlen(line);
2338                 if (linelen)
2339                         line[linelen - 1] = 0;
2341                 if (opt_iconv != ICONV_NONE) {
2342                         ICONV_CONST char *inbuf = line;
2343                         size_t inlen = linelen;
2345                         char *outbuf = out_buffer;
2346                         size_t outlen = sizeof(out_buffer);
2348                         size_t ret;
2350                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2351                         if (ret != (size_t) -1) {
2352                                 line = out_buffer;
2353                                 linelen = strlen(out_buffer);
2354                         }
2355                 }
2357                 if (!view->ops->read(view, line))
2358                         goto alloc_error;
2360                 if (lines-- == 1)
2361                         break;
2362         }
2364         {
2365                 int digits;
2367                 lines = view->lines;
2368                 for (digits = 0; lines; digits++)
2369                         lines /= 10;
2371                 /* Keep the displayed view in sync with line number scaling. */
2372                 if (digits != view->digits) {
2373                         view->digits = digits;
2374                         redraw_from = 0;
2375                 }
2376         }
2378         if (!view_is_displayed(view))
2379                 goto check_pipe;
2381         if (view == VIEW(REQ_VIEW_TREE)) {
2382                 /* Clear the view and redraw everything since the tree sorting
2383                  * might have rearranged things. */
2384                 redraw_view(view);
2386         } else if (redraw_from >= 0) {
2387                 /* If this is an incremental update, redraw the previous line
2388                  * since for commits some members could have changed when
2389                  * loading the main view. */
2390                 if (redraw_from > 0)
2391                         redraw_from--;
2393                 /* Since revision graph visualization requires knowledge
2394                  * about the parent commit, it causes a further one-off
2395                  * needed to be redrawn for incremental updates. */
2396                 if (redraw_from > 0 && opt_rev_graph)
2397                         redraw_from--;
2399                 /* Incrementally draw avoids flickering. */
2400                 redraw_view_from(view, redraw_from);
2401         }
2403         if (view == VIEW(REQ_VIEW_BLAME))
2404                 redraw_view_dirty(view);
2406         /* Update the title _after_ the redraw so that if the redraw picks up a
2407          * commit reference in view->ref it'll be available here. */
2408         update_view_title(view);
2410 check_pipe:
2411         if (ferror(view->pipe) && errno != 0) {
2412                 report("Failed to read: %s", strerror(errno));
2413                 end_update(view, TRUE);
2415         } else if (feof(view->pipe)) {
2416                 report("");
2417                 end_update(view, FALSE);
2418         }
2420         return TRUE;
2422 alloc_error:
2423         report("Allocation failure");
2424         end_update(view, TRUE);
2425         return FALSE;
2428 static struct line *
2429 add_line_data(struct view *view, void *data, enum line_type type)
2431         struct line *line = &view->line[view->lines++];
2433         memset(line, 0, sizeof(*line));
2434         line->type = type;
2435         line->data = data;
2437         return line;
2440 static struct line *
2441 add_line_text(struct view *view, const char *text, enum line_type type)
2443         char *data = text ? strdup(text) : NULL;
2445         return data ? add_line_data(view, data, type) : NULL;
2449 /*
2450  * View opening
2451  */
2453 enum open_flags {
2454         OPEN_DEFAULT = 0,       /* Use default view switching. */
2455         OPEN_SPLIT = 1,         /* Split current view. */
2456         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2457         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2458         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2459         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2460 };
2462 static void
2463 open_view(struct view *prev, enum request request, enum open_flags flags)
2465         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2466         bool split = !!(flags & OPEN_SPLIT);
2467         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2468         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2469         struct view *view = VIEW(request);
2470         int nviews = displayed_views();
2471         struct view *base_view = display[0];
2473         if (view == prev && nviews == 1 && !reload) {
2474                 report("Already in %s view", view->name);
2475                 return;
2476         }
2478         if (view->git_dir && !opt_git_dir[0]) {
2479                 report("The %s view is disabled in pager view", view->name);
2480                 return;
2481         }
2483         if (split) {
2484                 display[1] = view;
2485                 if (!backgrounded)
2486                         current_view = 1;
2487         } else if (!nomaximize) {
2488                 /* Maximize the current view. */
2489                 memset(display, 0, sizeof(display));
2490                 current_view = 0;
2491                 display[current_view] = view;
2492         }
2494         /* Resize the view when switching between split- and full-screen,
2495          * or when switching between two different full-screen views. */
2496         if (nviews != displayed_views() ||
2497             (nviews == 1 && base_view != display[0]))
2498                 resize_display();
2500         if (view->pipe)
2501                 end_update(view, TRUE);
2503         if (view->ops->open) {
2504                 if (!view->ops->open(view)) {
2505                         report("Failed to load %s view", view->name);
2506                         return;
2507                 }
2509         } else if ((reload || strcmp(view->vid, view->id)) &&
2510                    !begin_update(view, flags & OPEN_REFRESH)) {
2511                 report("Failed to load %s view", view->name);
2512                 return;
2513         }
2515         if (split && prev->lineno - prev->offset >= prev->height) {
2516                 /* Take the title line into account. */
2517                 int lines = prev->lineno - prev->offset - prev->height + 1;
2519                 /* Scroll the view that was split if the current line is
2520                  * outside the new limited view. */
2521                 do_scroll_view(prev, lines);
2522         }
2524         if (prev && view != prev) {
2525                 if (split && !backgrounded) {
2526                         /* "Blur" the previous view. */
2527                         update_view_title(prev);
2528                 }
2530                 view->parent = prev;
2531         }
2533         if (view->pipe && view->lines == 0) {
2534                 /* Clear the old view and let the incremental updating refill
2535                  * the screen. */
2536                 werase(view->win);
2537                 report("");
2538         } else if (view_is_displayed(view)) {
2539                 redraw_view(view);
2540                 report("");
2541         }
2543         /* If the view is backgrounded the above calls to report()
2544          * won't redraw the view title. */
2545         if (backgrounded)
2546                 update_view_title(view);
2549 static bool
2550 run_confirm(const char *cmd, const char *prompt)
2552         bool confirmation = prompt_yesno(prompt);
2554         if (confirmation)
2555                 system(cmd);
2557         return confirmation;
2560 static void
2561 open_external_viewer(const char *cmd)
2563         def_prog_mode();           /* save current tty modes */
2564         endwin();                  /* restore original tty modes */
2565         system(cmd);
2566         fprintf(stderr, "Press Enter to continue");
2567         getc(stdin);
2568         reset_prog_mode();
2569         redraw_display();
2572 static void
2573 open_mergetool(const char *file)
2575         char cmd[SIZEOF_STR];
2576         char file_sq[SIZEOF_STR];
2578         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2579             string_format(cmd, "git mergetool %s", file_sq)) {
2580                 open_external_viewer(cmd);
2581         }
2584 static void
2585 open_editor(bool from_root, const char *file)
2587         char cmd[SIZEOF_STR];
2588         char file_sq[SIZEOF_STR];
2589         const char *editor;
2590         char *prefix = from_root ? opt_cdup : "";
2592         editor = getenv("GIT_EDITOR");
2593         if (!editor && *opt_editor)
2594                 editor = opt_editor;
2595         if (!editor)
2596                 editor = getenv("VISUAL");
2597         if (!editor)
2598                 editor = getenv("EDITOR");
2599         if (!editor)
2600                 editor = "vi";
2602         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2603             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2604                 open_external_viewer(cmd);
2605         }
2608 static void
2609 open_run_request(enum request request)
2611         struct run_request *req = get_run_request(request);
2612         char buf[SIZEOF_STR * 2];
2613         size_t bufpos;
2614         char *cmd;
2616         if (!req) {
2617                 report("Unknown run request");
2618                 return;
2619         }
2621         bufpos = 0;
2622         cmd = req->cmd;
2624         while (cmd) {
2625                 char *next = strstr(cmd, "%(");
2626                 int len = next - cmd;
2627                 char *value;
2629                 if (!next) {
2630                         len = strlen(cmd);
2631                         value = "";
2633                 } else if (!strncmp(next, "%(head)", 7)) {
2634                         value = ref_head;
2636                 } else if (!strncmp(next, "%(commit)", 9)) {
2637                         value = ref_commit;
2639                 } else if (!strncmp(next, "%(blob)", 7)) {
2640                         value = ref_blob;
2642                 } else {
2643                         report("Unknown replacement in run request: `%s`", req->cmd);
2644                         return;
2645                 }
2647                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2648                         return;
2650                 if (next)
2651                         next = strchr(next, ')') + 1;
2652                 cmd = next;
2653         }
2655         open_external_viewer(buf);
2658 /*
2659  * User request switch noodle
2660  */
2662 static int
2663 view_driver(struct view *view, enum request request)
2665         int i;
2667         if (request == REQ_NONE) {
2668                 doupdate();
2669                 return TRUE;
2670         }
2672         if (request > REQ_NONE) {
2673                 open_run_request(request);
2674                 /* FIXME: When all views can refresh always do this. */
2675                 if (view == VIEW(REQ_VIEW_STATUS) ||
2676                     view == VIEW(REQ_VIEW_MAIN) ||
2677                     view == VIEW(REQ_VIEW_LOG) ||
2678                     view == VIEW(REQ_VIEW_STAGE))
2679                         request = REQ_REFRESH;
2680                 else
2681                         return TRUE;
2682         }
2684         if (view && view->lines) {
2685                 request = view->ops->request(view, request, &view->line[view->lineno]);
2686                 if (request == REQ_NONE)
2687                         return TRUE;
2688         }
2690         switch (request) {
2691         case REQ_MOVE_UP:
2692         case REQ_MOVE_DOWN:
2693         case REQ_MOVE_PAGE_UP:
2694         case REQ_MOVE_PAGE_DOWN:
2695         case REQ_MOVE_FIRST_LINE:
2696         case REQ_MOVE_LAST_LINE:
2697                 move_view(view, request);
2698                 break;
2700         case REQ_SCROLL_LINE_DOWN:
2701         case REQ_SCROLL_LINE_UP:
2702         case REQ_SCROLL_PAGE_DOWN:
2703         case REQ_SCROLL_PAGE_UP:
2704                 scroll_view(view, request);
2705                 break;
2707         case REQ_VIEW_BLAME:
2708                 if (!opt_file[0]) {
2709                         report("No file chosen, press %s to open tree view",
2710                                get_key(REQ_VIEW_TREE));
2711                         break;
2712                 }
2713                 open_view(view, request, OPEN_DEFAULT);
2714                 break;
2716         case REQ_VIEW_BLOB:
2717                 if (!ref_blob[0]) {
2718                         report("No file chosen, press %s to open tree view",
2719                                get_key(REQ_VIEW_TREE));
2720                         break;
2721                 }
2722                 open_view(view, request, OPEN_DEFAULT);
2723                 break;
2725         case REQ_VIEW_PAGER:
2726                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2727                         report("No pager content, press %s to run command from prompt",
2728                                get_key(REQ_PROMPT));
2729                         break;
2730                 }
2731                 open_view(view, request, OPEN_DEFAULT);
2732                 break;
2734         case REQ_VIEW_STAGE:
2735                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2736                         report("No stage content, press %s to open the status view and choose file",
2737                                get_key(REQ_VIEW_STATUS));
2738                         break;
2739                 }
2740                 open_view(view, request, OPEN_DEFAULT);
2741                 break;
2743         case REQ_VIEW_STATUS:
2744                 if (opt_is_inside_work_tree == FALSE) {
2745                         report("The status view requires a working tree");
2746                         break;
2747                 }
2748                 open_view(view, request, OPEN_DEFAULT);
2749                 break;
2751         case REQ_VIEW_MAIN:
2752         case REQ_VIEW_DIFF:
2753         case REQ_VIEW_LOG:
2754         case REQ_VIEW_TREE:
2755         case REQ_VIEW_HELP:
2756                 open_view(view, request, OPEN_DEFAULT);
2757                 break;
2759         case REQ_NEXT:
2760         case REQ_PREVIOUS:
2761                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2763                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2764                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2765                    (view == VIEW(REQ_VIEW_DIFF) &&
2766                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2767                    (view == VIEW(REQ_VIEW_STAGE) &&
2768                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2769                    (view == VIEW(REQ_VIEW_BLOB) &&
2770                      view->parent == VIEW(REQ_VIEW_TREE))) {
2771                         int line;
2773                         view = view->parent;
2774                         line = view->lineno;
2775                         move_view(view, request);
2776                         if (view_is_displayed(view))
2777                                 update_view_title(view);
2778                         if (line != view->lineno)
2779                                 view->ops->request(view, REQ_ENTER,
2780                                                    &view->line[view->lineno]);
2782                 } else {
2783                         move_view(view, request);
2784                 }
2785                 break;
2787         case REQ_VIEW_NEXT:
2788         {
2789                 int nviews = displayed_views();
2790                 int next_view = (current_view + 1) % nviews;
2792                 if (next_view == current_view) {
2793                         report("Only one view is displayed");
2794                         break;
2795                 }
2797                 current_view = next_view;
2798                 /* Blur out the title of the previous view. */
2799                 update_view_title(view);
2800                 report("");
2801                 break;
2802         }
2803         case REQ_REFRESH:
2804                 report("Refreshing is not yet supported for the %s view", view->name);
2805                 break;
2807         case REQ_MAXIMIZE:
2808                 if (displayed_views() == 2)
2809                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2810                 break;
2812         case REQ_TOGGLE_LINENO:
2813                 opt_line_number = !opt_line_number;
2814                 redraw_display();
2815                 break;
2817         case REQ_TOGGLE_DATE:
2818                 opt_date = !opt_date;
2819                 redraw_display();
2820                 break;
2822         case REQ_TOGGLE_AUTHOR:
2823                 opt_author = !opt_author;
2824                 redraw_display();
2825                 break;
2827         case REQ_TOGGLE_REV_GRAPH:
2828                 opt_rev_graph = !opt_rev_graph;
2829                 redraw_display();
2830                 break;
2832         case REQ_TOGGLE_REFS:
2833                 opt_show_refs = !opt_show_refs;
2834                 redraw_display();
2835                 break;
2837         case REQ_SEARCH:
2838         case REQ_SEARCH_BACK:
2839                 search_view(view, request);
2840                 break;
2842         case REQ_FIND_NEXT:
2843         case REQ_FIND_PREV:
2844                 find_next(view, request);
2845                 break;
2847         case REQ_STOP_LOADING:
2848                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2849                         view = &views[i];
2850                         if (view->pipe)
2851                                 report("Stopped loading the %s view", view->name),
2852                         end_update(view, TRUE);
2853                 }
2854                 break;
2856         case REQ_SHOW_VERSION:
2857                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2858                 return TRUE;
2860         case REQ_SCREEN_RESIZE:
2861                 resize_display();
2862                 /* Fall-through */
2863         case REQ_SCREEN_REDRAW:
2864                 redraw_display();
2865                 break;
2867         case REQ_EDIT:
2868                 report("Nothing to edit");
2869                 break;
2871         case REQ_ENTER:
2872                 report("Nothing to enter");
2873                 break;
2875         case REQ_VIEW_CLOSE:
2876                 /* XXX: Mark closed views by letting view->parent point to the
2877                  * view itself. Parents to closed view should never be
2878                  * followed. */
2879                 if (view->parent &&
2880                     view->parent->parent != view->parent) {
2881                         memset(display, 0, sizeof(display));
2882                         current_view = 0;
2883                         display[current_view] = view->parent;
2884                         view->parent = view;
2885                         resize_display();
2886                         redraw_display();
2887                         report("");
2888                         break;
2889                 }
2890                 /* Fall-through */
2891         case REQ_QUIT:
2892                 return FALSE;
2894         default:
2895                 report("Unknown key, press 'h' for help");
2896                 return TRUE;
2897         }
2899         return TRUE;
2903 /*
2904  * Pager backend
2905  */
2907 static bool
2908 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2910         char *text = line->data;
2912         if (opt_line_number && draw_lineno(view, lineno))
2913                 return TRUE;
2915         draw_text(view, line->type, text, TRUE);
2916         return TRUE;
2919 static bool
2920 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2922         char refbuf[SIZEOF_STR];
2923         char *ref = NULL;
2924         FILE *pipe;
2926         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2927                 return TRUE;
2929         pipe = popen(refbuf, "r");
2930         if (!pipe)
2931                 return TRUE;
2933         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2934                 ref = chomp_string(ref);
2935         pclose(pipe);
2937         if (!ref || !*ref)
2938                 return TRUE;
2940         /* This is the only fatal call, since it can "corrupt" the buffer. */
2941         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2942                 return FALSE;
2944         return TRUE;
2947 static void
2948 add_pager_refs(struct view *view, struct line *line)
2950         char buf[SIZEOF_STR];
2951         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2952         struct ref **refs;
2953         size_t bufpos = 0, refpos = 0;
2954         const char *sep = "Refs: ";
2955         bool is_tag = FALSE;
2957         assert(line->type == LINE_COMMIT);
2959         refs = get_refs(commit_id);
2960         if (!refs) {
2961                 if (view == VIEW(REQ_VIEW_DIFF))
2962                         goto try_add_describe_ref;
2963                 return;
2964         }
2966         do {
2967                 struct ref *ref = refs[refpos];
2968                 const char *fmt = ref->tag    ? "%s[%s]" :
2969                                   ref->remote ? "%s<%s>" : "%s%s";
2971                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2972                         return;
2973                 sep = ", ";
2974                 if (ref->tag)
2975                         is_tag = TRUE;
2976         } while (refs[refpos++]->next);
2978         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2979 try_add_describe_ref:
2980                 /* Add <tag>-g<commit_id> "fake" reference. */
2981                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2982                         return;
2983         }
2985         if (bufpos == 0)
2986                 return;
2988         if (!realloc_lines(view, view->line_size + 1))
2989                 return;
2991         add_line_text(view, buf, LINE_PP_REFS);
2994 static bool
2995 pager_read(struct view *view, char *data)
2997         struct line *line;
2999         if (!data)
3000                 return TRUE;
3002         line = add_line_text(view, data, get_line_type(data));
3003         if (!line)
3004                 return FALSE;
3006         if (line->type == LINE_COMMIT &&
3007             (view == VIEW(REQ_VIEW_DIFF) ||
3008              view == VIEW(REQ_VIEW_LOG)))
3009                 add_pager_refs(view, line);
3011         return TRUE;
3014 static enum request
3015 pager_request(struct view *view, enum request request, struct line *line)
3017         int split = 0;
3019         if (request != REQ_ENTER)
3020                 return request;
3022         if (line->type == LINE_COMMIT &&
3023            (view == VIEW(REQ_VIEW_LOG) ||
3024             view == VIEW(REQ_VIEW_PAGER))) {
3025                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3026                 split = 1;
3027         }
3029         /* Always scroll the view even if it was split. That way
3030          * you can use Enter to scroll through the log view and
3031          * split open each commit diff. */
3032         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3034         /* FIXME: A minor workaround. Scrolling the view will call report("")
3035          * but if we are scrolling a non-current view this won't properly
3036          * update the view title. */
3037         if (split)
3038                 update_view_title(view);
3040         return REQ_NONE;
3043 static bool
3044 pager_grep(struct view *view, struct line *line)
3046         regmatch_t pmatch;
3047         char *text = line->data;
3049         if (!*text)
3050                 return FALSE;
3052         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3053                 return FALSE;
3055         return TRUE;
3058 static void
3059 pager_select(struct view *view, struct line *line)
3061         if (line->type == LINE_COMMIT) {
3062                 char *text = (char *)line->data + STRING_SIZE("commit ");
3064                 if (view != VIEW(REQ_VIEW_PAGER))
3065                         string_copy_rev(view->ref, text);
3066                 string_copy_rev(ref_commit, text);
3067         }
3070 static struct view_ops pager_ops = {
3071         "line",
3072         NULL,
3073         pager_read,
3074         pager_draw,
3075         pager_request,
3076         pager_grep,
3077         pager_select,
3078 };
3080 static enum request
3081 log_request(struct view *view, enum request request, struct line *line)
3083         switch (request) {
3084         case REQ_REFRESH:
3085                 load_refs();
3086                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3087                 return REQ_NONE;
3088         default:
3089                 return pager_request(view, request, line);
3090         }
3093 static struct view_ops log_ops = {
3094         "line",
3095         NULL,
3096         pager_read,
3097         pager_draw,
3098         log_request,
3099         pager_grep,
3100         pager_select,
3101 };
3104 /*
3105  * Help backend
3106  */
3108 static bool
3109 help_open(struct view *view)
3111         char buf[BUFSIZ];
3112         int lines = ARRAY_SIZE(req_info) + 2;
3113         int i;
3115         if (view->lines > 0)
3116                 return TRUE;
3118         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3119                 if (!req_info[i].request)
3120                         lines++;
3122         lines += run_requests + 1;
3124         view->line = calloc(lines, sizeof(*view->line));
3125         if (!view->line)
3126                 return FALSE;
3128         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3130         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3131                 const char *key;
3133                 if (req_info[i].request == REQ_NONE)
3134                         continue;
3136                 if (!req_info[i].request) {
3137                         add_line_text(view, "", LINE_DEFAULT);
3138                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3139                         continue;
3140                 }
3142                 key = get_key(req_info[i].request);
3143                 if (!*key)
3144                         key = "(no key defined)";
3146                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3147                         continue;
3149                 add_line_text(view, buf, LINE_DEFAULT);
3150         }
3152         if (run_requests) {
3153                 add_line_text(view, "", LINE_DEFAULT);
3154                 add_line_text(view, "External commands:", LINE_DEFAULT);
3155         }
3157         for (i = 0; i < run_requests; i++) {
3158                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3159                 const char *key;
3161                 if (!req)
3162                         continue;
3164                 key = get_key_name(req->key);
3165                 if (!*key)
3166                         key = "(no key defined)";
3168                 if (!string_format(buf, "    %-10s %-14s `%s`",
3169                                    keymap_table[req->keymap].name,
3170                                    key, req->cmd))
3171                         continue;
3173                 add_line_text(view, buf, LINE_DEFAULT);
3174         }
3176         return TRUE;
3179 static struct view_ops help_ops = {
3180         "line",
3181         help_open,
3182         NULL,
3183         pager_draw,
3184         pager_request,
3185         pager_grep,
3186         pager_select,
3187 };
3190 /*
3191  * Tree backend
3192  */
3194 struct tree_stack_entry {
3195         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3196         unsigned long lineno;           /* Line number to restore */
3197         char *name;                     /* Position of name in opt_path */
3198 };
3200 /* The top of the path stack. */
3201 static struct tree_stack_entry *tree_stack = NULL;
3202 unsigned long tree_lineno = 0;
3204 static void
3205 pop_tree_stack_entry(void)
3207         struct tree_stack_entry *entry = tree_stack;
3209         tree_lineno = entry->lineno;
3210         entry->name[0] = 0;
3211         tree_stack = entry->prev;
3212         free(entry);
3215 static void
3216 push_tree_stack_entry(const char *name, unsigned long lineno)
3218         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3219         size_t pathlen = strlen(opt_path);
3221         if (!entry)
3222                 return;
3224         entry->prev = tree_stack;
3225         entry->name = opt_path + pathlen;
3226         tree_stack = entry;
3228         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3229                 pop_tree_stack_entry();
3230                 return;
3231         }
3233         /* Move the current line to the first tree entry. */
3234         tree_lineno = 1;
3235         entry->lineno = lineno;
3238 /* Parse output from git-ls-tree(1):
3239  *
3240  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3241  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3242  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3243  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3244  */
3246 #define SIZEOF_TREE_ATTR \
3247         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3249 #define TREE_UP_FORMAT "040000 tree %s\t.."
3251 static int
3252 tree_compare_entry(enum line_type type1, const char *name1,
3253                    enum line_type type2, const char *name2)
3255         if (type1 != type2) {
3256                 if (type1 == LINE_TREE_DIR)
3257                         return -1;
3258                 return 1;
3259         }
3261         return strcmp(name1, name2);
3264 static const char *
3265 tree_path(struct line *line)
3267         const char *path = line->data;
3269         return path + SIZEOF_TREE_ATTR;
3272 static bool
3273 tree_read(struct view *view, char *text)
3275         size_t textlen = text ? strlen(text) : 0;
3276         char buf[SIZEOF_STR];
3277         unsigned long pos;
3278         enum line_type type;
3279         bool first_read = view->lines == 0;
3281         if (!text)
3282                 return TRUE;
3283         if (textlen <= SIZEOF_TREE_ATTR)
3284                 return FALSE;
3286         type = text[STRING_SIZE("100644 ")] == 't'
3287              ? LINE_TREE_DIR : LINE_TREE_FILE;
3289         if (first_read) {
3290                 /* Add path info line */
3291                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3292                     !realloc_lines(view, view->line_size + 1) ||
3293                     !add_line_text(view, buf, LINE_DEFAULT))
3294                         return FALSE;
3296                 /* Insert "link" to parent directory. */
3297                 if (*opt_path) {
3298                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3299                             !realloc_lines(view, view->line_size + 1) ||
3300                             !add_line_text(view, buf, LINE_TREE_DIR))
3301                                 return FALSE;
3302                 }
3303         }
3305         /* Strip the path part ... */
3306         if (*opt_path) {
3307                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3308                 size_t striplen = strlen(opt_path);
3309                 char *path = text + SIZEOF_TREE_ATTR;
3311                 if (pathlen > striplen)
3312                         memmove(path, path + striplen,
3313                                 pathlen - striplen + 1);
3314         }
3316         /* Skip "Directory ..." and ".." line. */
3317         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3318                 struct line *line = &view->line[pos];
3319                 const char *path1 = tree_path(line);
3320                 char *path2 = text + SIZEOF_TREE_ATTR;
3321                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3323                 if (cmp <= 0)
3324                         continue;
3326                 text = strdup(text);
3327                 if (!text)
3328                         return FALSE;
3330                 if (view->lines > pos)
3331                         memmove(&view->line[pos + 1], &view->line[pos],
3332                                 (view->lines - pos) * sizeof(*line));
3334                 line = &view->line[pos];
3335                 line->data = text;
3336                 line->type = type;
3337                 view->lines++;
3338                 return TRUE;
3339         }
3341         if (!add_line_text(view, text, type))
3342                 return FALSE;
3344         if (tree_lineno > view->lineno) {
3345                 view->lineno = tree_lineno;
3346                 tree_lineno = 0;
3347         }
3349         return TRUE;
3352 static enum request
3353 tree_request(struct view *view, enum request request, struct line *line)
3355         enum open_flags flags;
3357         if (request == REQ_VIEW_BLAME) {
3358                 const char *filename = tree_path(line);
3360                 if (line->type == LINE_TREE_DIR) {
3361                         report("Cannot show blame for directory %s", opt_path);
3362                         return REQ_NONE;
3363                 }
3365                 string_copy(opt_ref, view->vid);
3366                 string_format(opt_file, "%s%s", opt_path, filename);
3367                 return request;
3368         }
3369         if (request == REQ_TREE_PARENT) {
3370                 if (*opt_path) {
3371                         /* fake 'cd  ..' */
3372                         request = REQ_ENTER;
3373                         line = &view->line[1];
3374                 } else {
3375                         /* quit view if at top of tree */
3376                         return REQ_VIEW_CLOSE;
3377                 }
3378         }
3379         if (request != REQ_ENTER)
3380                 return request;
3382         /* Cleanup the stack if the tree view is at a different tree. */
3383         while (!*opt_path && tree_stack)
3384                 pop_tree_stack_entry();
3386         switch (line->type) {
3387         case LINE_TREE_DIR:
3388                 /* Depending on whether it is a subdir or parent (updir?) link
3389                  * mangle the path buffer. */
3390                 if (line == &view->line[1] && *opt_path) {
3391                         pop_tree_stack_entry();
3393                 } else {
3394                         const char *basename = tree_path(line);
3396                         push_tree_stack_entry(basename, view->lineno);
3397                 }
3399                 /* Trees and subtrees share the same ID, so they are not not
3400                  * unique like blobs. */
3401                 flags = OPEN_RELOAD;
3402                 request = REQ_VIEW_TREE;
3403                 break;
3405         case LINE_TREE_FILE:
3406                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3407                 request = REQ_VIEW_BLOB;
3408                 break;
3410         default:
3411                 return TRUE;
3412         }
3414         open_view(view, request, flags);
3415         if (request == REQ_VIEW_TREE) {
3416                 view->lineno = tree_lineno;
3417         }
3419         return REQ_NONE;
3422 static void
3423 tree_select(struct view *view, struct line *line)
3425         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3427         if (line->type == LINE_TREE_FILE) {
3428                 string_copy_rev(ref_blob, text);
3430         } else if (line->type != LINE_TREE_DIR) {
3431                 return;
3432         }
3434         string_copy_rev(view->ref, text);
3437 static struct view_ops tree_ops = {
3438         "file",
3439         NULL,
3440         tree_read,
3441         pager_draw,
3442         tree_request,
3443         pager_grep,
3444         tree_select,
3445 };
3447 static bool
3448 blob_read(struct view *view, char *line)
3450         if (!line)
3451                 return TRUE;
3452         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3455 static struct view_ops blob_ops = {
3456         "line",
3457         NULL,
3458         blob_read,
3459         pager_draw,
3460         pager_request,
3461         pager_grep,
3462         pager_select,
3463 };
3465 /*
3466  * Blame backend
3467  *
3468  * Loading the blame view is a two phase job:
3469  *
3470  *  1. File content is read either using opt_file from the
3471  *     filesystem or using git-cat-file.
3472  *  2. Then blame information is incrementally added by
3473  *     reading output from git-blame.
3474  */
3476 struct blame_commit {
3477         char id[SIZEOF_REV];            /* SHA1 ID. */
3478         char title[128];                /* First line of the commit message. */
3479         char author[75];                /* Author of the commit. */
3480         struct tm time;                 /* Date from the author ident. */
3481         char filename[128];             /* Name of file. */
3482 };
3484 struct blame {
3485         struct blame_commit *commit;
3486         unsigned int header:1;
3487         char text[1];
3488 };
3490 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3491 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3493 static bool
3494 blame_open(struct view *view)
3496         char path[SIZEOF_STR];
3497         char ref[SIZEOF_STR] = "";
3499         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3500                 return FALSE;
3502         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3503                 return FALSE;
3505         if (*opt_ref) {
3506                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3507                         return FALSE;
3508         } else {
3509                 view->pipe = fopen(opt_file, "r");
3510                 if (!view->pipe &&
3511                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3512                         return FALSE;
3513         }
3515         if (!view->pipe)
3516                 view->pipe = popen(view->cmd, "r");
3517         if (!view->pipe)
3518                 return FALSE;
3520         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3521                 return FALSE;
3523         reset_view(view);
3524         string_format(view->ref, "%s ...", opt_file);
3525         string_copy_rev(view->vid, opt_file);
3526         set_nonblocking_input(TRUE);
3527         view->start_time = time(NULL);
3529         return TRUE;
3532 static struct blame_commit *
3533 get_blame_commit(struct view *view, const char *id)
3535         size_t i;
3537         for (i = 0; i < view->lines; i++) {
3538                 struct blame *blame = view->line[i].data;
3540                 if (!blame->commit)
3541                         continue;
3543                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3544                         return blame->commit;
3545         }
3547         {
3548                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3550                 if (commit)
3551                         string_ncopy(commit->id, id, SIZEOF_REV);
3552                 return commit;
3553         }
3556 static bool
3557 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3559         const char *pos = *posref;
3561         *posref = NULL;
3562         pos = strchr(pos + 1, ' ');
3563         if (!pos || !isdigit(pos[1]))
3564                 return FALSE;
3565         *number = atoi(pos + 1);
3566         if (*number < min || *number > max)
3567                 return FALSE;
3569         *posref = pos;
3570         return TRUE;
3573 static struct blame_commit *
3574 parse_blame_commit(struct view *view, const char *text, int *blamed)
3576         struct blame_commit *commit;
3577         struct blame *blame;
3578         const char *pos = text + SIZEOF_REV - 1;
3579         size_t lineno;
3580         size_t group;
3582         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3583                 return NULL;
3585         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3586             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3587                 return NULL;
3589         commit = get_blame_commit(view, text);
3590         if (!commit)
3591                 return NULL;
3593         *blamed += group;
3594         while (group--) {
3595                 struct line *line = &view->line[lineno + group - 1];
3597                 blame = line->data;
3598                 blame->commit = commit;
3599                 blame->header = !group;
3600                 line->dirty = 1;
3601         }
3603         return commit;
3606 static bool
3607 blame_read_file(struct view *view, const char *line)
3609         if (!line) {
3610                 FILE *pipe = NULL;
3612                 if (view->lines > 0)
3613                         pipe = popen(view->cmd, "r");
3614                 else if (!view->parent)
3615                         die("No blame exist for %s", view->vid);
3616                 view->cmd[0] = 0;
3617                 if (!pipe) {
3618                         report("Failed to load blame data");
3619                         return TRUE;
3620                 }
3622                 fclose(view->pipe);
3623                 view->pipe = pipe;
3624                 return FALSE;
3626         } else {
3627                 size_t linelen = strlen(line);
3628                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3630                 blame->commit = NULL;
3631                 strncpy(blame->text, line, linelen);
3632                 blame->text[linelen] = 0;
3633                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3634         }
3637 static bool
3638 match_blame_header(const char *name, char **line)
3640         size_t namelen = strlen(name);
3641         bool matched = !strncmp(name, *line, namelen);
3643         if (matched)
3644                 *line += namelen;
3646         return matched;
3649 static bool
3650 blame_read(struct view *view, char *line)
3652         static struct blame_commit *commit = NULL;
3653         static int blamed = 0;
3654         static time_t author_time;
3656         if (*view->cmd)
3657                 return blame_read_file(view, line);
3659         if (!line) {
3660                 /* Reset all! */
3661                 commit = NULL;
3662                 blamed = 0;
3663                 string_format(view->ref, "%s", view->vid);
3664                 if (view_is_displayed(view)) {
3665                         update_view_title(view);
3666                         redraw_view_from(view, 0);
3667                 }
3668                 return TRUE;
3669         }
3671         if (!commit) {
3672                 commit = parse_blame_commit(view, line, &blamed);
3673                 string_format(view->ref, "%s %2d%%", view->vid,
3674                               blamed * 100 / view->lines);
3676         } else if (match_blame_header("author ", &line)) {
3677                 string_ncopy(commit->author, line, strlen(line));
3679         } else if (match_blame_header("author-time ", &line)) {
3680                 author_time = (time_t) atol(line);
3682         } else if (match_blame_header("author-tz ", &line)) {
3683                 long tz;
3685                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3686                 tz += ('0' - line[2]) * 60 * 60;
3687                 tz += ('0' - line[3]) * 60;
3688                 tz += ('0' - line[4]) * 60;
3690                 if (line[0] == '-')
3691                         tz = -tz;
3693                 author_time -= tz;
3694                 gmtime_r(&author_time, &commit->time);
3696         } else if (match_blame_header("summary ", &line)) {
3697                 string_ncopy(commit->title, line, strlen(line));
3699         } else if (match_blame_header("filename ", &line)) {
3700                 string_ncopy(commit->filename, line, strlen(line));
3701                 commit = NULL;
3702         }
3704         return TRUE;
3707 static bool
3708 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3710         struct blame *blame = line->data;
3711         struct tm *time = NULL;
3712         const char *id = NULL, *author = NULL;
3714         if (blame->commit && *blame->commit->filename) {
3715                 id = blame->commit->id;
3716                 author = blame->commit->author;
3717                 time = &blame->commit->time;
3718         }
3720         if (opt_date && draw_date(view, time))
3721                 return TRUE;
3723         if (opt_author &&
3724             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3725                 return TRUE;
3727         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3728                 return TRUE;
3730         if (draw_lineno(view, lineno))
3731                 return TRUE;
3733         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3734         return TRUE;
3737 static enum request
3738 blame_request(struct view *view, enum request request, struct line *line)
3740         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3741         struct blame *blame = line->data;
3743         switch (request) {
3744         case REQ_ENTER:
3745                 if (!blame->commit) {
3746                         report("No commit loaded yet");
3747                         break;
3748                 }
3750                 if (!strcmp(blame->commit->id, NULL_ID)) {
3751                         char path[SIZEOF_STR];
3753                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3754                                 break;
3755                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3756                 }
3758                 open_view(view, REQ_VIEW_DIFF, flags);
3759                 break;
3761         default:
3762                 return request;
3763         }
3765         return REQ_NONE;
3768 static bool
3769 blame_grep(struct view *view, struct line *line)
3771         struct blame *blame = line->data;
3772         struct blame_commit *commit = blame->commit;
3773         regmatch_t pmatch;
3775 #define MATCH(text, on)                                                 \
3776         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3778         if (commit) {
3779                 char buf[DATE_COLS + 1];
3781                 if (MATCH(commit->title, 1) ||
3782                     MATCH(commit->author, opt_author) ||
3783                     MATCH(commit->id, opt_date))
3784                         return TRUE;
3786                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3787                     MATCH(buf, 1))
3788                         return TRUE;
3789         }
3791         return MATCH(blame->text, 1);
3793 #undef MATCH
3796 static void
3797 blame_select(struct view *view, struct line *line)
3799         struct blame *blame = line->data;
3800         struct blame_commit *commit = blame->commit;
3802         if (!commit)
3803                 return;
3805         if (!strcmp(commit->id, NULL_ID))
3806                 string_ncopy(ref_commit, "HEAD", 4);
3807         else
3808                 string_copy_rev(ref_commit, commit->id);
3811 static struct view_ops blame_ops = {
3812         "line",
3813         blame_open,
3814         blame_read,
3815         blame_draw,
3816         blame_request,
3817         blame_grep,
3818         blame_select,
3819 };
3821 /*
3822  * Status backend
3823  */
3825 struct status {
3826         char status;
3827         struct {
3828                 mode_t mode;
3829                 char rev[SIZEOF_REV];
3830                 char name[SIZEOF_STR];
3831         } old;
3832         struct {
3833                 mode_t mode;
3834                 char rev[SIZEOF_REV];
3835                 char name[SIZEOF_STR];
3836         } new;
3837 };
3839 static char status_onbranch[SIZEOF_STR];
3840 static struct status stage_status;
3841 static enum line_type stage_line_type;
3842 static size_t stage_chunks;
3843 static int *stage_chunk;
3845 /* This should work even for the "On branch" line. */
3846 static inline bool
3847 status_has_none(struct view *view, struct line *line)
3849         return line < view->line + view->lines && !line[1].data;
3852 /* Get fields from the diff line:
3853  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3854  */
3855 static inline bool
3856 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3858         const char *old_mode = buf +  1;
3859         const char *new_mode = buf +  8;
3860         const char *old_rev  = buf + 15;
3861         const char *new_rev  = buf + 56;
3862         const char *status   = buf + 97;
3864         if (bufsize < 99 ||
3865             old_mode[-1] != ':' ||
3866             new_mode[-1] != ' ' ||
3867             old_rev[-1]  != ' ' ||
3868             new_rev[-1]  != ' ' ||
3869             status[-1]   != ' ')
3870                 return FALSE;
3872         file->status = *status;
3874         string_copy_rev(file->old.rev, old_rev);
3875         string_copy_rev(file->new.rev, new_rev);
3877         file->old.mode = strtoul(old_mode, NULL, 8);
3878         file->new.mode = strtoul(new_mode, NULL, 8);
3880         file->old.name[0] = file->new.name[0] = 0;
3882         return TRUE;
3885 static bool
3886 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3888         struct status *file = NULL;
3889         struct status *unmerged = NULL;
3890         char buf[SIZEOF_STR * 4];
3891         size_t bufsize = 0;
3892         FILE *pipe;
3894         pipe = popen(cmd, "r");
3895         if (!pipe)
3896                 return FALSE;
3898         add_line_data(view, NULL, type);
3900         while (!feof(pipe) && !ferror(pipe)) {
3901                 char *sep;
3902                 size_t readsize;
3904                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3905                 if (!readsize)
3906                         break;
3907                 bufsize += readsize;
3909                 /* Process while we have NUL chars. */
3910                 while ((sep = memchr(buf, 0, bufsize))) {
3911                         size_t sepsize = sep - buf + 1;
3913                         if (!file) {
3914                                 if (!realloc_lines(view, view->line_size + 1))
3915                                         goto error_out;
3917                                 file = calloc(1, sizeof(*file));
3918                                 if (!file)
3919                                         goto error_out;
3921                                 add_line_data(view, file, type);
3922                         }
3924                         /* Parse diff info part. */
3925                         if (status) {
3926                                 file->status = status;
3927                                 if (status == 'A')
3928                                         string_copy(file->old.rev, NULL_ID);
3930                         } else if (!file->status) {
3931                                 if (!status_get_diff(file, buf, sepsize))
3932                                         goto error_out;
3934                                 bufsize -= sepsize;
3935                                 memmove(buf, sep + 1, bufsize);
3937                                 sep = memchr(buf, 0, bufsize);
3938                                 if (!sep)
3939                                         break;
3940                                 sepsize = sep - buf + 1;
3942                                 /* Collapse all 'M'odified entries that
3943                                  * follow a associated 'U'nmerged entry.
3944                                  */
3945                                 if (file->status == 'U') {
3946                                         unmerged = file;
3948                                 } else if (unmerged) {
3949                                         int collapse = !strcmp(buf, unmerged->new.name);
3951                                         unmerged = NULL;
3952                                         if (collapse) {
3953                                                 free(file);
3954                                                 view->lines--;
3955                                                 continue;
3956                                         }
3957                                 }
3958                         }
3960                         /* Grab the old name for rename/copy. */
3961                         if (!*file->old.name &&
3962                             (file->status == 'R' || file->status == 'C')) {
3963                                 sepsize = sep - buf + 1;
3964                                 string_ncopy(file->old.name, buf, sepsize);
3965                                 bufsize -= sepsize;
3966                                 memmove(buf, sep + 1, bufsize);
3968                                 sep = memchr(buf, 0, bufsize);
3969                                 if (!sep)
3970                                         break;
3971                                 sepsize = sep - buf + 1;
3972                         }
3974                         /* git-ls-files just delivers a NUL separated
3975                          * list of file names similar to the second half
3976                          * of the git-diff-* output. */
3977                         string_ncopy(file->new.name, buf, sepsize);
3978                         if (!*file->old.name)
3979                                 string_copy(file->old.name, file->new.name);
3980                         bufsize -= sepsize;
3981                         memmove(buf, sep + 1, bufsize);
3982                         file = NULL;
3983                 }
3984         }
3986         if (ferror(pipe)) {
3987 error_out:
3988                 pclose(pipe);
3989                 return FALSE;
3990         }
3992         if (!view->line[view->lines - 1].data)
3993                 add_line_data(view, NULL, LINE_STAT_NONE);
3995         pclose(pipe);
3996         return TRUE;
3999 /* Don't show unmerged entries in the staged section. */
4000 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4001 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4002 #define STATUS_LIST_OTHER_CMD \
4003         "git ls-files -z --others --exclude-standard"
4004 #define STATUS_LIST_NO_HEAD_CMD \
4005         "git ls-files -z --cached --exclude-standard"
4007 #define STATUS_DIFF_INDEX_SHOW_CMD \
4008         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4010 #define STATUS_DIFF_FILES_SHOW_CMD \
4011         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4013 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4014         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4016 /* First parse staged info using git-diff-index(1), then parse unstaged
4017  * info using git-diff-files(1), and finally untracked files using
4018  * git-ls-files(1). */
4019 static bool
4020 status_open(struct view *view)
4022         unsigned long prev_lineno = view->lineno;
4024         reset_view(view);
4026         if (!realloc_lines(view, view->line_size + 7))
4027                 return FALSE;
4029         add_line_data(view, NULL, LINE_STAT_HEAD);
4030         if (opt_no_head)
4031                 string_copy(status_onbranch, "Initial commit");
4032         else if (!*opt_head)
4033                 string_copy(status_onbranch, "Not currently on any branch");
4034         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4035                 return FALSE;
4037         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4039         if (opt_no_head) {
4040                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4041                         return FALSE;
4042         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4043                 return FALSE;
4044         }
4046         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4047             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4048                 return FALSE;
4050         /* If all went well restore the previous line number to stay in
4051          * the context or select a line with something that can be
4052          * updated. */
4053         if (prev_lineno >= view->lines)
4054                 prev_lineno = view->lines - 1;
4055         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4056                 prev_lineno++;
4057         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4058                 prev_lineno--;
4060         /* If the above fails, always skip the "On branch" line. */
4061         if (prev_lineno < view->lines)
4062                 view->lineno = prev_lineno;
4063         else
4064                 view->lineno = 1;
4066         if (view->lineno < view->offset)
4067                 view->offset = view->lineno;
4068         else if (view->offset + view->height <= view->lineno)
4069                 view->offset = view->lineno - view->height + 1;
4071         return TRUE;
4074 static bool
4075 status_draw(struct view *view, struct line *line, unsigned int lineno)
4077         struct status *status = line->data;
4078         enum line_type type;
4079         const char *text;
4081         if (!status) {
4082                 switch (line->type) {
4083                 case LINE_STAT_STAGED:
4084                         type = LINE_STAT_SECTION;
4085                         text = "Changes to be committed:";
4086                         break;
4088                 case LINE_STAT_UNSTAGED:
4089                         type = LINE_STAT_SECTION;
4090                         text = "Changed but not updated:";
4091                         break;
4093                 case LINE_STAT_UNTRACKED:
4094                         type = LINE_STAT_SECTION;
4095                         text = "Untracked files:";
4096                         break;
4098                 case LINE_STAT_NONE:
4099                         type = LINE_DEFAULT;
4100                         text = "    (no files)";
4101                         break;
4103                 case LINE_STAT_HEAD:
4104                         type = LINE_STAT_HEAD;
4105                         text = status_onbranch;
4106                         break;
4108                 default:
4109                         return FALSE;
4110                 }
4111         } else {
4112                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4114                 buf[0] = status->status;
4115                 if (draw_text(view, line->type, buf, TRUE))
4116                         return TRUE;
4117                 type = LINE_DEFAULT;
4118                 text = status->new.name;
4119         }
4121         draw_text(view, type, text, TRUE);
4122         return TRUE;
4125 static enum request
4126 status_enter(struct view *view, struct line *line)
4128         struct status *status = line->data;
4129         char oldpath[SIZEOF_STR] = "";
4130         char newpath[SIZEOF_STR] = "";
4131         const char *info;
4132         size_t cmdsize = 0;
4133         enum open_flags split;
4135         if (line->type == LINE_STAT_NONE ||
4136             (!status && line[1].type == LINE_STAT_NONE)) {
4137                 report("No file to diff");
4138                 return REQ_NONE;
4139         }
4141         if (status) {
4142                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4143                         return REQ_QUIT;
4144                 /* Diffs for unmerged entries are empty when pasing the
4145                  * new path, so leave it empty. */
4146                 if (status->status != 'U' &&
4147                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4148                         return REQ_QUIT;
4149         }
4151         if (opt_cdup[0] &&
4152             line->type != LINE_STAT_UNTRACKED &&
4153             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4154                 return REQ_QUIT;
4156         switch (line->type) {
4157         case LINE_STAT_STAGED:
4158                 if (opt_no_head) {
4159                         if (!string_format_from(opt_cmd, &cmdsize,
4160                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4161                                                 newpath))
4162                                 return REQ_QUIT;
4163                 } else {
4164                         if (!string_format_from(opt_cmd, &cmdsize,
4165                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4166                                                 oldpath, newpath))
4167                                 return REQ_QUIT;
4168                 }
4170                 if (status)
4171                         info = "Staged changes to %s";
4172                 else
4173                         info = "Staged changes";
4174                 break;
4176         case LINE_STAT_UNSTAGED:
4177                 if (!string_format_from(opt_cmd, &cmdsize,
4178                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4179                         return REQ_QUIT;
4180                 if (status)
4181                         info = "Unstaged changes to %s";
4182                 else
4183                         info = "Unstaged changes";
4184                 break;
4186         case LINE_STAT_UNTRACKED:
4187                 if (opt_pipe)
4188                         return REQ_QUIT;
4190                 if (!status) {
4191                         report("No file to show");
4192                         return REQ_NONE;
4193                 }
4195                 opt_pipe = fopen(status->new.name, "r");
4196                 info = "Untracked file %s";
4197                 break;
4199         case LINE_STAT_HEAD:
4200                 return REQ_NONE;
4202         default:
4203                 die("line type %d not handled in switch", line->type);
4204         }
4206         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4207         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4208         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4209                 if (status) {
4210                         stage_status = *status;
4211                 } else {
4212                         memset(&stage_status, 0, sizeof(stage_status));
4213                 }
4215                 stage_line_type = line->type;
4216                 stage_chunks = 0;
4217                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4218         }
4220         return REQ_NONE;
4223 static bool
4224 status_exists(struct status *status, enum line_type type)
4226         struct view *view = VIEW(REQ_VIEW_STATUS);
4227         struct line *line;
4229         for (line = view->line; line < view->line + view->lines; line++) {
4230                 struct status *pos = line->data;
4232                 if (line->type == type && pos &&
4233                     !strcmp(status->new.name, pos->new.name))
4234                         return TRUE;
4235         }
4237         return FALSE;
4241 static FILE *
4242 status_update_prepare(enum line_type type)
4244         char cmd[SIZEOF_STR];
4245         size_t cmdsize = 0;
4247         if (opt_cdup[0] &&
4248             type != LINE_STAT_UNTRACKED &&
4249             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4250                 return NULL;
4252         switch (type) {
4253         case LINE_STAT_STAGED:
4254                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4255                 break;
4257         case LINE_STAT_UNSTAGED:
4258         case LINE_STAT_UNTRACKED:
4259                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4260                 break;
4262         default:
4263                 die("line type %d not handled in switch", type);
4264         }
4266         return popen(cmd, "w");
4269 static bool
4270 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4272         char buf[SIZEOF_STR];
4273         size_t bufsize = 0;
4274         size_t written = 0;
4276         switch (type) {
4277         case LINE_STAT_STAGED:
4278                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4279                                         status->old.mode,
4280                                         status->old.rev,
4281                                         status->old.name, 0))
4282                         return FALSE;
4283                 break;
4285         case LINE_STAT_UNSTAGED:
4286         case LINE_STAT_UNTRACKED:
4287                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4288                         return FALSE;
4289                 break;
4291         default:
4292                 die("line type %d not handled in switch", type);
4293         }
4295         while (!ferror(pipe) && written < bufsize) {
4296                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4297         }
4299         return written == bufsize;
4302 static bool
4303 status_update_file(struct status *status, enum line_type type)
4305         FILE *pipe = status_update_prepare(type);
4306         bool result;
4308         if (!pipe)
4309                 return FALSE;
4311         result = status_update_write(pipe, status, type);
4312         pclose(pipe);
4313         return result;
4316 static bool
4317 status_update_files(struct view *view, struct line *line)
4319         FILE *pipe = status_update_prepare(line->type);
4320         bool result = TRUE;
4321         struct line *pos = view->line + view->lines;
4322         int files = 0;
4323         int file, done;
4325         if (!pipe)
4326                 return FALSE;
4328         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4329                 files++;
4331         for (file = 0, done = 0; result && file < files; line++, file++) {
4332                 int almost_done = file * 100 / files;
4334                 if (almost_done > done) {
4335                         done = almost_done;
4336                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4337                                       file, files, done);
4338                         update_view_title(view);
4339                 }
4340                 result = status_update_write(pipe, line->data, line->type);
4341         }
4343         pclose(pipe);
4344         return result;
4347 static bool
4348 status_update(struct view *view)
4350         struct line *line = &view->line[view->lineno];
4352         assert(view->lines);
4354         if (!line->data) {
4355                 /* This should work even for the "On branch" line. */
4356                 if (line < view->line + view->lines && !line[1].data) {
4357                         report("Nothing to update");
4358                         return FALSE;
4359                 }
4361                 if (!status_update_files(view, line + 1)) {
4362                         report("Failed to update file status");
4363                         return FALSE;
4364                 }
4366         } else if (!status_update_file(line->data, line->type)) {
4367                 report("Failed to update file status");
4368                 return FALSE;
4369         }
4371         return TRUE;
4374 static bool
4375 status_revert(struct status *status, enum line_type type, bool has_none)
4377         if (!status || type != LINE_STAT_UNSTAGED) {
4378                 if (type == LINE_STAT_STAGED) {
4379                         report("Cannot revert changes to staged files");
4380                 } else if (type == LINE_STAT_UNTRACKED) {
4381                         report("Cannot revert changes to untracked files");
4382                 } else if (has_none) {
4383                         report("Nothing to revert");
4384                 } else {
4385                         report("Cannot revert changes to multiple files");
4386                 }
4387                 return FALSE;
4389         } else {
4390                 char cmd[SIZEOF_STR];
4391                 char file_sq[SIZEOF_STR];
4393                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4394                     !string_format(cmd, "git checkout %s%s", opt_cdup, file_sq))
4395                         return FALSE;
4397                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4398         }
4401 static enum request
4402 status_request(struct view *view, enum request request, struct line *line)
4404         struct status *status = line->data;
4406         switch (request) {
4407         case REQ_STATUS_UPDATE:
4408                 if (!status_update(view))
4409                         return REQ_NONE;
4410                 break;
4412         case REQ_STATUS_REVERT:
4413                 if (!status_revert(status, line->type, status_has_none(view, line)))
4414                         return REQ_NONE;
4415                 break;
4417         case REQ_STATUS_MERGE:
4418                 if (!status || status->status != 'U') {
4419                         report("Merging only possible for files with unmerged status ('U').");
4420                         return REQ_NONE;
4421                 }
4422                 open_mergetool(status->new.name);
4423                 break;
4425         case REQ_EDIT:
4426                 if (!status)
4427                         return request;
4429                 open_editor(status->status != '?', status->new.name);
4430                 break;
4432         case REQ_VIEW_BLAME:
4433                 if (status) {
4434                         string_copy(opt_file, status->new.name);
4435                         opt_ref[0] = 0;
4436                 }
4437                 return request;
4439         case REQ_ENTER:
4440                 /* After returning the status view has been split to
4441                  * show the stage view. No further reloading is
4442                  * necessary. */
4443                 status_enter(view, line);
4444                 return REQ_NONE;
4446         case REQ_REFRESH:
4447                 /* Simply reload the view. */
4448                 break;
4450         default:
4451                 return request;
4452         }
4454         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4456         return REQ_NONE;
4459 static void
4460 status_select(struct view *view, struct line *line)
4462         struct status *status = line->data;
4463         char file[SIZEOF_STR] = "all files";
4464         const char *text;
4465         const char *key;
4467         if (status && !string_format(file, "'%s'", status->new.name))
4468                 return;
4470         if (!status && line[1].type == LINE_STAT_NONE)
4471                 line++;
4473         switch (line->type) {
4474         case LINE_STAT_STAGED:
4475                 text = "Press %s to unstage %s for commit";
4476                 break;
4478         case LINE_STAT_UNSTAGED:
4479                 text = "Press %s to stage %s for commit";
4480                 break;
4482         case LINE_STAT_UNTRACKED:
4483                 text = "Press %s to stage %s for addition";
4484                 break;
4486         case LINE_STAT_HEAD:
4487         case LINE_STAT_NONE:
4488                 text = "Nothing to update";
4489                 break;
4491         default:
4492                 die("line type %d not handled in switch", line->type);
4493         }
4495         if (status && status->status == 'U') {
4496                 text = "Press %s to resolve conflict in %s";
4497                 key = get_key(REQ_STATUS_MERGE);
4499         } else {
4500                 key = get_key(REQ_STATUS_UPDATE);
4501         }
4503         string_format(view->ref, text, key, file);
4506 static bool
4507 status_grep(struct view *view, struct line *line)
4509         struct status *status = line->data;
4510         enum { S_STATUS, S_NAME, S_END } state;
4511         char buf[2] = "?";
4512         regmatch_t pmatch;
4514         if (!status)
4515                 return FALSE;
4517         for (state = S_STATUS; state < S_END; state++) {
4518                 const char *text;
4520                 switch (state) {
4521                 case S_NAME:    text = status->new.name;        break;
4522                 case S_STATUS:
4523                         buf[0] = status->status;
4524                         text = buf;
4525                         break;
4527                 default:
4528                         return FALSE;
4529                 }
4531                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4532                         return TRUE;
4533         }
4535         return FALSE;
4538 static struct view_ops status_ops = {
4539         "file",
4540         status_open,
4541         NULL,
4542         status_draw,
4543         status_request,
4544         status_grep,
4545         status_select,
4546 };
4549 static bool
4550 stage_diff_line(FILE *pipe, struct line *line)
4552         const char *buf = line->data;
4553         size_t bufsize = strlen(buf);
4554         size_t written = 0;
4556         while (!ferror(pipe) && written < bufsize) {
4557                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4558         }
4560         fputc('\n', pipe);
4562         return written == bufsize;
4565 static bool
4566 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4568         while (line < end) {
4569                 if (!stage_diff_line(pipe, line++))
4570                         return FALSE;
4571                 if (line->type == LINE_DIFF_CHUNK ||
4572                     line->type == LINE_DIFF_HEADER)
4573                         break;
4574         }
4576         return TRUE;
4579 static struct line *
4580 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4582         for (; view->line < line; line--)
4583                 if (line->type == type)
4584                         return line;
4586         return NULL;
4589 static bool
4590 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4592         char cmd[SIZEOF_STR];
4593         size_t cmdsize = 0;
4594         struct line *diff_hdr;
4595         FILE *pipe;
4597         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4598         if (!diff_hdr)
4599                 return FALSE;
4601         if (opt_cdup[0] &&
4602             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4603                 return FALSE;
4605         if (!string_format_from(cmd, &cmdsize,
4606                                 "git apply --whitespace=nowarn %s %s - && "
4607                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4608                                 revert ? "" : "--cached",
4609                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4610                 return FALSE;
4612         pipe = popen(cmd, "w");
4613         if (!pipe)
4614                 return FALSE;
4616         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4617             !stage_diff_write(pipe, chunk, view->line + view->lines))
4618                 chunk = NULL;
4620         pclose(pipe);
4622         return chunk ? TRUE : FALSE;
4625 static bool
4626 stage_update(struct view *view, struct line *line)
4628         struct line *chunk = NULL;
4630         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4631                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4633         if (chunk) {
4634                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4635                         report("Failed to apply chunk");
4636                         return FALSE;
4637                 }
4639         } else if (!stage_status.status) {
4640                 view = VIEW(REQ_VIEW_STATUS);
4642                 for (line = view->line; line < view->line + view->lines; line++)
4643                         if (line->type == stage_line_type)
4644                                 break;
4646                 if (!status_update_files(view, line + 1)) {
4647                         report("Failed to update files");
4648                         return FALSE;
4649                 }
4651         } else if (!status_update_file(&stage_status, stage_line_type)) {
4652                 report("Failed to update file");
4653                 return FALSE;
4654         }
4656         return TRUE;
4659 static bool
4660 stage_revert(struct view *view, struct line *line)
4662         struct line *chunk = NULL;
4664         if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4665                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4667         if (chunk) {
4668                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4669                         return FALSE;
4671                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4672                         report("Failed to revert chunk");
4673                         return FALSE;
4674                 }
4675                 return TRUE;
4677         } else {
4678                 return status_revert(stage_status.status ? &stage_status : NULL,
4679                                      stage_line_type, FALSE);
4680         }
4684 static void
4685 stage_next(struct view *view, struct line *line)
4687         int i;
4689         if (!stage_chunks) {
4690                 static size_t alloc = 0;
4691                 int *tmp;
4693                 for (line = view->line; line < view->line + view->lines; line++) {
4694                         if (line->type != LINE_DIFF_CHUNK)
4695                                 continue;
4697                         tmp = realloc_items(stage_chunk, &alloc,
4698                                             stage_chunks, sizeof(*tmp));
4699                         if (!tmp) {
4700                                 report("Allocation failure");
4701                                 return;
4702                         }
4704                         stage_chunk = tmp;
4705                         stage_chunk[stage_chunks++] = line - view->line;
4706                 }
4707         }
4709         for (i = 0; i < stage_chunks; i++) {
4710                 if (stage_chunk[i] > view->lineno) {
4711                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4712                         report("Chunk %d of %d", i + 1, stage_chunks);
4713                         return;
4714                 }
4715         }
4717         report("No next chunk found");
4720 static enum request
4721 stage_request(struct view *view, enum request request, struct line *line)
4723         switch (request) {
4724         case REQ_STATUS_UPDATE:
4725                 if (!stage_update(view, line))
4726                         return REQ_NONE;
4727                 break;
4729         case REQ_STATUS_REVERT:
4730                 if (!stage_revert(view, line))
4731                         return REQ_NONE;
4732                 break;
4734         case REQ_STAGE_NEXT:
4735                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4736                         report("File is untracked; press %s to add",
4737                                get_key(REQ_STATUS_UPDATE));
4738                         return REQ_NONE;
4739                 }
4740                 stage_next(view, line);
4741                 return REQ_NONE;
4743         case REQ_EDIT:
4744                 if (!stage_status.new.name[0])
4745                         return request;
4747                 open_editor(stage_status.status != '?', stage_status.new.name);
4748                 break;
4750         case REQ_REFRESH:
4751                 /* Reload everything ... */
4752                 break;
4754         case REQ_VIEW_BLAME:
4755                 if (stage_status.new.name[0]) {
4756                         string_copy(opt_file, stage_status.new.name);
4757                         opt_ref[0] = 0;
4758                 }
4759                 return request;
4761         case REQ_ENTER:
4762                 return pager_request(view, request, line);
4764         default:
4765                 return request;
4766         }
4768         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4770         /* Check whether the staged entry still exists, and close the
4771          * stage view if it doesn't. */
4772         if (!status_exists(&stage_status, stage_line_type))
4773                 return REQ_VIEW_CLOSE;
4775         if (stage_line_type == LINE_STAT_UNTRACKED)
4776                 opt_pipe = fopen(stage_status.new.name, "r");
4777         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4779         return REQ_NONE;
4782 static struct view_ops stage_ops = {
4783         "line",
4784         NULL,
4785         pager_read,
4786         pager_draw,
4787         stage_request,
4788         pager_grep,
4789         pager_select,
4790 };
4793 /*
4794  * Revision graph
4795  */
4797 struct commit {
4798         char id[SIZEOF_REV];            /* SHA1 ID. */
4799         char title[128];                /* First line of the commit message. */
4800         char author[75];                /* Author of the commit. */
4801         struct tm time;                 /* Date from the author ident. */
4802         struct ref **refs;              /* Repository references. */
4803         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4804         size_t graph_size;              /* The width of the graph array. */
4805         bool has_parents;               /* Rewritten --parents seen. */
4806 };
4808 /* Size of rev graph with no  "padding" columns */
4809 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4811 struct rev_graph {
4812         struct rev_graph *prev, *next, *parents;
4813         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4814         size_t size;
4815         struct commit *commit;
4816         size_t pos;
4817         unsigned int boundary:1;
4818 };
4820 /* Parents of the commit being visualized. */
4821 static struct rev_graph graph_parents[4];
4823 /* The current stack of revisions on the graph. */
4824 static struct rev_graph graph_stacks[4] = {
4825         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4826         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4827         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4828         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4829 };
4831 static inline bool
4832 graph_parent_is_merge(struct rev_graph *graph)
4834         return graph->parents->size > 1;
4837 static inline void
4838 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4840         struct commit *commit = graph->commit;
4842         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4843                 commit->graph[commit->graph_size++] = symbol;
4846 static void
4847 clear_rev_graph(struct rev_graph *graph)
4849         graph->boundary = 0;
4850         graph->size = graph->pos = 0;
4851         graph->commit = NULL;
4852         memset(graph->parents, 0, sizeof(*graph->parents));
4855 static void
4856 done_rev_graph(struct rev_graph *graph)
4858         if (graph_parent_is_merge(graph) &&
4859             graph->pos < graph->size - 1 &&
4860             graph->next->size == graph->size + graph->parents->size - 1) {
4861                 size_t i = graph->pos + graph->parents->size - 1;
4863                 graph->commit->graph_size = i * 2;
4864                 while (i < graph->next->size - 1) {
4865                         append_to_rev_graph(graph, ' ');
4866                         append_to_rev_graph(graph, '\\');
4867                         i++;
4868                 }
4869         }
4871         clear_rev_graph(graph);
4874 static void
4875 push_rev_graph(struct rev_graph *graph, const char *parent)
4877         int i;
4879         /* "Collapse" duplicate parents lines.
4880          *
4881          * FIXME: This needs to also update update the drawn graph but
4882          * for now it just serves as a method for pruning graph lines. */
4883         for (i = 0; i < graph->size; i++)
4884                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4885                         return;
4887         if (graph->size < SIZEOF_REVITEMS) {
4888                 string_copy_rev(graph->rev[graph->size++], parent);
4889         }
4892 static chtype
4893 get_rev_graph_symbol(struct rev_graph *graph)
4895         chtype symbol;
4897         if (graph->boundary)
4898                 symbol = REVGRAPH_BOUND;
4899         else if (graph->parents->size == 0)
4900                 symbol = REVGRAPH_INIT;
4901         else if (graph_parent_is_merge(graph))
4902                 symbol = REVGRAPH_MERGE;
4903         else if (graph->pos >= graph->size)
4904                 symbol = REVGRAPH_BRANCH;
4905         else
4906                 symbol = REVGRAPH_COMMIT;
4908         return symbol;
4911 static void
4912 draw_rev_graph(struct rev_graph *graph)
4914         struct rev_filler {
4915                 chtype separator, line;
4916         };
4917         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4918         static struct rev_filler fillers[] = {
4919                 { ' ',  '|' },
4920                 { '`',  '.' },
4921                 { '\'', ' ' },
4922                 { '/',  ' ' },
4923         };
4924         chtype symbol = get_rev_graph_symbol(graph);
4925         struct rev_filler *filler;
4926         size_t i;
4928         if (opt_line_graphics)
4929                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4931         filler = &fillers[DEFAULT];
4933         for (i = 0; i < graph->pos; i++) {
4934                 append_to_rev_graph(graph, filler->line);
4935                 if (graph_parent_is_merge(graph->prev) &&
4936                     graph->prev->pos == i)
4937                         filler = &fillers[RSHARP];
4939                 append_to_rev_graph(graph, filler->separator);
4940         }
4942         /* Place the symbol for this revision. */
4943         append_to_rev_graph(graph, symbol);
4945         if (graph->prev->size > graph->size)
4946                 filler = &fillers[RDIAG];
4947         else
4948                 filler = &fillers[DEFAULT];
4950         i++;
4952         for (; i < graph->size; i++) {
4953                 append_to_rev_graph(graph, filler->separator);
4954                 append_to_rev_graph(graph, filler->line);
4955                 if (graph_parent_is_merge(graph->prev) &&
4956                     i < graph->prev->pos + graph->parents->size)
4957                         filler = &fillers[RSHARP];
4958                 if (graph->prev->size > graph->size)
4959                         filler = &fillers[LDIAG];
4960         }
4962         if (graph->prev->size > graph->size) {
4963                 append_to_rev_graph(graph, filler->separator);
4964                 if (filler->line != ' ')
4965                         append_to_rev_graph(graph, filler->line);
4966         }
4969 /* Prepare the next rev graph */
4970 static void
4971 prepare_rev_graph(struct rev_graph *graph)
4973         size_t i;
4975         /* First, traverse all lines of revisions up to the active one. */
4976         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4977                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4978                         break;
4980                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4981         }
4983         /* Interleave the new revision parent(s). */
4984         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4985                 push_rev_graph(graph->next, graph->parents->rev[i]);
4987         /* Lastly, put any remaining revisions. */
4988         for (i = graph->pos + 1; i < graph->size; i++)
4989                 push_rev_graph(graph->next, graph->rev[i]);
4992 static void
4993 update_rev_graph(struct rev_graph *graph)
4995         /* If this is the finalizing update ... */
4996         if (graph->commit)
4997                 prepare_rev_graph(graph);
4999         /* Graph visualization needs a one rev look-ahead,
5000          * so the first update doesn't visualize anything. */
5001         if (!graph->prev->commit)
5002                 return;
5004         draw_rev_graph(graph->prev);
5005         done_rev_graph(graph->prev->prev);
5009 /*
5010  * Main view backend
5011  */
5013 static bool
5014 main_draw(struct view *view, struct line *line, unsigned int lineno)
5016         struct commit *commit = line->data;
5018         if (!*commit->author)
5019                 return FALSE;
5021         if (opt_date && draw_date(view, &commit->time))
5022                 return TRUE;
5024         if (opt_author &&
5025             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5026                 return TRUE;
5028         if (opt_rev_graph && commit->graph_size &&
5029             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5030                 return TRUE;
5032         if (opt_show_refs && commit->refs) {
5033                 size_t i = 0;
5035                 do {
5036                         enum line_type type;
5038                         if (commit->refs[i]->head)
5039                                 type = LINE_MAIN_HEAD;
5040                         else if (commit->refs[i]->ltag)
5041                                 type = LINE_MAIN_LOCAL_TAG;
5042                         else if (commit->refs[i]->tag)
5043                                 type = LINE_MAIN_TAG;
5044                         else if (commit->refs[i]->tracked)
5045                                 type = LINE_MAIN_TRACKED;
5046                         else if (commit->refs[i]->remote)
5047                                 type = LINE_MAIN_REMOTE;
5048                         else
5049                                 type = LINE_MAIN_REF;
5051                         if (draw_text(view, type, "[", TRUE) ||
5052                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5053                             draw_text(view, type, "]", TRUE))
5054                                 return TRUE;
5056                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5057                                 return TRUE;
5058                 } while (commit->refs[i++]->next);
5059         }
5061         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5062         return TRUE;
5065 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5066 static bool
5067 main_read(struct view *view, char *line)
5069         static struct rev_graph *graph = graph_stacks;
5070         enum line_type type;
5071         struct commit *commit;
5073         if (!line) {
5074                 int i;
5076                 if (!view->lines && !view->parent)
5077                         die("No revisions match the given arguments.");
5078                 if (view->lines > 0) {
5079                         commit = view->line[view->lines - 1].data;
5080                         if (!*commit->author) {
5081                                 view->lines--;
5082                                 free(commit);
5083                                 graph->commit = NULL;
5084                         }
5085                 }
5086                 update_rev_graph(graph);
5088                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5089                         clear_rev_graph(&graph_stacks[i]);
5090                 return TRUE;
5091         }
5093         type = get_line_type(line);
5094         if (type == LINE_COMMIT) {
5095                 commit = calloc(1, sizeof(struct commit));
5096                 if (!commit)
5097                         return FALSE;
5099                 line += STRING_SIZE("commit ");
5100                 if (*line == '-') {
5101                         graph->boundary = 1;
5102                         line++;
5103                 }
5105                 string_copy_rev(commit->id, line);
5106                 commit->refs = get_refs(commit->id);
5107                 graph->commit = commit;
5108                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5110                 while ((line = strchr(line, ' '))) {
5111                         line++;
5112                         push_rev_graph(graph->parents, line);
5113                         commit->has_parents = TRUE;
5114                 }
5115                 return TRUE;
5116         }
5118         if (!view->lines)
5119                 return TRUE;
5120         commit = view->line[view->lines - 1].data;
5122         switch (type) {
5123         case LINE_PARENT:
5124                 if (commit->has_parents)
5125                         break;
5126                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5127                 break;
5129         case LINE_AUTHOR:
5130         {
5131                 /* Parse author lines where the name may be empty:
5132                  *      author  <email@address.tld> 1138474660 +0100
5133                  */
5134                 char *ident = line + STRING_SIZE("author ");
5135                 char *nameend = strchr(ident, '<');
5136                 char *emailend = strchr(ident, '>');
5138                 if (!nameend || !emailend)
5139                         break;
5141                 update_rev_graph(graph);
5142                 graph = graph->next;
5144                 *nameend = *emailend = 0;
5145                 ident = chomp_string(ident);
5146                 if (!*ident) {
5147                         ident = chomp_string(nameend + 1);
5148                         if (!*ident)
5149                                 ident = "Unknown";
5150                 }
5152                 string_ncopy(commit->author, ident, strlen(ident));
5154                 /* Parse epoch and timezone */
5155                 if (emailend[1] == ' ') {
5156                         char *secs = emailend + 2;
5157                         char *zone = strchr(secs, ' ');
5158                         time_t time = (time_t) atol(secs);
5160                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5161                                 long tz;
5163                                 zone++;
5164                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5165                                 tz += ('0' - zone[2]) * 60 * 60;
5166                                 tz += ('0' - zone[3]) * 60;
5167                                 tz += ('0' - zone[4]) * 60;
5169                                 if (zone[0] == '-')
5170                                         tz = -tz;
5172                                 time -= tz;
5173                         }
5175                         gmtime_r(&time, &commit->time);
5176                 }
5177                 break;
5178         }
5179         default:
5180                 /* Fill in the commit title if it has not already been set. */
5181                 if (commit->title[0])
5182                         break;
5184                 /* Require titles to start with a non-space character at the
5185                  * offset used by git log. */
5186                 if (strncmp(line, "    ", 4))
5187                         break;
5188                 line += 4;
5189                 /* Well, if the title starts with a whitespace character,
5190                  * try to be forgiving.  Otherwise we end up with no title. */
5191                 while (isspace(*line))
5192                         line++;
5193                 if (*line == '\0')
5194                         break;
5195                 /* FIXME: More graceful handling of titles; append "..." to
5196                  * shortened titles, etc. */
5198                 string_ncopy(commit->title, line, strlen(line));
5199         }
5201         return TRUE;
5204 static enum request
5205 main_request(struct view *view, enum request request, struct line *line)
5207         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5209         switch (request) {
5210         case REQ_ENTER:
5211                 open_view(view, REQ_VIEW_DIFF, flags);
5212                 break;
5213         case REQ_REFRESH:
5214                 load_refs();
5215                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5216                 break;
5217         default:
5218                 return request;
5219         }
5221         return REQ_NONE;
5224 static bool
5225 grep_refs(struct ref **refs, regex_t *regex)
5227         regmatch_t pmatch;
5228         size_t i = 0;
5230         if (!refs)
5231                 return FALSE;
5232         do {
5233                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5234                         return TRUE;
5235         } while (refs[i++]->next);
5237         return FALSE;
5240 static bool
5241 main_grep(struct view *view, struct line *line)
5243         struct commit *commit = line->data;
5244         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5245         char buf[DATE_COLS + 1];
5246         regmatch_t pmatch;
5248         for (state = S_TITLE; state < S_END; state++) {
5249                 char *text;
5251                 switch (state) {
5252                 case S_TITLE:   text = commit->title;   break;
5253                 case S_AUTHOR:
5254                         if (!opt_author)
5255                                 continue;
5256                         text = commit->author;
5257                         break;
5258                 case S_DATE:
5259                         if (!opt_date)
5260                                 continue;
5261                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5262                                 continue;
5263                         text = buf;
5264                         break;
5265                 case S_REFS:
5266                         if (!opt_show_refs)
5267                                 continue;
5268                         if (grep_refs(commit->refs, view->regex) == TRUE)
5269                                 return TRUE;
5270                         continue;
5271                 default:
5272                         return FALSE;
5273                 }
5275                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5276                         return TRUE;
5277         }
5279         return FALSE;
5282 static void
5283 main_select(struct view *view, struct line *line)
5285         struct commit *commit = line->data;
5287         string_copy_rev(view->ref, commit->id);
5288         string_copy_rev(ref_commit, view->ref);
5291 static struct view_ops main_ops = {
5292         "commit",
5293         NULL,
5294         main_read,
5295         main_draw,
5296         main_request,
5297         main_grep,
5298         main_select,
5299 };
5302 /*
5303  * Unicode / UTF-8 handling
5304  *
5305  * NOTE: Much of the following code for dealing with unicode is derived from
5306  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5307  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5308  */
5310 /* I've (over)annotated a lot of code snippets because I am not entirely
5311  * confident that the approach taken by this small UTF-8 interface is correct.
5312  * --jonas */
5314 static inline int
5315 unicode_width(unsigned long c)
5317         if (c >= 0x1100 &&
5318            (c <= 0x115f                         /* Hangul Jamo */
5319             || c == 0x2329
5320             || c == 0x232a
5321             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5322                                                 /* CJK ... Yi */
5323             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5324             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5325             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5326             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5327             || (c >= 0xffe0  && c <= 0xffe6)
5328             || (c >= 0x20000 && c <= 0x2fffd)
5329             || (c >= 0x30000 && c <= 0x3fffd)))
5330                 return 2;
5332         if (c == '\t')
5333                 return opt_tab_size;
5335         return 1;
5338 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5339  * Illegal bytes are set one. */
5340 static const unsigned char utf8_bytes[256] = {
5341         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,
5342         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,
5343         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,
5344         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,
5345         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,
5346         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,
5347         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,
5348         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,
5349 };
5351 /* Decode UTF-8 multi-byte representation into a unicode character. */
5352 static inline unsigned long
5353 utf8_to_unicode(const char *string, size_t length)
5355         unsigned long unicode;
5357         switch (length) {
5358         case 1:
5359                 unicode  =   string[0];
5360                 break;
5361         case 2:
5362                 unicode  =  (string[0] & 0x1f) << 6;
5363                 unicode +=  (string[1] & 0x3f);
5364                 break;
5365         case 3:
5366                 unicode  =  (string[0] & 0x0f) << 12;
5367                 unicode += ((string[1] & 0x3f) << 6);
5368                 unicode +=  (string[2] & 0x3f);
5369                 break;
5370         case 4:
5371                 unicode  =  (string[0] & 0x0f) << 18;
5372                 unicode += ((string[1] & 0x3f) << 12);
5373                 unicode += ((string[2] & 0x3f) << 6);
5374                 unicode +=  (string[3] & 0x3f);
5375                 break;
5376         case 5:
5377                 unicode  =  (string[0] & 0x0f) << 24;
5378                 unicode += ((string[1] & 0x3f) << 18);
5379                 unicode += ((string[2] & 0x3f) << 12);
5380                 unicode += ((string[3] & 0x3f) << 6);
5381                 unicode +=  (string[4] & 0x3f);
5382                 break;
5383         case 6:
5384                 unicode  =  (string[0] & 0x01) << 30;
5385                 unicode += ((string[1] & 0x3f) << 24);
5386                 unicode += ((string[2] & 0x3f) << 18);
5387                 unicode += ((string[3] & 0x3f) << 12);
5388                 unicode += ((string[4] & 0x3f) << 6);
5389                 unicode +=  (string[5] & 0x3f);
5390                 break;
5391         default:
5392                 die("Invalid unicode length");
5393         }
5395         /* Invalid characters could return the special 0xfffd value but NUL
5396          * should be just as good. */
5397         return unicode > 0xffff ? 0 : unicode;
5400 /* Calculates how much of string can be shown within the given maximum width
5401  * and sets trimmed parameter to non-zero value if all of string could not be
5402  * shown. If the reserve flag is TRUE, it will reserve at least one
5403  * trailing character, which can be useful when drawing a delimiter.
5404  *
5405  * Returns the number of bytes to output from string to satisfy max_width. */
5406 static size_t
5407 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5409         const char *start = string;
5410         const char *end = strchr(string, '\0');
5411         unsigned char last_bytes = 0;
5412         size_t last_ucwidth = 0;
5414         *width = 0;
5415         *trimmed = 0;
5417         while (string < end) {
5418                 int c = *(unsigned char *) string;
5419                 unsigned char bytes = utf8_bytes[c];
5420                 size_t ucwidth;
5421                 unsigned long unicode;
5423                 if (string + bytes > end)
5424                         break;
5426                 /* Change representation to figure out whether
5427                  * it is a single- or double-width character. */
5429                 unicode = utf8_to_unicode(string, bytes);
5430                 /* FIXME: Graceful handling of invalid unicode character. */
5431                 if (!unicode)
5432                         break;
5434                 ucwidth = unicode_width(unicode);
5435                 *width  += ucwidth;
5436                 if (*width > max_width) {
5437                         *trimmed = 1;
5438                         *width -= ucwidth;
5439                         if (reserve && *width == max_width) {
5440                                 string -= last_bytes;
5441                                 *width -= last_ucwidth;
5442                         }
5443                         break;
5444                 }
5446                 string  += bytes;
5447                 last_bytes = bytes;
5448                 last_ucwidth = ucwidth;
5449         }
5451         return string - start;
5455 /*
5456  * Status management
5457  */
5459 /* Whether or not the curses interface has been initialized. */
5460 static bool cursed = FALSE;
5462 /* The status window is used for polling keystrokes. */
5463 static WINDOW *status_win;
5465 static bool status_empty = TRUE;
5467 /* Update status and title window. */
5468 static void
5469 report(const char *msg, ...)
5471         struct view *view = display[current_view];
5473         if (input_mode)
5474                 return;
5476         if (!view) {
5477                 char buf[SIZEOF_STR];
5478                 va_list args;
5480                 va_start(args, msg);
5481                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5482                         buf[sizeof(buf) - 1] = 0;
5483                         buf[sizeof(buf) - 2] = '.';
5484                         buf[sizeof(buf) - 3] = '.';
5485                         buf[sizeof(buf) - 4] = '.';
5486                 }
5487                 va_end(args);
5488                 die("%s", buf);
5489         }
5491         if (!status_empty || *msg) {
5492                 va_list args;
5494                 va_start(args, msg);
5496                 wmove(status_win, 0, 0);
5497                 if (*msg) {
5498                         vwprintw(status_win, msg, args);
5499                         status_empty = FALSE;
5500                 } else {
5501                         status_empty = TRUE;
5502                 }
5503                 wclrtoeol(status_win);
5504                 wrefresh(status_win);
5506                 va_end(args);
5507         }
5509         update_view_title(view);
5510         update_display_cursor(view);
5513 /* Controls when nodelay should be in effect when polling user input. */
5514 static void
5515 set_nonblocking_input(bool loading)
5517         static unsigned int loading_views;
5519         if ((loading == FALSE && loading_views-- == 1) ||
5520             (loading == TRUE  && loading_views++ == 0))
5521                 nodelay(status_win, loading);
5524 static void
5525 init_display(void)
5527         int x, y;
5529         /* Initialize the curses library */
5530         if (isatty(STDIN_FILENO)) {
5531                 cursed = !!initscr();
5532         } else {
5533                 /* Leave stdin and stdout alone when acting as a pager. */
5534                 FILE *io = fopen("/dev/tty", "r+");
5536                 if (!io)
5537                         die("Failed to open /dev/tty");
5538                 cursed = !!newterm(NULL, io, io);
5539         }
5541         if (!cursed)
5542                 die("Failed to initialize curses");
5544         nonl();         /* Tell curses not to do NL->CR/NL on output */
5545         cbreak();       /* Take input chars one at a time, no wait for \n */
5546         noecho();       /* Don't echo input */
5547         leaveok(stdscr, TRUE);
5549         if (has_colors())
5550                 init_colors();
5552         getmaxyx(stdscr, y, x);
5553         status_win = newwin(1, 0, y - 1, 0);
5554         if (!status_win)
5555                 die("Failed to create status window");
5557         /* Enable keyboard mapping */
5558         keypad(status_win, TRUE);
5559         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5561         TABSIZE = opt_tab_size;
5562         if (opt_line_graphics) {
5563                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5564         }
5567 static bool
5568 prompt_yesno(const char *prompt)
5570         enum { WAIT, STOP, CANCEL  } status = WAIT;
5571         bool answer = FALSE;
5573         while (status == WAIT) {
5574                 struct view *view;
5575                 int i, key;
5577                 input_mode = TRUE;
5579                 foreach_view (view, i)
5580                         update_view(view);
5582                 input_mode = FALSE;
5584                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5585                 wclrtoeol(status_win);
5587                 /* Refresh, accept single keystroke of input */
5588                 key = wgetch(status_win);
5589                 switch (key) {
5590                 case ERR:
5591                         break;
5593                 case 'y':
5594                 case 'Y':
5595                         answer = TRUE;
5596                         status = STOP;
5597                         break;
5599                 case KEY_ESC:
5600                 case KEY_RETURN:
5601                 case KEY_ENTER:
5602                 case KEY_BACKSPACE:
5603                 case 'n':
5604                 case 'N':
5605                 case '\n':
5606                 default:
5607                         answer = FALSE;
5608                         status = CANCEL;
5609                 }
5610         }
5612         /* Clear the status window */
5613         status_empty = FALSE;
5614         report("");
5616         return answer;
5619 static char *
5620 read_prompt(const char *prompt)
5622         enum { READING, STOP, CANCEL } status = READING;
5623         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5624         int pos = 0;
5626         while (status == READING) {
5627                 struct view *view;
5628                 int i, key;
5630                 input_mode = TRUE;
5632                 foreach_view (view, i)
5633                         update_view(view);
5635                 input_mode = FALSE;
5637                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5638                 wclrtoeol(status_win);
5640                 /* Refresh, accept single keystroke of input */
5641                 key = wgetch(status_win);
5642                 switch (key) {
5643                 case KEY_RETURN:
5644                 case KEY_ENTER:
5645                 case '\n':
5646                         status = pos ? STOP : CANCEL;
5647                         break;
5649                 case KEY_BACKSPACE:
5650                         if (pos > 0)
5651                                 pos--;
5652                         else
5653                                 status = CANCEL;
5654                         break;
5656                 case KEY_ESC:
5657                         status = CANCEL;
5658                         break;
5660                 case ERR:
5661                         break;
5663                 default:
5664                         if (pos >= sizeof(buf)) {
5665                                 report("Input string too long");
5666                                 return NULL;
5667                         }
5669                         if (isprint(key))
5670                                 buf[pos++] = (char) key;
5671                 }
5672         }
5674         /* Clear the status window */
5675         status_empty = FALSE;
5676         report("");
5678         if (status == CANCEL)
5679                 return NULL;
5681         buf[pos++] = 0;
5683         return buf;
5686 /*
5687  * Repository references
5688  */
5690 static struct ref *refs = NULL;
5691 static size_t refs_alloc = 0;
5692 static size_t refs_size = 0;
5694 /* Id <-> ref store */
5695 static struct ref ***id_refs = NULL;
5696 static size_t id_refs_alloc = 0;
5697 static size_t id_refs_size = 0;
5699 static int
5700 compare_refs(const void *ref1_, const void *ref2_)
5702         const struct ref *ref1 = *(const struct ref **)ref1_;
5703         const struct ref *ref2 = *(const struct ref **)ref2_;
5705         if (ref1->tag != ref2->tag)
5706                 return ref2->tag - ref1->tag;
5707         if (ref1->ltag != ref2->ltag)
5708                 return ref2->ltag - ref2->ltag;
5709         if (ref1->head != ref2->head)
5710                 return ref2->head - ref1->head;
5711         if (ref1->tracked != ref2->tracked)
5712                 return ref2->tracked - ref1->tracked;
5713         if (ref1->remote != ref2->remote)
5714                 return ref2->remote - ref1->remote;
5715         return strcmp(ref1->name, ref2->name);
5718 static struct ref **
5719 get_refs(const char *id)
5721         struct ref ***tmp_id_refs;
5722         struct ref **ref_list = NULL;
5723         size_t ref_list_alloc = 0;
5724         size_t ref_list_size = 0;
5725         size_t i;
5727         for (i = 0; i < id_refs_size; i++)
5728                 if (!strcmp(id, id_refs[i][0]->id))
5729                         return id_refs[i];
5731         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5732                                     sizeof(*id_refs));
5733         if (!tmp_id_refs)
5734                 return NULL;
5736         id_refs = tmp_id_refs;
5738         for (i = 0; i < refs_size; i++) {
5739                 struct ref **tmp;
5741                 if (strcmp(id, refs[i].id))
5742                         continue;
5744                 tmp = realloc_items(ref_list, &ref_list_alloc,
5745                                     ref_list_size + 1, sizeof(*ref_list));
5746                 if (!tmp) {
5747                         if (ref_list)
5748                                 free(ref_list);
5749                         return NULL;
5750                 }
5752                 ref_list = tmp;
5753                 ref_list[ref_list_size] = &refs[i];
5754                 /* XXX: The properties of the commit chains ensures that we can
5755                  * safely modify the shared ref. The repo references will
5756                  * always be similar for the same id. */
5757                 ref_list[ref_list_size]->next = 1;
5759                 ref_list_size++;
5760         }
5762         if (ref_list) {
5763                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5764                 ref_list[ref_list_size - 1]->next = 0;
5765                 id_refs[id_refs_size++] = ref_list;
5766         }
5768         return ref_list;
5771 static int
5772 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5774         struct ref *ref;
5775         bool tag = FALSE;
5776         bool ltag = FALSE;
5777         bool remote = FALSE;
5778         bool tracked = FALSE;
5779         bool check_replace = FALSE;
5780         bool head = FALSE;
5782         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5783                 if (!strcmp(name + namelen - 3, "^{}")) {
5784                         namelen -= 3;
5785                         name[namelen] = 0;
5786                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5787                                 check_replace = TRUE;
5788                 } else {
5789                         ltag = TRUE;
5790                 }
5792                 tag = TRUE;
5793                 namelen -= STRING_SIZE("refs/tags/");
5794                 name    += STRING_SIZE("refs/tags/");
5796         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5797                 remote = TRUE;
5798                 namelen -= STRING_SIZE("refs/remotes/");
5799                 name    += STRING_SIZE("refs/remotes/");
5800                 tracked  = !strcmp(opt_remote, name);
5802         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5803                 namelen -= STRING_SIZE("refs/heads/");
5804                 name    += STRING_SIZE("refs/heads/");
5805                 head     = !strncmp(opt_head, name, namelen);
5807         } else if (!strcmp(name, "HEAD")) {
5808                 opt_no_head = FALSE;
5809                 return OK;
5810         }
5812         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5813                 /* it's an annotated tag, replace the previous sha1 with the
5814                  * resolved commit id; relies on the fact git-ls-remote lists
5815                  * the commit id of an annotated tag right before the commit id
5816                  * it points to. */
5817                 refs[refs_size - 1].ltag = ltag;
5818                 string_copy_rev(refs[refs_size - 1].id, id);
5820                 return OK;
5821         }
5822         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5823         if (!refs)
5824                 return ERR;
5826         ref = &refs[refs_size++];
5827         ref->name = malloc(namelen + 1);
5828         if (!ref->name)
5829                 return ERR;
5831         strncpy(ref->name, name, namelen);
5832         ref->name[namelen] = 0;
5833         ref->head = head;
5834         ref->tag = tag;
5835         ref->ltag = ltag;
5836         ref->remote = remote;
5837         ref->tracked = tracked;
5838         string_copy_rev(ref->id, id);
5840         return OK;
5843 static int
5844 load_refs(void)
5846         const char *cmd_env = getenv("TIG_LS_REMOTE");
5847         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5849         if (!*opt_git_dir)
5850                 return OK;
5852         while (refs_size > 0)
5853                 free(refs[--refs_size].name);
5854         while (id_refs_size > 0)
5855                 free(id_refs[--id_refs_size]);
5857         return read_properties(popen(cmd, "r"), "\t", read_ref);
5860 static int
5861 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5863         if (!strcmp(name, "i18n.commitencoding"))
5864                 string_ncopy(opt_encoding, value, valuelen);
5866         if (!strcmp(name, "core.editor"))
5867                 string_ncopy(opt_editor, value, valuelen);
5869         /* branch.<head>.remote */
5870         if (*opt_head &&
5871             !strncmp(name, "branch.", 7) &&
5872             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5873             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5874                 string_ncopy(opt_remote, value, valuelen);
5876         if (*opt_head && *opt_remote &&
5877             !strncmp(name, "branch.", 7) &&
5878             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5879             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5880                 size_t from = strlen(opt_remote);
5882                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5883                         value += STRING_SIZE("refs/heads/");
5884                         valuelen -= STRING_SIZE("refs/heads/");
5885                 }
5887                 if (!string_format_from(opt_remote, &from, "/%s", value))
5888                         opt_remote[0] = 0;
5889         }
5891         return OK;
5894 static int
5895 load_git_config(void)
5897         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5898                                "=", read_repo_config_option);
5901 static int
5902 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5904         if (!opt_git_dir[0]) {
5905                 string_ncopy(opt_git_dir, name, namelen);
5907         } else if (opt_is_inside_work_tree == -1) {
5908                 /* This can be 3 different values depending on the
5909                  * version of git being used. If git-rev-parse does not
5910                  * understand --is-inside-work-tree it will simply echo
5911                  * the option else either "true" or "false" is printed.
5912                  * Default to true for the unknown case. */
5913                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5915         } else if (opt_cdup[0] == ' ') {
5916                 string_ncopy(opt_cdup, name, namelen);
5917         } else {
5918                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5919                         namelen -= STRING_SIZE("refs/heads/");
5920                         name    += STRING_SIZE("refs/heads/");
5921                         string_ncopy(opt_head, name, namelen);
5922                 }
5923         }
5925         return OK;
5928 static int
5929 load_repo_info(void)
5931         int result;
5932         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5933                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5935         /* XXX: The line outputted by "--show-cdup" can be empty so
5936          * initialize it to something invalid to make it possible to
5937          * detect whether it has been set or not. */
5938         opt_cdup[0] = ' ';
5940         result = read_properties(pipe, "=", read_repo_info);
5941         if (opt_cdup[0] == ' ')
5942                 opt_cdup[0] = 0;
5944         return result;
5947 static int
5948 read_properties(FILE *pipe, const char *separators,
5949                 int (*read_property)(char *, size_t, char *, size_t))
5951         char buffer[BUFSIZ];
5952         char *name;
5953         int state = OK;
5955         if (!pipe)
5956                 return ERR;
5958         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5959                 char *value;
5960                 size_t namelen;
5961                 size_t valuelen;
5963                 name = chomp_string(name);
5964                 namelen = strcspn(name, separators);
5966                 if (name[namelen]) {
5967                         name[namelen] = 0;
5968                         value = chomp_string(name + namelen + 1);
5969                         valuelen = strlen(value);
5971                 } else {
5972                         value = "";
5973                         valuelen = 0;
5974                 }
5976                 state = read_property(name, namelen, value, valuelen);
5977         }
5979         if (state != ERR && ferror(pipe))
5980                 state = ERR;
5982         pclose(pipe);
5984         return state;
5988 /*
5989  * Main
5990  */
5992 static void __NORETURN
5993 quit(int sig)
5995         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5996         if (cursed)
5997                 endwin();
5998         exit(0);
6001 static void __NORETURN
6002 die(const char *err, ...)
6004         va_list args;
6006         endwin();
6008         va_start(args, err);
6009         fputs("tig: ", stderr);
6010         vfprintf(stderr, err, args);
6011         fputs("\n", stderr);
6012         va_end(args);
6014         exit(1);
6017 static void
6018 warn(const char *msg, ...)
6020         va_list args;
6022         va_start(args, msg);
6023         fputs("tig warning: ", stderr);
6024         vfprintf(stderr, msg, args);
6025         fputs("\n", stderr);
6026         va_end(args);
6029 int
6030 main(int argc, const char *argv[])
6032         struct view *view;
6033         enum request request;
6034         size_t i;
6036         signal(SIGINT, quit);
6038         if (setlocale(LC_ALL, "")) {
6039                 char *codeset = nl_langinfo(CODESET);
6041                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6042         }
6044         if (load_repo_info() == ERR)
6045                 die("Failed to load repo info.");
6047         if (load_options() == ERR)
6048                 die("Failed to load user config.");
6050         if (load_git_config() == ERR)
6051                 die("Failed to load repo config.");
6053         request = parse_options(argc, argv);
6054         if (request == REQ_NONE)
6055                 return 0;
6057         /* Require a git repository unless when running in pager mode. */
6058         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6059                 die("Not a git repository");
6061         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6062                 opt_utf8 = FALSE;
6064         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6065                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6066                 if (opt_iconv == ICONV_NONE)
6067                         die("Failed to initialize character set conversion");
6068         }
6070         if (load_refs() == ERR)
6071                 die("Failed to load refs.");
6073         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
6074                 view->cmd_env = getenv(view->cmd_env);
6076         init_display();
6078         while (view_driver(display[current_view], request)) {
6079                 int key;
6080                 int i;
6082                 foreach_view (view, i)
6083                         update_view(view);
6085                 /* Refresh, accept single keystroke of input */
6086                 key = wgetch(status_win);
6088                 /* wgetch() with nodelay() enabled returns ERR when there's no
6089                  * input. */
6090                 if (key == ERR) {
6091                         request = REQ_NONE;
6092                         continue;
6093                 }
6095                 request = get_keybinding(display[current_view]->keymap, key);
6097                 /* Some low-level request handling. This keeps access to
6098                  * status_win restricted. */
6099                 switch (request) {
6100                 case REQ_PROMPT:
6101                 {
6102                         char *cmd = read_prompt(":");
6104                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6105                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6106                                         request = REQ_VIEW_DIFF;
6107                                 } else {
6108                                         request = REQ_VIEW_PAGER;
6109                                 }
6111                                 /* Always reload^Wrerun commands from the prompt. */
6112                                 open_view(view, request, OPEN_RELOAD);
6113                         }
6115                         request = REQ_NONE;
6116                         break;
6117                 }
6118                 case REQ_SEARCH:
6119                 case REQ_SEARCH_BACK:
6120                 {
6121                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6122                         char *search = read_prompt(prompt);
6124                         if (search)
6125                                 string_ncopy(opt_search, search, strlen(search));
6126                         else
6127                                 request = REQ_NONE;
6128                         break;
6129                 }
6130                 case REQ_SCREEN_RESIZE:
6131                 {
6132                         int height, width;
6134                         getmaxyx(stdscr, height, width);
6136                         /* Resize the status view and let the view driver take
6137                          * care of resizing the displayed views. */
6138                         wresize(status_win, 1, width);
6139                         mvwin(status_win, height - 1, 0);
6140                         wrefresh(status_win);
6141                         break;
6142                 }
6143                 default:
6144                         break;
6145                 }
6146         }
6148         quit(0);
6150         return 0;