Code

ce562d27aa47dcdb0e2f69fd068bb88d308e5041
[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(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("Cursor navigation") \
353         REQ_(MOVE_UP,           "Move cursor one line up"), \
354         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
355         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
356         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
357         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
358         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
359         \
360         REQ_GROUP("Scrolling") \
361         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
362         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
363         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
364         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
365         \
366         REQ_GROUP("Searching") \
367         REQ_(SEARCH,            "Search the view"), \
368         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
369         REQ_(FIND_NEXT,         "Find next search match"), \
370         REQ_(FIND_PREV,         "Find previous search match"), \
371         \
372         REQ_GROUP("Misc") \
373         REQ_(PROMPT,            "Bring up the prompt"), \
374         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
375         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
376         REQ_(SHOW_VERSION,      "Show version information"), \
377         REQ_(STOP_LOADING,      "Stop all loading views"), \
378         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
379         REQ_(TOGGLE_DATE,       "Toggle date display"), \
380         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
381         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
382         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
383         REQ_(STATUS_UPDATE,     "Update file status"), \
384         REQ_(STATUS_CHECKOUT,   "Checkout file"), \
385         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
386         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
387         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
388         REQ_(EDIT,              "Open in editor"), \
389         REQ_(NONE,              "Do nothing")
392 /* User action requests. */
393 enum request {
394 #define REQ_GROUP(help)
395 #define REQ_(req, help) REQ_##req
397         /* Offset all requests to avoid conflicts with ncurses getch values. */
398         REQ_OFFSET = KEY_MAX + 1,
399         REQ_INFO
401 #undef  REQ_GROUP
402 #undef  REQ_
403 };
405 struct request_info {
406         enum request request;
407         char *name;
408         int namelen;
409         char *help;
410 };
412 static struct request_info req_info[] = {
413 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
414 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
415         REQ_INFO
416 #undef  REQ_GROUP
417 #undef  REQ_
418 };
420 static enum request
421 get_request(const char *name)
423         int namelen = strlen(name);
424         int i;
426         for (i = 0; i < ARRAY_SIZE(req_info); i++)
427                 if (req_info[i].namelen == namelen &&
428                     !string_enum_compare(req_info[i].name, name, namelen))
429                         return req_info[i].request;
431         return REQ_NONE;
435 /*
436  * Options
437  */
439 static const char usage[] =
440 "tig " TIG_VERSION " (" __DATE__ ")\n"
441 "\n"
442 "Usage: tig        [options] [revs] [--] [paths]\n"
443 "   or: tig show   [options] [revs] [--] [paths]\n"
444 "   or: tig blame  [rev] path\n"
445 "   or: tig status\n"
446 "   or: tig <      [git command output]\n"
447 "\n"
448 "Options:\n"
449 "  -v, --version   Show version and exit\n"
450 "  -h, --help      Show help message and exit";
452 /* Option and state variables. */
453 static bool opt_date                    = TRUE;
454 static bool opt_author                  = TRUE;
455 static bool opt_line_number             = FALSE;
456 static bool opt_line_graphics           = TRUE;
457 static bool opt_rev_graph               = FALSE;
458 static bool opt_show_refs               = TRUE;
459 static int opt_num_interval             = NUMBER_INTERVAL;
460 static int opt_tab_size                 = TAB_SIZE;
461 static int opt_author_cols              = AUTHOR_COLS-1;
462 static char opt_cmd[SIZEOF_STR]         = "";
463 static char opt_path[SIZEOF_STR]        = "";
464 static char opt_file[SIZEOF_STR]        = "";
465 static char opt_ref[SIZEOF_REF]         = "";
466 static char opt_head[SIZEOF_REF]        = "";
467 static char opt_remote[SIZEOF_REF]      = "";
468 static bool opt_no_head                 = TRUE;
469 static FILE *opt_pipe                   = NULL;
470 static char opt_encoding[20]            = "UTF-8";
471 static bool opt_utf8                    = TRUE;
472 static char opt_codeset[20]             = "UTF-8";
473 static iconv_t opt_iconv                = ICONV_NONE;
474 static char opt_search[SIZEOF_STR]      = "";
475 static char opt_cdup[SIZEOF_STR]        = "";
476 static char opt_git_dir[SIZEOF_STR]     = "";
477 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
478 static char opt_editor[SIZEOF_STR]      = "";
480 static enum request
481 parse_options(int argc, char *argv[])
483         enum request request = REQ_VIEW_MAIN;
484         size_t buf_size;
485         char *subcommand;
486         bool seen_dashdash = FALSE;
487         int i;
489         if (!isatty(STDIN_FILENO)) {
490                 opt_pipe = stdin;
491                 return REQ_VIEW_PAGER;
492         }
494         if (argc <= 1)
495                 return REQ_VIEW_MAIN;
497         subcommand = argv[1];
498         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
499                 if (!strcmp(subcommand, "-S"))
500                         warn("`-S' has been deprecated; use `tig status' instead");
501                 if (argc > 2)
502                         warn("ignoring arguments after `%s'", subcommand);
503                 return REQ_VIEW_STATUS;
505         } else if (!strcmp(subcommand, "blame")) {
506                 if (argc <= 2 || argc > 4)
507                         die("invalid number of options to blame\n\n%s", usage);
509                 i = 2;
510                 if (argc == 4) {
511                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
512                         i++;
513                 }
515                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
516                 return REQ_VIEW_BLAME;
518         } else if (!strcmp(subcommand, "show")) {
519                 request = REQ_VIEW_DIFF;
521         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
522                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
523                 warn("`tig %s' has been deprecated", subcommand);
525         } else {
526                 subcommand = NULL;
527         }
529         if (!subcommand)
530                 /* XXX: This is vulnerable to the user overriding
531                  * options required for the main view parser. */
532                 string_copy(opt_cmd, TIG_MAIN_BASE);
533         else
534                 string_format(opt_cmd, "git %s", subcommand);
536         buf_size = strlen(opt_cmd);
538         for (i = 1 + !!subcommand; i < argc; i++) {
539                 char *opt = argv[i];
541                 if (seen_dashdash || !strcmp(opt, "--")) {
542                         seen_dashdash = TRUE;
544                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
545                         printf("tig version %s\n", TIG_VERSION);
546                         return REQ_NONE;
548                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
549                         printf("%s\n", usage);
550                         return REQ_NONE;
551                 }
553                 opt_cmd[buf_size++] = ' ';
554                 buf_size = sq_quote(opt_cmd, buf_size, opt);
555                 if (buf_size >= sizeof(opt_cmd))
556                         die("command too long");
557         }
559         opt_cmd[buf_size] = 0;
561         return request;
565 /*
566  * Line-oriented content detection.
567  */
569 #define LINE_INFO \
570 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
571 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
572 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
573 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
574 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
575 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
579 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
580 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
581 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
582 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
583 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
584 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
585 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
586 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
587 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
588 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
589 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
591 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
593 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
594 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
595 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
596 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
597 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
599 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
600 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
601 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
603 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
604 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
605 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
606 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
607 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
608 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
609 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
611 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
612 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
613 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
614 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
616 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
617 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
619 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
620 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
621 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
622 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
623 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
625 enum line_type {
626 #define LINE(type, line, fg, bg, attr) \
627         LINE_##type
628         LINE_INFO,
629         LINE_NONE
630 #undef  LINE
631 };
633 struct line_info {
634         const char *name;       /* Option name. */
635         int namelen;            /* Size of option name. */
636         const char *line;       /* The start of line to match. */
637         int linelen;            /* Size of string to match. */
638         int fg, bg, attr;       /* Color and text attributes for the lines. */
639 };
641 static struct line_info line_info[] = {
642 #define LINE(type, line, fg, bg, attr) \
643         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
644         LINE_INFO
645 #undef  LINE
646 };
648 static enum line_type
649 get_line_type(const char *line)
651         int linelen = strlen(line);
652         enum line_type type;
654         for (type = 0; type < ARRAY_SIZE(line_info); type++)
655                 /* Case insensitive search matches Signed-off-by lines better. */
656                 if (linelen >= line_info[type].linelen &&
657                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
658                         return type;
660         return LINE_DEFAULT;
663 static inline int
664 get_line_attr(enum line_type type)
666         assert(type < ARRAY_SIZE(line_info));
667         return COLOR_PAIR(type) | line_info[type].attr;
670 static struct line_info *
671 get_line_info(const char *name)
673         size_t namelen = strlen(name);
674         enum line_type type;
676         for (type = 0; type < ARRAY_SIZE(line_info); type++)
677                 if (namelen == line_info[type].namelen &&
678                     !string_enum_compare(line_info[type].name, name, namelen))
679                         return &line_info[type];
681         return NULL;
684 static void
685 init_colors(void)
687         int default_bg = line_info[LINE_DEFAULT].bg;
688         int default_fg = line_info[LINE_DEFAULT].fg;
689         enum line_type type;
691         start_color();
693         if (assume_default_colors(default_fg, default_bg) == ERR) {
694                 default_bg = COLOR_BLACK;
695                 default_fg = COLOR_WHITE;
696         }
698         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
699                 struct line_info *info = &line_info[type];
700                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
701                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
703                 init_pair(type, fg, bg);
704         }
707 struct line {
708         enum line_type type;
710         /* State flags */
711         unsigned int selected:1;
712         unsigned int dirty:1;
714         void *data;             /* User data */
715 };
718 /*
719  * Keys
720  */
722 struct keybinding {
723         int alias;
724         enum request request;
725         struct keybinding *next;
726 };
728 static struct keybinding default_keybindings[] = {
729         /* View switching */
730         { 'm',          REQ_VIEW_MAIN },
731         { 'd',          REQ_VIEW_DIFF },
732         { 'l',          REQ_VIEW_LOG },
733         { 't',          REQ_VIEW_TREE },
734         { 'f',          REQ_VIEW_BLOB },
735         { 'B',          REQ_VIEW_BLAME },
736         { 'p',          REQ_VIEW_PAGER },
737         { 'h',          REQ_VIEW_HELP },
738         { 'S',          REQ_VIEW_STATUS },
739         { 'c',          REQ_VIEW_STAGE },
741         /* View manipulation */
742         { 'q',          REQ_VIEW_CLOSE },
743         { KEY_TAB,      REQ_VIEW_NEXT },
744         { KEY_RETURN,   REQ_ENTER },
745         { KEY_UP,       REQ_PREVIOUS },
746         { KEY_DOWN,     REQ_NEXT },
747         { 'R',          REQ_REFRESH },
748         { KEY_F(5),     REQ_REFRESH },
749         { 'O',          REQ_MAXIMIZE },
751         /* Cursor navigation */
752         { 'k',          REQ_MOVE_UP },
753         { 'j',          REQ_MOVE_DOWN },
754         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
755         { KEY_END,      REQ_MOVE_LAST_LINE },
756         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
757         { ' ',          REQ_MOVE_PAGE_DOWN },
758         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
759         { 'b',          REQ_MOVE_PAGE_UP },
760         { '-',          REQ_MOVE_PAGE_UP },
762         /* Scrolling */
763         { KEY_IC,       REQ_SCROLL_LINE_UP },
764         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
765         { 'w',          REQ_SCROLL_PAGE_UP },
766         { 's',          REQ_SCROLL_PAGE_DOWN },
768         /* Searching */
769         { '/',          REQ_SEARCH },
770         { '?',          REQ_SEARCH_BACK },
771         { 'n',          REQ_FIND_NEXT },
772         { 'N',          REQ_FIND_PREV },
774         /* Misc */
775         { 'Q',          REQ_QUIT },
776         { 'z',          REQ_STOP_LOADING },
777         { 'v',          REQ_SHOW_VERSION },
778         { 'r',          REQ_SCREEN_REDRAW },
779         { '.',          REQ_TOGGLE_LINENO },
780         { 'D',          REQ_TOGGLE_DATE },
781         { 'A',          REQ_TOGGLE_AUTHOR },
782         { 'g',          REQ_TOGGLE_REV_GRAPH },
783         { 'F',          REQ_TOGGLE_REFS },
784         { ':',          REQ_PROMPT },
785         { 'u',          REQ_STATUS_UPDATE },
786         { '!',          REQ_STATUS_CHECKOUT },
787         { 'M',          REQ_STATUS_MERGE },
788         { '@',          REQ_STAGE_NEXT },
789         { ',',          REQ_TREE_PARENT },
790         { 'e',          REQ_EDIT },
792         /* Using the ncurses SIGWINCH handler. */
793         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
794 };
796 #define KEYMAP_INFO \
797         KEYMAP_(GENERIC), \
798         KEYMAP_(MAIN), \
799         KEYMAP_(DIFF), \
800         KEYMAP_(LOG), \
801         KEYMAP_(TREE), \
802         KEYMAP_(BLOB), \
803         KEYMAP_(BLAME), \
804         KEYMAP_(PAGER), \
805         KEYMAP_(HELP), \
806         KEYMAP_(STATUS), \
807         KEYMAP_(STAGE)
809 enum keymap {
810 #define KEYMAP_(name) KEYMAP_##name
811         KEYMAP_INFO
812 #undef  KEYMAP_
813 };
815 static struct int_map keymap_table[] = {
816 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
817         KEYMAP_INFO
818 #undef  KEYMAP_
819 };
821 #define set_keymap(map, name) \
822         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
824 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
826 static void
827 add_keybinding(enum keymap keymap, enum request request, int key)
829         struct keybinding *keybinding;
831         keybinding = calloc(1, sizeof(*keybinding));
832         if (!keybinding)
833                 die("Failed to allocate keybinding");
835         keybinding->alias = key;
836         keybinding->request = request;
837         keybinding->next = keybindings[keymap];
838         keybindings[keymap] = keybinding;
841 /* Looks for a key binding first in the given map, then in the generic map, and
842  * lastly in the default keybindings. */
843 static enum request
844 get_keybinding(enum keymap keymap, int key)
846         struct keybinding *kbd;
847         int i;
849         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
850                 if (kbd->alias == key)
851                         return kbd->request;
853         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
854                 if (kbd->alias == key)
855                         return kbd->request;
857         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
858                 if (default_keybindings[i].alias == key)
859                         return default_keybindings[i].request;
861         return (enum request) key;
865 struct key {
866         char *name;
867         int value;
868 };
870 static struct key key_table[] = {
871         { "Enter",      KEY_RETURN },
872         { "Space",      ' ' },
873         { "Backspace",  KEY_BACKSPACE },
874         { "Tab",        KEY_TAB },
875         { "Escape",     KEY_ESC },
876         { "Left",       KEY_LEFT },
877         { "Right",      KEY_RIGHT },
878         { "Up",         KEY_UP },
879         { "Down",       KEY_DOWN },
880         { "Insert",     KEY_IC },
881         { "Delete",     KEY_DC },
882         { "Hash",       '#' },
883         { "Home",       KEY_HOME },
884         { "End",        KEY_END },
885         { "PageUp",     KEY_PPAGE },
886         { "PageDown",   KEY_NPAGE },
887         { "F1",         KEY_F(1) },
888         { "F2",         KEY_F(2) },
889         { "F3",         KEY_F(3) },
890         { "F4",         KEY_F(4) },
891         { "F5",         KEY_F(5) },
892         { "F6",         KEY_F(6) },
893         { "F7",         KEY_F(7) },
894         { "F8",         KEY_F(8) },
895         { "F9",         KEY_F(9) },
896         { "F10",        KEY_F(10) },
897         { "F11",        KEY_F(11) },
898         { "F12",        KEY_F(12) },
899 };
901 static int
902 get_key_value(const char *name)
904         int i;
906         for (i = 0; i < ARRAY_SIZE(key_table); i++)
907                 if (!strcasecmp(key_table[i].name, name))
908                         return key_table[i].value;
910         if (strlen(name) == 1 && isprint(*name))
911                 return (int) *name;
913         return ERR;
916 static char *
917 get_key_name(int key_value)
919         static char key_char[] = "'X'";
920         char *seq = NULL;
921         int key;
923         for (key = 0; key < ARRAY_SIZE(key_table); key++)
924                 if (key_table[key].value == key_value)
925                         seq = key_table[key].name;
927         if (seq == NULL &&
928             key_value < 127 &&
929             isprint(key_value)) {
930                 key_char[1] = (char) key_value;
931                 seq = key_char;
932         }
934         return seq ? seq : "'?'";
937 static char *
938 get_key(enum request request)
940         static char buf[BUFSIZ];
941         size_t pos = 0;
942         char *sep = "";
943         int i;
945         buf[pos] = 0;
947         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
948                 struct keybinding *keybinding = &default_keybindings[i];
950                 if (keybinding->request != request)
951                         continue;
953                 if (!string_format_from(buf, &pos, "%s%s", sep,
954                                         get_key_name(keybinding->alias)))
955                         return "Too many keybindings!";
956                 sep = ", ";
957         }
959         return buf;
962 struct run_request {
963         enum keymap keymap;
964         int key;
965         char cmd[SIZEOF_STR];
966 };
968 static struct run_request *run_request;
969 static size_t run_requests;
971 static enum request
972 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
974         struct run_request *req;
975         char cmd[SIZEOF_STR];
976         size_t bufpos;
978         for (bufpos = 0; argc > 0; argc--, argv++)
979                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
980                         return REQ_NONE;
982         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
983         if (!req)
984                 return REQ_NONE;
986         run_request = req;
987         req = &run_request[run_requests++];
988         string_copy(req->cmd, cmd);
989         req->keymap = keymap;
990         req->key = key;
992         return REQ_NONE + run_requests;
995 static struct run_request *
996 get_run_request(enum request request)
998         if (request <= REQ_NONE)
999                 return NULL;
1000         return &run_request[request - REQ_NONE - 1];
1003 static void
1004 add_builtin_run_requests(void)
1006         struct {
1007                 enum keymap keymap;
1008                 int key;
1009                 const char *argv[1];
1010         } reqs[] = {
1011                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1012                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1013         };
1014         int i;
1016         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1017                 enum request req;
1019                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1020                 if (req != REQ_NONE)
1021                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1022         }
1025 /*
1026  * User config file handling.
1027  */
1029 static struct int_map color_map[] = {
1030 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1031         COLOR_MAP(DEFAULT),
1032         COLOR_MAP(BLACK),
1033         COLOR_MAP(BLUE),
1034         COLOR_MAP(CYAN),
1035         COLOR_MAP(GREEN),
1036         COLOR_MAP(MAGENTA),
1037         COLOR_MAP(RED),
1038         COLOR_MAP(WHITE),
1039         COLOR_MAP(YELLOW),
1040 };
1042 #define set_color(color, name) \
1043         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1045 static struct int_map attr_map[] = {
1046 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1047         ATTR_MAP(NORMAL),
1048         ATTR_MAP(BLINK),
1049         ATTR_MAP(BOLD),
1050         ATTR_MAP(DIM),
1051         ATTR_MAP(REVERSE),
1052         ATTR_MAP(STANDOUT),
1053         ATTR_MAP(UNDERLINE),
1054 };
1056 #define set_attribute(attr, name) \
1057         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1059 static int   config_lineno;
1060 static bool  config_errors;
1061 static char *config_msg;
1063 /* Wants: object fgcolor bgcolor [attr] */
1064 static int
1065 option_color_command(int argc, const char *argv[])
1067         struct line_info *info;
1069         if (argc != 3 && argc != 4) {
1070                 config_msg = "Wrong number of arguments given to color command";
1071                 return ERR;
1072         }
1074         info = get_line_info(argv[0]);
1075         if (!info) {
1076                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1077                         info = get_line_info("delimiter");
1079                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1080                         info = get_line_info("date");
1082                 } else {
1083                         config_msg = "Unknown color name";
1084                         return ERR;
1085                 }
1086         }
1088         if (set_color(&info->fg, argv[1]) == ERR ||
1089             set_color(&info->bg, argv[2]) == ERR) {
1090                 config_msg = "Unknown color";
1091                 return ERR;
1092         }
1094         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1095                 config_msg = "Unknown attribute";
1096                 return ERR;
1097         }
1099         return OK;
1102 static bool parse_bool(const char *s)
1104         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1105                 !strcmp(s, "yes")) ? TRUE : FALSE;
1108 static int
1109 parse_int(const char *s, int default_value, int min, int max)
1111         int value = atoi(s);
1113         return (value < min || value > max) ? default_value : value;
1116 /* Wants: name = value */
1117 static int
1118 option_set_command(int argc, const char *argv[])
1120         if (argc != 3) {
1121                 config_msg = "Wrong number of arguments given to set command";
1122                 return ERR;
1123         }
1125         if (strcmp(argv[1], "=")) {
1126                 config_msg = "No value assigned";
1127                 return ERR;
1128         }
1130         if (!strcmp(argv[0], "show-author")) {
1131                 opt_author = parse_bool(argv[2]);
1132                 return OK;
1133         }
1135         if (!strcmp(argv[0], "show-date")) {
1136                 opt_date = parse_bool(argv[2]);
1137                 return OK;
1138         }
1140         if (!strcmp(argv[0], "show-rev-graph")) {
1141                 opt_rev_graph = parse_bool(argv[2]);
1142                 return OK;
1143         }
1145         if (!strcmp(argv[0], "show-refs")) {
1146                 opt_show_refs = parse_bool(argv[2]);
1147                 return OK;
1148         }
1150         if (!strcmp(argv[0], "show-line-numbers")) {
1151                 opt_line_number = parse_bool(argv[2]);
1152                 return OK;
1153         }
1155         if (!strcmp(argv[0], "line-graphics")) {
1156                 opt_line_graphics = parse_bool(argv[2]);
1157                 return OK;
1158         }
1160         if (!strcmp(argv[0], "line-number-interval")) {
1161                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1162                 return OK;
1163         }
1165         if (!strcmp(argv[0], "author-width")) {
1166                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1167                 return OK;
1168         }
1170         if (!strcmp(argv[0], "tab-size")) {
1171                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1172                 return OK;
1173         }
1175         if (!strcmp(argv[0], "commit-encoding")) {
1176                 const char *arg = argv[2];
1177                 int arglen = strlen(arg);
1179                 switch (arg[0]) {
1180                 case '"':
1181                 case '\'':
1182                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1183                                 config_msg = "Unmatched quotation";
1184                                 return ERR;
1185                         }
1186                         arg += 1; arglen -= 2;
1187                 default:
1188                         string_ncopy(opt_encoding, arg, strlen(arg));
1189                         return OK;
1190                 }
1191         }
1193         config_msg = "Unknown variable name";
1194         return ERR;
1197 /* Wants: mode request key */
1198 static int
1199 option_bind_command(int argc, const char *argv[])
1201         enum request request;
1202         int keymap;
1203         int key;
1205         if (argc < 3) {
1206                 config_msg = "Wrong number of arguments given to bind command";
1207                 return ERR;
1208         }
1210         if (set_keymap(&keymap, argv[0]) == ERR) {
1211                 config_msg = "Unknown key map";
1212                 return ERR;
1213         }
1215         key = get_key_value(argv[1]);
1216         if (key == ERR) {
1217                 config_msg = "Unknown key";
1218                 return ERR;
1219         }
1221         request = get_request(argv[2]);
1222         if (request == REQ_NONE) {
1223                 const char *obsolete[] = { "cherry-pick" };
1224                 size_t namelen = strlen(argv[2]);
1225                 int i;
1227                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1228                         if (namelen == strlen(obsolete[i]) &&
1229                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1230                                 config_msg = "Obsolete request name";
1231                                 return ERR;
1232                         }
1233                 }
1234         }
1235         if (request == REQ_NONE && *argv[2]++ == '!')
1236                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1237         if (request == REQ_NONE) {
1238                 config_msg = "Unknown request name";
1239                 return ERR;
1240         }
1242         add_keybinding(keymap, request, key);
1244         return OK;
1247 static int
1248 set_option(char *opt, char *value)
1250         const char *argv[SIZEOF_ARG];
1251         int valuelen;
1252         int argc = 0;
1254         /* Tokenize */
1255         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1256                 argv[argc++] = value;
1257                 value += valuelen;
1259                 /* Nothing more to tokenize or last available token. */
1260                 if (!*value || argc >= ARRAY_SIZE(argv))
1261                         break;
1263                 *value++ = 0;
1264                 while (isspace(*value))
1265                         value++;
1266         }
1268         if (!strcmp(opt, "color"))
1269                 return option_color_command(argc, argv);
1271         if (!strcmp(opt, "set"))
1272                 return option_set_command(argc, argv);
1274         if (!strcmp(opt, "bind"))
1275                 return option_bind_command(argc, argv);
1277         config_msg = "Unknown option command";
1278         return ERR;
1281 static int
1282 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1284         int status = OK;
1286         config_lineno++;
1287         config_msg = "Internal error";
1289         /* Check for comment markers, since read_properties() will
1290          * only ensure opt and value are split at first " \t". */
1291         optlen = strcspn(opt, "#");
1292         if (optlen == 0)
1293                 return OK;
1295         if (opt[optlen] != 0) {
1296                 config_msg = "No option value";
1297                 status = ERR;
1299         }  else {
1300                 /* Look for comment endings in the value. */
1301                 size_t len = strcspn(value, "#");
1303                 if (len < valuelen) {
1304                         valuelen = len;
1305                         value[valuelen] = 0;
1306                 }
1308                 status = set_option(opt, value);
1309         }
1311         if (status == ERR) {
1312                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1313                         config_lineno, (int) optlen, opt, config_msg);
1314                 config_errors = TRUE;
1315         }
1317         /* Always keep going if errors are encountered. */
1318         return OK;
1321 static void
1322 load_option_file(const char *path)
1324         FILE *file;
1326         /* It's ok that the file doesn't exist. */
1327         file = fopen(path, "r");
1328         if (!file)
1329                 return;
1331         config_lineno = 0;
1332         config_errors = FALSE;
1334         if (read_properties(file, " \t", read_option) == ERR ||
1335             config_errors == TRUE)
1336                 fprintf(stderr, "Errors while loading %s.\n", path);
1339 static int
1340 load_options(void)
1342         char *home = getenv("HOME");
1343         char *tigrc_user = getenv("TIGRC_USER");
1344         char *tigrc_system = getenv("TIGRC_SYSTEM");
1345         char buf[SIZEOF_STR];
1347         add_builtin_run_requests();
1349         if (!tigrc_system) {
1350                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1351                         return ERR;
1352                 tigrc_system = buf;
1353         }
1354         load_option_file(tigrc_system);
1356         if (!tigrc_user) {
1357                 if (!home || !string_format(buf, "%s/.tigrc", home))
1358                         return ERR;
1359                 tigrc_user = buf;
1360         }
1361         load_option_file(tigrc_user);
1363         return OK;
1367 /*
1368  * The viewer
1369  */
1371 struct view;
1372 struct view_ops;
1374 /* The display array of active views and the index of the current view. */
1375 static struct view *display[2];
1376 static unsigned int current_view;
1378 /* Reading from the prompt? */
1379 static bool input_mode = FALSE;
1381 #define foreach_displayed_view(view, i) \
1382         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1384 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1386 /* Current head and commit ID */
1387 static char ref_blob[SIZEOF_REF]        = "";
1388 static char ref_commit[SIZEOF_REF]      = "HEAD";
1389 static char ref_head[SIZEOF_REF]        = "HEAD";
1391 struct view {
1392         const char *name;       /* View name */
1393         const char *cmd_fmt;    /* Default command line format */
1394         const char *cmd_env;    /* Command line set via environment */
1395         const char *id;         /* Points to either of ref_{head,commit,blob} */
1397         struct view_ops *ops;   /* View operations */
1399         enum keymap keymap;     /* What keymap does this view have */
1400         bool git_dir;           /* Whether the view requires a git directory. */
1402         char cmd[SIZEOF_STR];   /* Command buffer */
1403         char ref[SIZEOF_REF];   /* Hovered commit reference */
1404         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1406         int height, width;      /* The width and height of the main window */
1407         WINDOW *win;            /* The main window */
1408         WINDOW *title;          /* The title window living below the main window */
1410         /* Navigation */
1411         unsigned long offset;   /* Offset of the window top */
1412         unsigned long lineno;   /* Current line number */
1414         /* Searching */
1415         char grep[SIZEOF_STR];  /* Search string */
1416         regex_t *regex;         /* Pre-compiled regex */
1418         /* If non-NULL, points to the view that opened this view. If this view
1419          * is closed tig will switch back to the parent view. */
1420         struct view *parent;
1422         /* Buffering */
1423         size_t lines;           /* Total number of lines */
1424         struct line *line;      /* Line index */
1425         size_t line_alloc;      /* Total number of allocated lines */
1426         size_t line_size;       /* Total number of used lines */
1427         unsigned int digits;    /* Number of digits in the lines member. */
1429         /* Drawing */
1430         struct line *curline;   /* Line currently being drawn. */
1431         enum line_type curtype; /* Attribute currently used for drawing. */
1432         unsigned long col;      /* Column when drawing. */
1434         /* Loading */
1435         FILE *pipe;
1436         time_t start_time;
1437 };
1439 struct view_ops {
1440         /* What type of content being displayed. Used in the title bar. */
1441         const char *type;
1442         /* Open and reads in all view content. */
1443         bool (*open)(struct view *view);
1444         /* Read one line; updates view->line. */
1445         bool (*read)(struct view *view, char *data);
1446         /* Draw one line; @lineno must be < view->height. */
1447         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1448         /* Depending on view handle a special requests. */
1449         enum request (*request)(struct view *view, enum request request, struct line *line);
1450         /* Search for regex in a line. */
1451         bool (*grep)(struct view *view, struct line *line);
1452         /* Select line */
1453         void (*select)(struct view *view, struct line *line);
1454 };
1456 static struct view_ops blame_ops;
1457 static struct view_ops blob_ops;
1458 static struct view_ops help_ops;
1459 static struct view_ops log_ops;
1460 static struct view_ops main_ops;
1461 static struct view_ops pager_ops;
1462 static struct view_ops stage_ops;
1463 static struct view_ops status_ops;
1464 static struct view_ops tree_ops;
1466 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1467         { name, cmd, #env, ref, ops, map, git }
1469 #define VIEW_(id, name, ops, git, ref) \
1470         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1473 static struct view views[] = {
1474         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1475         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1476         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1477         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1478         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1479         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1480         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1481         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1482         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1483         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1484 };
1486 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1487 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1489 #define foreach_view(view, i) \
1490         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1492 #define view_is_displayed(view) \
1493         (view == display[0] || view == display[1])
1496 enum line_graphic {
1497         LINE_GRAPHIC_VLINE
1498 };
1500 static int line_graphics[] = {
1501         /* LINE_GRAPHIC_VLINE: */ '|'
1502 };
1504 static inline void
1505 set_view_attr(struct view *view, enum line_type type)
1507         if (!view->curline->selected && view->curtype != type) {
1508                 wattrset(view->win, get_line_attr(type));
1509                 wchgat(view->win, -1, 0, type, NULL);
1510                 view->curtype = type;
1511         }
1514 static int
1515 draw_chars(struct view *view, enum line_type type, const char *string,
1516            int max_len, bool use_tilde)
1518         int len = 0;
1519         int col = 0;
1520         int trimmed = FALSE;
1522         if (max_len <= 0)
1523                 return 0;
1525         if (opt_utf8) {
1526                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1527         } else {
1528                 col = len = strlen(string);
1529                 if (len > max_len) {
1530                         if (use_tilde) {
1531                                 max_len -= 1;
1532                         }
1533                         col = len = max_len;
1534                         trimmed = TRUE;
1535                 }
1536         }
1538         set_view_attr(view, type);
1539         waddnstr(view->win, string, len);
1540         if (trimmed && use_tilde) {
1541                 set_view_attr(view, LINE_DELIMITER);
1542                 waddch(view->win, '~');
1543                 col++;
1544         }
1546         return col;
1549 static int
1550 draw_space(struct view *view, enum line_type type, int max, int spaces)
1552         static char space[] = "                    ";
1553         int col = 0;
1555         spaces = MIN(max, spaces);
1557         while (spaces > 0) {
1558                 int len = MIN(spaces, sizeof(space) - 1);
1560                 col += draw_chars(view, type, space, spaces, FALSE);
1561                 spaces -= len;
1562         }
1564         return col;
1567 static bool
1568 draw_lineno(struct view *view, unsigned int lineno)
1570         char number[10];
1571         int digits3 = view->digits < 3 ? 3 : view->digits;
1572         int max_number = MIN(digits3, STRING_SIZE(number));
1573         int max = view->width - view->col;
1574         int col;
1576         if (max < max_number)
1577                 max_number = max;
1579         lineno += view->offset + 1;
1580         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1581                 static char fmt[] = "%1ld";
1583                 if (view->digits <= 9)
1584                         fmt[1] = '0' + digits3;
1586                 if (!string_format(number, fmt, lineno))
1587                         number[0] = 0;
1588                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1589         } else {
1590                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1591         }
1593         if (col < max) {
1594                 set_view_attr(view, LINE_DEFAULT);
1595                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1596                 col++;
1597         }
1599         if (col < max)
1600                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1601         view->col += col;
1603         return view->width - view->col <= 0;
1606 static bool
1607 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1609         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1610         return view->width - view->col <= 0;
1613 static bool
1614 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1616         int max = view->width - view->col;
1617         int i;
1619         if (max < size)
1620                 size = max;
1622         set_view_attr(view, type);
1623         /* Using waddch() instead of waddnstr() ensures that
1624          * they'll be rendered correctly for the cursor line. */
1625         for (i = 0; i < size; i++)
1626                 waddch(view->win, graphic[i]);
1628         view->col += size;
1629         if (size < max) {
1630                 waddch(view->win, ' ');
1631                 view->col++;
1632         }
1634         return view->width - view->col <= 0;
1637 static bool
1638 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1640         int max = MIN(view->width - view->col, len);
1641         int col;
1643         if (text)
1644                 col = draw_chars(view, type, text, max - 1, trim);
1645         else
1646                 col = draw_space(view, type, max - 1, max - 1);
1648         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1649         return view->width - view->col <= 0;
1652 static bool
1653 draw_date(struct view *view, struct tm *time)
1655         char buf[DATE_COLS];
1656         char *date;
1657         int timelen = 0;
1659         if (time)
1660                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1661         date = timelen ? buf : NULL;
1663         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1666 static bool
1667 draw_view_line(struct view *view, unsigned int lineno)
1669         struct line *line;
1670         bool selected = (view->offset + lineno == view->lineno);
1671         bool draw_ok;
1673         assert(view_is_displayed(view));
1675         if (view->offset + lineno >= view->lines)
1676                 return FALSE;
1678         line = &view->line[view->offset + lineno];
1680         wmove(view->win, lineno, 0);
1681         view->col = 0;
1682         view->curline = line;
1683         view->curtype = LINE_NONE;
1684         line->selected = FALSE;
1686         if (selected) {
1687                 set_view_attr(view, LINE_CURSOR);
1688                 line->selected = TRUE;
1689                 view->ops->select(view, line);
1690         } else if (line->selected) {
1691                 wclrtoeol(view->win);
1692         }
1694         scrollok(view->win, FALSE);
1695         draw_ok = view->ops->draw(view, line, lineno);
1696         scrollok(view->win, TRUE);
1698         return draw_ok;
1701 static void
1702 redraw_view_dirty(struct view *view)
1704         bool dirty = FALSE;
1705         int lineno;
1707         for (lineno = 0; lineno < view->height; lineno++) {
1708                 struct line *line = &view->line[view->offset + lineno];
1710                 if (!line->dirty)
1711                         continue;
1712                 line->dirty = 0;
1713                 dirty = TRUE;
1714                 if (!draw_view_line(view, lineno))
1715                         break;
1716         }
1718         if (!dirty)
1719                 return;
1720         redrawwin(view->win);
1721         if (input_mode)
1722                 wnoutrefresh(view->win);
1723         else
1724                 wrefresh(view->win);
1727 static void
1728 redraw_view_from(struct view *view, int lineno)
1730         assert(0 <= lineno && lineno < view->height);
1732         for (; lineno < view->height; lineno++) {
1733                 if (!draw_view_line(view, lineno))
1734                         break;
1735         }
1737         redrawwin(view->win);
1738         if (input_mode)
1739                 wnoutrefresh(view->win);
1740         else
1741                 wrefresh(view->win);
1744 static void
1745 redraw_view(struct view *view)
1747         wclear(view->win);
1748         redraw_view_from(view, 0);
1752 static void
1753 update_view_title(struct view *view)
1755         char buf[SIZEOF_STR];
1756         char state[SIZEOF_STR];
1757         size_t bufpos = 0, statelen = 0;
1759         assert(view_is_displayed(view));
1761         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1762                 unsigned int view_lines = view->offset + view->height;
1763                 unsigned int lines = view->lines
1764                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1765                                    : 0;
1767                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1768                                    view->ops->type,
1769                                    view->lineno + 1,
1770                                    view->lines,
1771                                    lines);
1773                 if (view->pipe) {
1774                         time_t secs = time(NULL) - view->start_time;
1776                         /* Three git seconds are a long time ... */
1777                         if (secs > 2)
1778                                 string_format_from(state, &statelen, " %lds", secs);
1779                 }
1780         }
1782         string_format_from(buf, &bufpos, "[%s]", view->name);
1783         if (*view->ref && bufpos < view->width) {
1784                 size_t refsize = strlen(view->ref);
1785                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1787                 if (minsize < view->width)
1788                         refsize = view->width - minsize + 7;
1789                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1790         }
1792         if (statelen && bufpos < view->width) {
1793                 string_format_from(buf, &bufpos, " %s", state);
1794         }
1796         if (view == display[current_view])
1797                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1798         else
1799                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1801         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1802         wclrtoeol(view->title);
1803         wmove(view->title, 0, view->width - 1);
1805         if (input_mode)
1806                 wnoutrefresh(view->title);
1807         else
1808                 wrefresh(view->title);
1811 static void
1812 resize_display(void)
1814         int offset, i;
1815         struct view *base = display[0];
1816         struct view *view = display[1] ? display[1] : display[0];
1818         /* Setup window dimensions */
1820         getmaxyx(stdscr, base->height, base->width);
1822         /* Make room for the status window. */
1823         base->height -= 1;
1825         if (view != base) {
1826                 /* Horizontal split. */
1827                 view->width   = base->width;
1828                 view->height  = SCALE_SPLIT_VIEW(base->height);
1829                 base->height -= view->height;
1831                 /* Make room for the title bar. */
1832                 view->height -= 1;
1833         }
1835         /* Make room for the title bar. */
1836         base->height -= 1;
1838         offset = 0;
1840         foreach_displayed_view (view, i) {
1841                 if (!view->win) {
1842                         view->win = newwin(view->height, 0, offset, 0);
1843                         if (!view->win)
1844                                 die("Failed to create %s view", view->name);
1846                         scrollok(view->win, TRUE);
1848                         view->title = newwin(1, 0, offset + view->height, 0);
1849                         if (!view->title)
1850                                 die("Failed to create title window");
1852                 } else {
1853                         wresize(view->win, view->height, view->width);
1854                         mvwin(view->win,   offset, 0);
1855                         mvwin(view->title, offset + view->height, 0);
1856                 }
1858                 offset += view->height + 1;
1859         }
1862 static void
1863 redraw_display(void)
1865         struct view *view;
1866         int i;
1868         foreach_displayed_view (view, i) {
1869                 redraw_view(view);
1870                 update_view_title(view);
1871         }
1874 static void
1875 update_display_cursor(struct view *view)
1877         /* Move the cursor to the right-most column of the cursor line.
1878          *
1879          * XXX: This could turn out to be a bit expensive, but it ensures that
1880          * the cursor does not jump around. */
1881         if (view->lines) {
1882                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1883                 wrefresh(view->win);
1884         }
1887 /*
1888  * Navigation
1889  */
1891 /* Scrolling backend */
1892 static void
1893 do_scroll_view(struct view *view, int lines)
1895         bool redraw_current_line = FALSE;
1897         /* The rendering expects the new offset. */
1898         view->offset += lines;
1900         assert(0 <= view->offset && view->offset < view->lines);
1901         assert(lines);
1903         /* Move current line into the view. */
1904         if (view->lineno < view->offset) {
1905                 view->lineno = view->offset;
1906                 redraw_current_line = TRUE;
1907         } else if (view->lineno >= view->offset + view->height) {
1908                 view->lineno = view->offset + view->height - 1;
1909                 redraw_current_line = TRUE;
1910         }
1912         assert(view->offset <= view->lineno && view->lineno < view->lines);
1914         /* Redraw the whole screen if scrolling is pointless. */
1915         if (view->height < ABS(lines)) {
1916                 redraw_view(view);
1918         } else {
1919                 int line = lines > 0 ? view->height - lines : 0;
1920                 int end = line + ABS(lines);
1922                 wscrl(view->win, lines);
1924                 for (; line < end; line++) {
1925                         if (!draw_view_line(view, line))
1926                                 break;
1927                 }
1929                 if (redraw_current_line)
1930                         draw_view_line(view, view->lineno - view->offset);
1931         }
1933         redrawwin(view->win);
1934         wrefresh(view->win);
1935         report("");
1938 /* Scroll frontend */
1939 static void
1940 scroll_view(struct view *view, enum request request)
1942         int lines = 1;
1944         assert(view_is_displayed(view));
1946         switch (request) {
1947         case REQ_SCROLL_PAGE_DOWN:
1948                 lines = view->height;
1949         case REQ_SCROLL_LINE_DOWN:
1950                 if (view->offset + lines > view->lines)
1951                         lines = view->lines - view->offset;
1953                 if (lines == 0 || view->offset + view->height >= view->lines) {
1954                         report("Cannot scroll beyond the last line");
1955                         return;
1956                 }
1957                 break;
1959         case REQ_SCROLL_PAGE_UP:
1960                 lines = view->height;
1961         case REQ_SCROLL_LINE_UP:
1962                 if (lines > view->offset)
1963                         lines = view->offset;
1965                 if (lines == 0) {
1966                         report("Cannot scroll beyond the first line");
1967                         return;
1968                 }
1970                 lines = -lines;
1971                 break;
1973         default:
1974                 die("request %d not handled in switch", request);
1975         }
1977         do_scroll_view(view, lines);
1980 /* Cursor moving */
1981 static void
1982 move_view(struct view *view, enum request request)
1984         int scroll_steps = 0;
1985         int steps;
1987         switch (request) {
1988         case REQ_MOVE_FIRST_LINE:
1989                 steps = -view->lineno;
1990                 break;
1992         case REQ_MOVE_LAST_LINE:
1993                 steps = view->lines - view->lineno - 1;
1994                 break;
1996         case REQ_MOVE_PAGE_UP:
1997                 steps = view->height > view->lineno
1998                       ? -view->lineno : -view->height;
1999                 break;
2001         case REQ_MOVE_PAGE_DOWN:
2002                 steps = view->lineno + view->height >= view->lines
2003                       ? view->lines - view->lineno - 1 : view->height;
2004                 break;
2006         case REQ_MOVE_UP:
2007                 steps = -1;
2008                 break;
2010         case REQ_MOVE_DOWN:
2011                 steps = 1;
2012                 break;
2014         default:
2015                 die("request %d not handled in switch", request);
2016         }
2018         if (steps <= 0 && view->lineno == 0) {
2019                 report("Cannot move beyond the first line");
2020                 return;
2022         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2023                 report("Cannot move beyond the last line");
2024                 return;
2025         }
2027         /* Move the current line */
2028         view->lineno += steps;
2029         assert(0 <= view->lineno && view->lineno < view->lines);
2031         /* Check whether the view needs to be scrolled */
2032         if (view->lineno < view->offset ||
2033             view->lineno >= view->offset + view->height) {
2034                 scroll_steps = steps;
2035                 if (steps < 0 && -steps > view->offset) {
2036                         scroll_steps = -view->offset;
2038                 } else if (steps > 0) {
2039                         if (view->lineno == view->lines - 1 &&
2040                             view->lines > view->height) {
2041                                 scroll_steps = view->lines - view->offset - 1;
2042                                 if (scroll_steps >= view->height)
2043                                         scroll_steps -= view->height - 1;
2044                         }
2045                 }
2046         }
2048         if (!view_is_displayed(view)) {
2049                 view->offset += scroll_steps;
2050                 assert(0 <= view->offset && view->offset < view->lines);
2051                 view->ops->select(view, &view->line[view->lineno]);
2052                 return;
2053         }
2055         /* Repaint the old "current" line if we be scrolling */
2056         if (ABS(steps) < view->height)
2057                 draw_view_line(view, view->lineno - steps - view->offset);
2059         if (scroll_steps) {
2060                 do_scroll_view(view, scroll_steps);
2061                 return;
2062         }
2064         /* Draw the current line */
2065         draw_view_line(view, view->lineno - view->offset);
2067         redrawwin(view->win);
2068         wrefresh(view->win);
2069         report("");
2073 /*
2074  * Searching
2075  */
2077 static void search_view(struct view *view, enum request request);
2079 static bool
2080 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2082         assert(view_is_displayed(view));
2084         if (!view->ops->grep(view, line))
2085                 return FALSE;
2087         if (lineno - view->offset >= view->height) {
2088                 view->offset = lineno;
2089                 view->lineno = lineno;
2090                 redraw_view(view);
2092         } else {
2093                 unsigned long old_lineno = view->lineno - view->offset;
2095                 view->lineno = lineno;
2096                 draw_view_line(view, old_lineno);
2098                 draw_view_line(view, view->lineno - view->offset);
2099                 redrawwin(view->win);
2100                 wrefresh(view->win);
2101         }
2103         report("Line %ld matches '%s'", lineno + 1, view->grep);
2104         return TRUE;
2107 static void
2108 find_next(struct view *view, enum request request)
2110         unsigned long lineno = view->lineno;
2111         int direction;
2113         if (!*view->grep) {
2114                 if (!*opt_search)
2115                         report("No previous search");
2116                 else
2117                         search_view(view, request);
2118                 return;
2119         }
2121         switch (request) {
2122         case REQ_SEARCH:
2123         case REQ_FIND_NEXT:
2124                 direction = 1;
2125                 break;
2127         case REQ_SEARCH_BACK:
2128         case REQ_FIND_PREV:
2129                 direction = -1;
2130                 break;
2132         default:
2133                 return;
2134         }
2136         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2137                 lineno += direction;
2139         /* Note, lineno is unsigned long so will wrap around in which case it
2140          * will become bigger than view->lines. */
2141         for (; lineno < view->lines; lineno += direction) {
2142                 struct line *line = &view->line[lineno];
2144                 if (find_next_line(view, lineno, line))
2145                         return;
2146         }
2148         report("No match found for '%s'", view->grep);
2151 static void
2152 search_view(struct view *view, enum request request)
2154         int regex_err;
2156         if (view->regex) {
2157                 regfree(view->regex);
2158                 *view->grep = 0;
2159         } else {
2160                 view->regex = calloc(1, sizeof(*view->regex));
2161                 if (!view->regex)
2162                         return;
2163         }
2165         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2166         if (regex_err != 0) {
2167                 char buf[SIZEOF_STR] = "unknown error";
2169                 regerror(regex_err, view->regex, buf, sizeof(buf));
2170                 report("Search failed: %s", buf);
2171                 return;
2172         }
2174         string_copy(view->grep, opt_search);
2176         find_next(view, request);
2179 /*
2180  * Incremental updating
2181  */
2183 static void
2184 end_update(struct view *view, bool force)
2186         if (!view->pipe)
2187                 return;
2188         while (!view->ops->read(view, NULL))
2189                 if (!force)
2190                         return;
2191         set_nonblocking_input(FALSE);
2192         if (view->pipe == stdin)
2193                 fclose(view->pipe);
2194         else
2195                 pclose(view->pipe);
2196         view->pipe = NULL;
2199 static bool
2200 begin_update(struct view *view, bool refresh)
2202         if (opt_cmd[0]) {
2203                 string_copy(view->cmd, opt_cmd);
2204                 opt_cmd[0] = 0;
2205                 /* When running random commands, initially show the
2206                  * command in the title. However, it maybe later be
2207                  * overwritten if a commit line is selected. */
2208                 if (view == VIEW(REQ_VIEW_PAGER))
2209                         string_copy(view->ref, view->cmd);
2210                 else
2211                         view->ref[0] = 0;
2213         } else if (view == VIEW(REQ_VIEW_TREE)) {
2214                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2215                 char path[SIZEOF_STR];
2217                 if (strcmp(view->vid, view->id))
2218                         opt_path[0] = path[0] = 0;
2219                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2220                         return FALSE;
2222                 if (!string_format(view->cmd, format, view->id, path))
2223                         return FALSE;
2225         } else if (!refresh) {
2226                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2227                 const char *id = view->id;
2229                 if (!string_format(view->cmd, format, id, id, id, id, id))
2230                         return FALSE;
2232                 /* Put the current ref_* value to the view title ref
2233                  * member. This is needed by the blob view. Most other
2234                  * views sets it automatically after loading because the
2235                  * first line is a commit line. */
2236                 string_copy_rev(view->ref, view->id);
2237         }
2239         /* Special case for the pager view. */
2240         if (opt_pipe) {
2241                 view->pipe = opt_pipe;
2242                 opt_pipe = NULL;
2243         } else {
2244                 view->pipe = popen(view->cmd, "r");
2245         }
2247         if (!view->pipe)
2248                 return FALSE;
2250         set_nonblocking_input(TRUE);
2252         view->offset = 0;
2253         view->lines  = 0;
2254         view->lineno = 0;
2255         string_copy_rev(view->vid, view->id);
2257         if (view->line) {
2258                 int i;
2260                 for (i = 0; i < view->lines; i++)
2261                         if (view->line[i].data)
2262                                 free(view->line[i].data);
2264                 free(view->line);
2265                 view->line = NULL;
2266         }
2268         view->start_time = time(NULL);
2270         return TRUE;
2273 #define ITEM_CHUNK_SIZE 256
2274 static void *
2275 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2277         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2278         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2280         if (mem == NULL || num_chunks != num_chunks_new) {
2281                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2282                 mem = realloc(mem, *size * item_size);
2283         }
2285         return mem;
2288 static struct line *
2289 realloc_lines(struct view *view, size_t line_size)
2291         size_t alloc = view->line_alloc;
2292         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2293                                          sizeof(*view->line));
2295         if (!tmp)
2296                 return NULL;
2298         view->line = tmp;
2299         view->line_alloc = alloc;
2300         view->line_size = line_size;
2301         return view->line;
2304 static bool
2305 update_view(struct view *view)
2307         char in_buffer[BUFSIZ];
2308         char out_buffer[BUFSIZ * 2];
2309         char *line;
2310         /* The number of lines to read. If too low it will cause too much
2311          * redrawing (and possible flickering), if too high responsiveness
2312          * will suffer. */
2313         unsigned long lines = view->height;
2314         int redraw_from = -1;
2316         if (!view->pipe)
2317                 return TRUE;
2319         /* Only redraw if lines are visible. */
2320         if (view->offset + view->height >= view->lines)
2321                 redraw_from = view->lines - view->offset;
2323         /* FIXME: This is probably not perfect for backgrounded views. */
2324         if (!realloc_lines(view, view->lines + lines))
2325                 goto alloc_error;
2327         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2328                 size_t linelen = strlen(line);
2330                 if (linelen)
2331                         line[linelen - 1] = 0;
2333                 if (opt_iconv != ICONV_NONE) {
2334                         ICONV_CONST char *inbuf = line;
2335                         size_t inlen = linelen;
2337                         char *outbuf = out_buffer;
2338                         size_t outlen = sizeof(out_buffer);
2340                         size_t ret;
2342                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2343                         if (ret != (size_t) -1) {
2344                                 line = out_buffer;
2345                                 linelen = strlen(out_buffer);
2346                         }
2347                 }
2349                 if (!view->ops->read(view, line))
2350                         goto alloc_error;
2352                 if (lines-- == 1)
2353                         break;
2354         }
2356         {
2357                 int digits;
2359                 lines = view->lines;
2360                 for (digits = 0; lines; digits++)
2361                         lines /= 10;
2363                 /* Keep the displayed view in sync with line number scaling. */
2364                 if (digits != view->digits) {
2365                         view->digits = digits;
2366                         redraw_from = 0;
2367                 }
2368         }
2370         if (!view_is_displayed(view))
2371                 goto check_pipe;
2373         if (view == VIEW(REQ_VIEW_TREE)) {
2374                 /* Clear the view and redraw everything since the tree sorting
2375                  * might have rearranged things. */
2376                 redraw_view(view);
2378         } else if (redraw_from >= 0) {
2379                 /* If this is an incremental update, redraw the previous line
2380                  * since for commits some members could have changed when
2381                  * loading the main view. */
2382                 if (redraw_from > 0)
2383                         redraw_from--;
2385                 /* Since revision graph visualization requires knowledge
2386                  * about the parent commit, it causes a further one-off
2387                  * needed to be redrawn for incremental updates. */
2388                 if (redraw_from > 0 && opt_rev_graph)
2389                         redraw_from--;
2391                 /* Incrementally draw avoids flickering. */
2392                 redraw_view_from(view, redraw_from);
2393         }
2395         if (view == VIEW(REQ_VIEW_BLAME))
2396                 redraw_view_dirty(view);
2398         /* Update the title _after_ the redraw so that if the redraw picks up a
2399          * commit reference in view->ref it'll be available here. */
2400         update_view_title(view);
2402 check_pipe:
2403         if (ferror(view->pipe) && errno != 0) {
2404                 report("Failed to read: %s", strerror(errno));
2405                 end_update(view, TRUE);
2407         } else if (feof(view->pipe)) {
2408                 report("");
2409                 end_update(view, FALSE);
2410         }
2412         return TRUE;
2414 alloc_error:
2415         report("Allocation failure");
2416         end_update(view, TRUE);
2417         return FALSE;
2420 static struct line *
2421 add_line_data(struct view *view, void *data, enum line_type type)
2423         struct line *line = &view->line[view->lines++];
2425         memset(line, 0, sizeof(*line));
2426         line->type = type;
2427         line->data = data;
2429         return line;
2432 static struct line *
2433 add_line_text(struct view *view, char *data, enum line_type type)
2435         if (data)
2436                 data = strdup(data);
2438         return data ? add_line_data(view, data, type) : NULL;
2442 /*
2443  * View opening
2444  */
2446 enum open_flags {
2447         OPEN_DEFAULT = 0,       /* Use default view switching. */
2448         OPEN_SPLIT = 1,         /* Split current view. */
2449         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2450         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2451         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2452         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2453 };
2455 static void
2456 open_view(struct view *prev, enum request request, enum open_flags flags)
2458         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2459         bool split = !!(flags & OPEN_SPLIT);
2460         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2461         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2462         struct view *view = VIEW(request);
2463         int nviews = displayed_views();
2464         struct view *base_view = display[0];
2466         if (view == prev && nviews == 1 && !reload) {
2467                 report("Already in %s view", view->name);
2468                 return;
2469         }
2471         if (view->git_dir && !opt_git_dir[0]) {
2472                 report("The %s view is disabled in pager view", view->name);
2473                 return;
2474         }
2476         if (split) {
2477                 display[1] = view;
2478                 if (!backgrounded)
2479                         current_view = 1;
2480         } else if (!nomaximize) {
2481                 /* Maximize the current view. */
2482                 memset(display, 0, sizeof(display));
2483                 current_view = 0;
2484                 display[current_view] = view;
2485         }
2487         /* Resize the view when switching between split- and full-screen,
2488          * or when switching between two different full-screen views. */
2489         if (nviews != displayed_views() ||
2490             (nviews == 1 && base_view != display[0]))
2491                 resize_display();
2493         if (view->pipe)
2494                 end_update(view, TRUE);
2496         if (view->ops->open) {
2497                 if (!view->ops->open(view)) {
2498                         report("Failed to load %s view", view->name);
2499                         return;
2500                 }
2502         } else if ((reload || strcmp(view->vid, view->id)) &&
2503                    !begin_update(view, flags & OPEN_REFRESH)) {
2504                 report("Failed to load %s view", view->name);
2505                 return;
2506         }
2508         if (split && prev->lineno - prev->offset >= prev->height) {
2509                 /* Take the title line into account. */
2510                 int lines = prev->lineno - prev->offset - prev->height + 1;
2512                 /* Scroll the view that was split if the current line is
2513                  * outside the new limited view. */
2514                 do_scroll_view(prev, lines);
2515         }
2517         if (prev && view != prev) {
2518                 if (split && !backgrounded) {
2519                         /* "Blur" the previous view. */
2520                         update_view_title(prev);
2521                 }
2523                 view->parent = prev;
2524         }
2526         if (view->pipe && view->lines == 0) {
2527                 /* Clear the old view and let the incremental updating refill
2528                  * the screen. */
2529                 werase(view->win);
2530                 report("");
2531         } else {
2532                 redraw_view(view);
2533                 report("");
2534         }
2536         /* If the view is backgrounded the above calls to report()
2537          * won't redraw the view title. */
2538         if (backgrounded)
2539                 update_view_title(view);
2542 static bool
2543 run_confirm(const char *cmd, const char *prompt)
2545         bool confirmation = prompt_yesno(prompt);
2547         if (confirmation)
2548                 system(cmd);
2550         return confirmation;
2553 static void
2554 open_external_viewer(const char *cmd)
2556         def_prog_mode();           /* save current tty modes */
2557         endwin();                  /* restore original tty modes */
2558         system(cmd);
2559         fprintf(stderr, "Press Enter to continue");
2560         getc(stdin);
2561         reset_prog_mode();
2562         redraw_display();
2565 static void
2566 open_mergetool(const char *file)
2568         char cmd[SIZEOF_STR];
2569         char file_sq[SIZEOF_STR];
2571         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2572             string_format(cmd, "git mergetool %s", file_sq)) {
2573                 open_external_viewer(cmd);
2574         }
2577 static void
2578 open_editor(bool from_root, const char *file)
2580         char cmd[SIZEOF_STR];
2581         char file_sq[SIZEOF_STR];
2582         char *editor;
2583         char *prefix = from_root ? opt_cdup : "";
2585         editor = getenv("GIT_EDITOR");
2586         if (!editor && *opt_editor)
2587                 editor = opt_editor;
2588         if (!editor)
2589                 editor = getenv("VISUAL");
2590         if (!editor)
2591                 editor = getenv("EDITOR");
2592         if (!editor)
2593                 editor = "vi";
2595         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2596             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2597                 open_external_viewer(cmd);
2598         }
2601 static void
2602 open_run_request(enum request request)
2604         struct run_request *req = get_run_request(request);
2605         char buf[SIZEOF_STR * 2];
2606         size_t bufpos;
2607         char *cmd;
2609         if (!req) {
2610                 report("Unknown run request");
2611                 return;
2612         }
2614         bufpos = 0;
2615         cmd = req->cmd;
2617         while (cmd) {
2618                 char *next = strstr(cmd, "%(");
2619                 int len = next - cmd;
2620                 char *value;
2622                 if (!next) {
2623                         len = strlen(cmd);
2624                         value = "";
2626                 } else if (!strncmp(next, "%(head)", 7)) {
2627                         value = ref_head;
2629                 } else if (!strncmp(next, "%(commit)", 9)) {
2630                         value = ref_commit;
2632                 } else if (!strncmp(next, "%(blob)", 7)) {
2633                         value = ref_blob;
2635                 } else {
2636                         report("Unknown replacement in run request: `%s`", req->cmd);
2637                         return;
2638                 }
2640                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2641                         return;
2643                 if (next)
2644                         next = strchr(next, ')') + 1;
2645                 cmd = next;
2646         }
2648         open_external_viewer(buf);
2651 /*
2652  * User request switch noodle
2653  */
2655 static int
2656 view_driver(struct view *view, enum request request)
2658         int i;
2660         if (request == REQ_NONE) {
2661                 doupdate();
2662                 return TRUE;
2663         }
2665         if (request > REQ_NONE) {
2666                 open_run_request(request);
2667                 /* FIXME: When all views can refresh always do this. */
2668                 if (view == VIEW(REQ_VIEW_STATUS) ||
2669                     view == VIEW(REQ_VIEW_MAIN) ||
2670                     view == VIEW(REQ_VIEW_LOG) ||
2671                     view == VIEW(REQ_VIEW_STAGE))
2672                         request = REQ_REFRESH;
2673                 else
2674                         return TRUE;
2675         }
2677         if (view && view->lines) {
2678                 request = view->ops->request(view, request, &view->line[view->lineno]);
2679                 if (request == REQ_NONE)
2680                         return TRUE;
2681         }
2683         switch (request) {
2684         case REQ_MOVE_UP:
2685         case REQ_MOVE_DOWN:
2686         case REQ_MOVE_PAGE_UP:
2687         case REQ_MOVE_PAGE_DOWN:
2688         case REQ_MOVE_FIRST_LINE:
2689         case REQ_MOVE_LAST_LINE:
2690                 move_view(view, request);
2691                 break;
2693         case REQ_SCROLL_LINE_DOWN:
2694         case REQ_SCROLL_LINE_UP:
2695         case REQ_SCROLL_PAGE_DOWN:
2696         case REQ_SCROLL_PAGE_UP:
2697                 scroll_view(view, request);
2698                 break;
2700         case REQ_VIEW_BLAME:
2701                 if (!opt_file[0]) {
2702                         report("No file chosen, press %s to open tree view",
2703                                get_key(REQ_VIEW_TREE));
2704                         break;
2705                 }
2706                 open_view(view, request, OPEN_DEFAULT);
2707                 break;
2709         case REQ_VIEW_BLOB:
2710                 if (!ref_blob[0]) {
2711                         report("No file chosen, press %s to open tree view",
2712                                get_key(REQ_VIEW_TREE));
2713                         break;
2714                 }
2715                 open_view(view, request, OPEN_DEFAULT);
2716                 break;
2718         case REQ_VIEW_PAGER:
2719                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2720                         report("No pager content, press %s to run command from prompt",
2721                                get_key(REQ_PROMPT));
2722                         break;
2723                 }
2724                 open_view(view, request, OPEN_DEFAULT);
2725                 break;
2727         case REQ_VIEW_STAGE:
2728                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2729                         report("No stage content, press %s to open the status view and choose file",
2730                                get_key(REQ_VIEW_STATUS));
2731                         break;
2732                 }
2733                 open_view(view, request, OPEN_DEFAULT);
2734                 break;
2736         case REQ_VIEW_STATUS:
2737                 if (opt_is_inside_work_tree == FALSE) {
2738                         report("The status view requires a working tree");
2739                         break;
2740                 }
2741                 open_view(view, request, OPEN_DEFAULT);
2742                 break;
2744         case REQ_VIEW_MAIN:
2745         case REQ_VIEW_DIFF:
2746         case REQ_VIEW_LOG:
2747         case REQ_VIEW_TREE:
2748         case REQ_VIEW_HELP:
2749                 open_view(view, request, OPEN_DEFAULT);
2750                 break;
2752         case REQ_NEXT:
2753         case REQ_PREVIOUS:
2754                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2756                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2757                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2758                    (view == VIEW(REQ_VIEW_DIFF) &&
2759                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2760                    (view == VIEW(REQ_VIEW_STAGE) &&
2761                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2762                    (view == VIEW(REQ_VIEW_BLOB) &&
2763                      view->parent == VIEW(REQ_VIEW_TREE))) {
2764                         int line;
2766                         view = view->parent;
2767                         line = view->lineno;
2768                         move_view(view, request);
2769                         if (view_is_displayed(view))
2770                                 update_view_title(view);
2771                         if (line != view->lineno)
2772                                 view->ops->request(view, REQ_ENTER,
2773                                                    &view->line[view->lineno]);
2775                 } else {
2776                         move_view(view, request);
2777                 }
2778                 break;
2780         case REQ_VIEW_NEXT:
2781         {
2782                 int nviews = displayed_views();
2783                 int next_view = (current_view + 1) % nviews;
2785                 if (next_view == current_view) {
2786                         report("Only one view is displayed");
2787                         break;
2788                 }
2790                 current_view = next_view;
2791                 /* Blur out the title of the previous view. */
2792                 update_view_title(view);
2793                 report("");
2794                 break;
2795         }
2796         case REQ_REFRESH:
2797                 report("Refreshing is not yet supported for the %s view", view->name);
2798                 break;
2800         case REQ_MAXIMIZE:
2801                 if (displayed_views() == 2)
2802                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2803                 break;
2805         case REQ_TOGGLE_LINENO:
2806                 opt_line_number = !opt_line_number;
2807                 redraw_display();
2808                 break;
2810         case REQ_TOGGLE_DATE:
2811                 opt_date = !opt_date;
2812                 redraw_display();
2813                 break;
2815         case REQ_TOGGLE_AUTHOR:
2816                 opt_author = !opt_author;
2817                 redraw_display();
2818                 break;
2820         case REQ_TOGGLE_REV_GRAPH:
2821                 opt_rev_graph = !opt_rev_graph;
2822                 redraw_display();
2823                 break;
2825         case REQ_TOGGLE_REFS:
2826                 opt_show_refs = !opt_show_refs;
2827                 redraw_display();
2828                 break;
2830         case REQ_SEARCH:
2831         case REQ_SEARCH_BACK:
2832                 search_view(view, request);
2833                 break;
2835         case REQ_FIND_NEXT:
2836         case REQ_FIND_PREV:
2837                 find_next(view, request);
2838                 break;
2840         case REQ_STOP_LOADING:
2841                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2842                         view = &views[i];
2843                         if (view->pipe)
2844                                 report("Stopped loading the %s view", view->name),
2845                         end_update(view, TRUE);
2846                 }
2847                 break;
2849         case REQ_SHOW_VERSION:
2850                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2851                 return TRUE;
2853         case REQ_SCREEN_RESIZE:
2854                 resize_display();
2855                 /* Fall-through */
2856         case REQ_SCREEN_REDRAW:
2857                 redraw_display();
2858                 break;
2860         case REQ_EDIT:
2861                 report("Nothing to edit");
2862                 break;
2864         case REQ_ENTER:
2865                 report("Nothing to enter");
2866                 break;
2868         case REQ_VIEW_CLOSE:
2869                 /* XXX: Mark closed views by letting view->parent point to the
2870                  * view itself. Parents to closed view should never be
2871                  * followed. */
2872                 if (view->parent &&
2873                     view->parent->parent != view->parent) {
2874                         memset(display, 0, sizeof(display));
2875                         current_view = 0;
2876                         display[current_view] = view->parent;
2877                         view->parent = view;
2878                         resize_display();
2879                         redraw_display();
2880                         report("");
2881                         break;
2882                 }
2883                 /* Fall-through */
2884         case REQ_QUIT:
2885                 return FALSE;
2887         default:
2888                 /* An unknown key will show most commonly used commands. */
2889                 report("Unknown key, press 'h' for help");
2890                 return TRUE;
2891         }
2893         return TRUE;
2897 /*
2898  * Pager backend
2899  */
2901 static bool
2902 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2904         char *text = line->data;
2906         if (opt_line_number && draw_lineno(view, lineno))
2907                 return TRUE;
2909         draw_text(view, line->type, text, TRUE);
2910         return TRUE;
2913 static bool
2914 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2916         char refbuf[SIZEOF_STR];
2917         char *ref = NULL;
2918         FILE *pipe;
2920         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2921                 return TRUE;
2923         pipe = popen(refbuf, "r");
2924         if (!pipe)
2925                 return TRUE;
2927         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2928                 ref = chomp_string(ref);
2929         pclose(pipe);
2931         if (!ref || !*ref)
2932                 return TRUE;
2934         /* This is the only fatal call, since it can "corrupt" the buffer. */
2935         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2936                 return FALSE;
2938         return TRUE;
2941 static void
2942 add_pager_refs(struct view *view, struct line *line)
2944         char buf[SIZEOF_STR];
2945         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2946         struct ref **refs;
2947         size_t bufpos = 0, refpos = 0;
2948         const char *sep = "Refs: ";
2949         bool is_tag = FALSE;
2951         assert(line->type == LINE_COMMIT);
2953         refs = get_refs(commit_id);
2954         if (!refs) {
2955                 if (view == VIEW(REQ_VIEW_DIFF))
2956                         goto try_add_describe_ref;
2957                 return;
2958         }
2960         do {
2961                 struct ref *ref = refs[refpos];
2962                 char *fmt = ref->tag    ? "%s[%s]" :
2963                             ref->remote ? "%s<%s>" : "%s%s";
2965                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2966                         return;
2967                 sep = ", ";
2968                 if (ref->tag)
2969                         is_tag = TRUE;
2970         } while (refs[refpos++]->next);
2972         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2973 try_add_describe_ref:
2974                 /* Add <tag>-g<commit_id> "fake" reference. */
2975                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2976                         return;
2977         }
2979         if (bufpos == 0)
2980                 return;
2982         if (!realloc_lines(view, view->line_size + 1))
2983                 return;
2985         add_line_text(view, buf, LINE_PP_REFS);
2988 static bool
2989 pager_read(struct view *view, char *data)
2991         struct line *line;
2993         if (!data)
2994                 return TRUE;
2996         line = add_line_text(view, data, get_line_type(data));
2997         if (!line)
2998                 return FALSE;
3000         if (line->type == LINE_COMMIT &&
3001             (view == VIEW(REQ_VIEW_DIFF) ||
3002              view == VIEW(REQ_VIEW_LOG)))
3003                 add_pager_refs(view, line);
3005         return TRUE;
3008 static enum request
3009 pager_request(struct view *view, enum request request, struct line *line)
3011         int split = 0;
3013         if (request != REQ_ENTER)
3014                 return request;
3016         if (line->type == LINE_COMMIT &&
3017            (view == VIEW(REQ_VIEW_LOG) ||
3018             view == VIEW(REQ_VIEW_PAGER))) {
3019                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3020                 split = 1;
3021         }
3023         /* Always scroll the view even if it was split. That way
3024          * you can use Enter to scroll through the log view and
3025          * split open each commit diff. */
3026         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3028         /* FIXME: A minor workaround. Scrolling the view will call report("")
3029          * but if we are scrolling a non-current view this won't properly
3030          * update the view title. */
3031         if (split)
3032                 update_view_title(view);
3034         return REQ_NONE;
3037 static bool
3038 pager_grep(struct view *view, struct line *line)
3040         regmatch_t pmatch;
3041         char *text = line->data;
3043         if (!*text)
3044                 return FALSE;
3046         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3047                 return FALSE;
3049         return TRUE;
3052 static void
3053 pager_select(struct view *view, struct line *line)
3055         if (line->type == LINE_COMMIT) {
3056                 char *text = (char *)line->data + STRING_SIZE("commit ");
3058                 if (view != VIEW(REQ_VIEW_PAGER))
3059                         string_copy_rev(view->ref, text);
3060                 string_copy_rev(ref_commit, text);
3061         }
3064 static struct view_ops pager_ops = {
3065         "line",
3066         NULL,
3067         pager_read,
3068         pager_draw,
3069         pager_request,
3070         pager_grep,
3071         pager_select,
3072 };
3074 static enum request
3075 log_request(struct view *view, enum request request, struct line *line)
3077         switch (request) {
3078         case REQ_REFRESH:
3079                 load_refs();
3080                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3081                 return REQ_NONE;
3082         default:
3083                 return pager_request(view, request, line);
3084         }
3087 static struct view_ops log_ops = {
3088         "line",
3089         NULL,
3090         pager_read,
3091         pager_draw,
3092         log_request,
3093         pager_grep,
3094         pager_select,
3095 };
3098 /*
3099  * Help backend
3100  */
3102 static bool
3103 help_open(struct view *view)
3105         char buf[BUFSIZ];
3106         int lines = ARRAY_SIZE(req_info) + 2;
3107         int i;
3109         if (view->lines > 0)
3110                 return TRUE;
3112         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3113                 if (!req_info[i].request)
3114                         lines++;
3116         lines += run_requests + 1;
3118         view->line = calloc(lines, sizeof(*view->line));
3119         if (!view->line)
3120                 return FALSE;
3122         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3124         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3125                 char *key;
3127                 if (req_info[i].request == REQ_NONE)
3128                         continue;
3130                 if (!req_info[i].request) {
3131                         add_line_text(view, "", LINE_DEFAULT);
3132                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3133                         continue;
3134                 }
3136                 key = get_key(req_info[i].request);
3137                 if (!*key)
3138                         key = "(no key defined)";
3140                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3141                         continue;
3143                 add_line_text(view, buf, LINE_DEFAULT);
3144         }
3146         if (run_requests) {
3147                 add_line_text(view, "", LINE_DEFAULT);
3148                 add_line_text(view, "External commands:", LINE_DEFAULT);
3149         }
3151         for (i = 0; i < run_requests; i++) {
3152                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3153                 char *key;
3155                 if (!req)
3156                         continue;
3158                 key = get_key_name(req->key);
3159                 if (!*key)
3160                         key = "(no key defined)";
3162                 if (!string_format(buf, "    %-10s %-14s `%s`",
3163                                    keymap_table[req->keymap].name,
3164                                    key, req->cmd))
3165                         continue;
3167                 add_line_text(view, buf, LINE_DEFAULT);
3168         }
3170         return TRUE;
3173 static struct view_ops help_ops = {
3174         "line",
3175         help_open,
3176         NULL,
3177         pager_draw,
3178         pager_request,
3179         pager_grep,
3180         pager_select,
3181 };
3184 /*
3185  * Tree backend
3186  */
3188 struct tree_stack_entry {
3189         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3190         unsigned long lineno;           /* Line number to restore */
3191         char *name;                     /* Position of name in opt_path */
3192 };
3194 /* The top of the path stack. */
3195 static struct tree_stack_entry *tree_stack = NULL;
3196 unsigned long tree_lineno = 0;
3198 static void
3199 pop_tree_stack_entry(void)
3201         struct tree_stack_entry *entry = tree_stack;
3203         tree_lineno = entry->lineno;
3204         entry->name[0] = 0;
3205         tree_stack = entry->prev;
3206         free(entry);
3209 static void
3210 push_tree_stack_entry(char *name, unsigned long lineno)
3212         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3213         size_t pathlen = strlen(opt_path);
3215         if (!entry)
3216                 return;
3218         entry->prev = tree_stack;
3219         entry->name = opt_path + pathlen;
3220         tree_stack = entry;
3222         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3223                 pop_tree_stack_entry();
3224                 return;
3225         }
3227         /* Move the current line to the first tree entry. */
3228         tree_lineno = 1;
3229         entry->lineno = lineno;
3232 /* Parse output from git-ls-tree(1):
3233  *
3234  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3235  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3236  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3237  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3238  */
3240 #define SIZEOF_TREE_ATTR \
3241         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3243 #define TREE_UP_FORMAT "040000 tree %s\t.."
3245 static int
3246 tree_compare_entry(enum line_type type1, char *name1,
3247                    enum line_type type2, char *name2)
3249         if (type1 != type2) {
3250                 if (type1 == LINE_TREE_DIR)
3251                         return -1;
3252                 return 1;
3253         }
3255         return strcmp(name1, name2);
3258 static char *
3259 tree_path(struct line *line)
3261         char *path = line->data;
3263         return path + SIZEOF_TREE_ATTR;
3266 static bool
3267 tree_read(struct view *view, char *text)
3269         size_t textlen = text ? strlen(text) : 0;
3270         char buf[SIZEOF_STR];
3271         unsigned long pos;
3272         enum line_type type;
3273         bool first_read = view->lines == 0;
3275         if (!text)
3276                 return TRUE;
3277         if (textlen <= SIZEOF_TREE_ATTR)
3278                 return FALSE;
3280         type = text[STRING_SIZE("100644 ")] == 't'
3281              ? LINE_TREE_DIR : LINE_TREE_FILE;
3283         if (first_read) {
3284                 /* Add path info line */
3285                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3286                     !realloc_lines(view, view->line_size + 1) ||
3287                     !add_line_text(view, buf, LINE_DEFAULT))
3288                         return FALSE;
3290                 /* Insert "link" to parent directory. */
3291                 if (*opt_path) {
3292                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3293                             !realloc_lines(view, view->line_size + 1) ||
3294                             !add_line_text(view, buf, LINE_TREE_DIR))
3295                                 return FALSE;
3296                 }
3297         }
3299         /* Strip the path part ... */
3300         if (*opt_path) {
3301                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3302                 size_t striplen = strlen(opt_path);
3303                 char *path = text + SIZEOF_TREE_ATTR;
3305                 if (pathlen > striplen)
3306                         memmove(path, path + striplen,
3307                                 pathlen - striplen + 1);
3308         }
3310         /* Skip "Directory ..." and ".." line. */
3311         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3312                 struct line *line = &view->line[pos];
3313                 char *path1 = tree_path(line);
3314                 char *path2 = text + SIZEOF_TREE_ATTR;
3315                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3317                 if (cmp <= 0)
3318                         continue;
3320                 text = strdup(text);
3321                 if (!text)
3322                         return FALSE;
3324                 if (view->lines > pos)
3325                         memmove(&view->line[pos + 1], &view->line[pos],
3326                                 (view->lines - pos) * sizeof(*line));
3328                 line = &view->line[pos];
3329                 line->data = text;
3330                 line->type = type;
3331                 view->lines++;
3332                 return TRUE;
3333         }
3335         if (!add_line_text(view, text, type))
3336                 return FALSE;
3338         if (tree_lineno > view->lineno) {
3339                 view->lineno = tree_lineno;
3340                 tree_lineno = 0;
3341         }
3343         return TRUE;
3346 static enum request
3347 tree_request(struct view *view, enum request request, struct line *line)
3349         enum open_flags flags;
3351         if (request == REQ_VIEW_BLAME) {
3352                 char *filename = tree_path(line);
3354                 if (line->type == LINE_TREE_DIR) {
3355                         report("Cannot show blame for directory %s", opt_path);
3356                         return REQ_NONE;
3357                 }
3359                 string_copy(opt_ref, view->vid);
3360                 string_format(opt_file, "%s%s", opt_path, filename);
3361                 return request;
3362         }
3363         if (request == REQ_TREE_PARENT) {
3364                 if (*opt_path) {
3365                         /* fake 'cd  ..' */
3366                         request = REQ_ENTER;
3367                         line = &view->line[1];
3368                 } else {
3369                         /* quit view if at top of tree */
3370                         return REQ_VIEW_CLOSE;
3371                 }
3372         }
3373         if (request != REQ_ENTER)
3374                 return request;
3376         /* Cleanup the stack if the tree view is at a different tree. */
3377         while (!*opt_path && tree_stack)
3378                 pop_tree_stack_entry();
3380         switch (line->type) {
3381         case LINE_TREE_DIR:
3382                 /* Depending on whether it is a subdir or parent (updir?) link
3383                  * mangle the path buffer. */
3384                 if (line == &view->line[1] && *opt_path) {
3385                         pop_tree_stack_entry();
3387                 } else {
3388                         char *basename = tree_path(line);
3390                         push_tree_stack_entry(basename, view->lineno);
3391                 }
3393                 /* Trees and subtrees share the same ID, so they are not not
3394                  * unique like blobs. */
3395                 flags = OPEN_RELOAD;
3396                 request = REQ_VIEW_TREE;
3397                 break;
3399         case LINE_TREE_FILE:
3400                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3401                 request = REQ_VIEW_BLOB;
3402                 break;
3404         default:
3405                 return TRUE;
3406         }
3408         open_view(view, request, flags);
3409         if (request == REQ_VIEW_TREE) {
3410                 view->lineno = tree_lineno;
3411         }
3413         return REQ_NONE;
3416 static void
3417 tree_select(struct view *view, struct line *line)
3419         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3421         if (line->type == LINE_TREE_FILE) {
3422                 string_copy_rev(ref_blob, text);
3424         } else if (line->type != LINE_TREE_DIR) {
3425                 return;
3426         }
3428         string_copy_rev(view->ref, text);
3431 static struct view_ops tree_ops = {
3432         "file",
3433         NULL,
3434         tree_read,
3435         pager_draw,
3436         tree_request,
3437         pager_grep,
3438         tree_select,
3439 };
3441 static bool
3442 blob_read(struct view *view, char *line)
3444         if (!line)
3445                 return TRUE;
3446         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3449 static struct view_ops blob_ops = {
3450         "line",
3451         NULL,
3452         blob_read,
3453         pager_draw,
3454         pager_request,
3455         pager_grep,
3456         pager_select,
3457 };
3459 /*
3460  * Blame backend
3461  *
3462  * Loading the blame view is a two phase job:
3463  *
3464  *  1. File content is read either using opt_file from the
3465  *     filesystem or using git-cat-file.
3466  *  2. Then blame information is incrementally added by
3467  *     reading output from git-blame.
3468  */
3470 struct blame_commit {
3471         char id[SIZEOF_REV];            /* SHA1 ID. */
3472         char title[128];                /* First line of the commit message. */
3473         char author[75];                /* Author of the commit. */
3474         struct tm time;                 /* Date from the author ident. */
3475         char filename[128];             /* Name of file. */
3476 };
3478 struct blame {
3479         struct blame_commit *commit;
3480         unsigned int header:1;
3481         char text[1];
3482 };
3484 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3485 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3487 static bool
3488 blame_open(struct view *view)
3490         char path[SIZEOF_STR];
3491         char ref[SIZEOF_STR] = "";
3493         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3494                 return FALSE;
3496         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3497                 return FALSE;
3499         if (*opt_ref) {
3500                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3501                         return FALSE;
3502         } else {
3503                 view->pipe = fopen(opt_file, "r");
3504                 if (!view->pipe &&
3505                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3506                         return FALSE;
3507         }
3509         if (!view->pipe)
3510                 view->pipe = popen(view->cmd, "r");
3511         if (!view->pipe)
3512                 return FALSE;
3514         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3515                 return FALSE;
3517         string_format(view->ref, "%s ...", opt_file);
3518         string_copy_rev(view->vid, opt_file);
3519         set_nonblocking_input(TRUE);
3521         if (view->line) {
3522                 int i;
3524                 for (i = 0; i < view->lines; i++)
3525                         free(view->line[i].data);
3526                 free(view->line);
3527         }
3529         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3530         view->offset = view->lines  = view->lineno = 0;
3531         view->line = NULL;
3532         view->start_time = time(NULL);
3534         return TRUE;
3537 static struct blame_commit *
3538 get_blame_commit(struct view *view, const char *id)
3540         size_t i;
3542         for (i = 0; i < view->lines; i++) {
3543                 struct blame *blame = view->line[i].data;
3545                 if (!blame->commit)
3546                         continue;
3548                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3549                         return blame->commit;
3550         }
3552         {
3553                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3555                 if (commit)
3556                         string_ncopy(commit->id, id, SIZEOF_REV);
3557                 return commit;
3558         }
3561 static bool
3562 parse_number(char **posref, size_t *number, size_t min, size_t max)
3564         char *pos = *posref;
3566         *posref = NULL;
3567         pos = strchr(pos + 1, ' ');
3568         if (!pos || !isdigit(pos[1]))
3569                 return FALSE;
3570         *number = atoi(pos + 1);
3571         if (*number < min || *number > max)
3572                 return FALSE;
3574         *posref = pos;
3575         return TRUE;
3578 static struct blame_commit *
3579 parse_blame_commit(struct view *view, char *text, int *blamed)
3581         struct blame_commit *commit;
3582         struct blame *blame;
3583         char *pos = text + SIZEOF_REV - 1;
3584         size_t lineno;
3585         size_t group;
3587         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3588                 return NULL;
3590         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3591             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3592                 return NULL;
3594         commit = get_blame_commit(view, text);
3595         if (!commit)
3596                 return NULL;
3598         *blamed += group;
3599         while (group--) {
3600                 struct line *line = &view->line[lineno + group - 1];
3602                 blame = line->data;
3603                 blame->commit = commit;
3604                 blame->header = !group;
3605                 line->dirty = 1;
3606         }
3608         return commit;
3611 static bool
3612 blame_read_file(struct view *view, char *line)
3614         if (!line) {
3615                 FILE *pipe = NULL;
3617                 if (view->lines > 0)
3618                         pipe = popen(view->cmd, "r");
3619                 else if (!view->parent)
3620                         die("No blame exist for %s", view->vid);
3621                 view->cmd[0] = 0;
3622                 if (!pipe) {
3623                         report("Failed to load blame data");
3624                         return TRUE;
3625                 }
3627                 fclose(view->pipe);
3628                 view->pipe = pipe;
3629                 return FALSE;
3631         } else {
3632                 size_t linelen = strlen(line);
3633                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3635                 blame->commit = NULL;
3636                 strncpy(blame->text, line, linelen);
3637                 blame->text[linelen] = 0;
3638                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3639         }
3642 static bool
3643 match_blame_header(const char *name, char **line)
3645         size_t namelen = strlen(name);
3646         bool matched = !strncmp(name, *line, namelen);
3648         if (matched)
3649                 *line += namelen;
3651         return matched;
3654 static bool
3655 blame_read(struct view *view, char *line)
3657         static struct blame_commit *commit = NULL;
3658         static int blamed = 0;
3659         static time_t author_time;
3661         if (*view->cmd)
3662                 return blame_read_file(view, line);
3664         if (!line) {
3665                 /* Reset all! */
3666                 commit = NULL;
3667                 blamed = 0;
3668                 string_format(view->ref, "%s", view->vid);
3669                 if (view_is_displayed(view)) {
3670                         update_view_title(view);
3671                         redraw_view_from(view, 0);
3672                 }
3673                 return TRUE;
3674         }
3676         if (!commit) {
3677                 commit = parse_blame_commit(view, line, &blamed);
3678                 string_format(view->ref, "%s %2d%%", view->vid,
3679                               blamed * 100 / view->lines);
3681         } else if (match_blame_header("author ", &line)) {
3682                 string_ncopy(commit->author, line, strlen(line));
3684         } else if (match_blame_header("author-time ", &line)) {
3685                 author_time = (time_t) atol(line);
3687         } else if (match_blame_header("author-tz ", &line)) {
3688                 long tz;
3690                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3691                 tz += ('0' - line[2]) * 60 * 60;
3692                 tz += ('0' - line[3]) * 60;
3693                 tz += ('0' - line[4]) * 60;
3695                 if (line[0] == '-')
3696                         tz = -tz;
3698                 author_time -= tz;
3699                 gmtime_r(&author_time, &commit->time);
3701         } else if (match_blame_header("summary ", &line)) {
3702                 string_ncopy(commit->title, line, strlen(line));
3704         } else if (match_blame_header("filename ", &line)) {
3705                 string_ncopy(commit->filename, line, strlen(line));
3706                 commit = NULL;
3707         }
3709         return TRUE;
3712 static bool
3713 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3715         struct blame *blame = line->data;
3716         struct tm *time = NULL;
3717         char *id = NULL, *author = NULL;
3719         if (blame->commit && *blame->commit->filename) {
3720                 id = blame->commit->id;
3721                 author = blame->commit->author;
3722                 time = &blame->commit->time;
3723         }
3725         if (opt_date && draw_date(view, time))
3726                 return TRUE;
3728         if (opt_author &&
3729             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3730                 return TRUE;
3732         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3733                 return TRUE;
3735         if (draw_lineno(view, lineno))
3736                 return TRUE;
3738         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3739         return TRUE;
3742 static enum request
3743 blame_request(struct view *view, enum request request, struct line *line)
3745         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3746         struct blame *blame = line->data;
3748         switch (request) {
3749         case REQ_ENTER:
3750                 if (!blame->commit) {
3751                         report("No commit loaded yet");
3752                         break;
3753                 }
3755                 if (!strcmp(blame->commit->id, NULL_ID)) {
3756                         char path[SIZEOF_STR];
3758                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3759                                 break;
3760                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3761                 }
3763                 open_view(view, REQ_VIEW_DIFF, flags);
3764                 break;
3766         default:
3767                 return request;
3768         }
3770         return REQ_NONE;
3773 static bool
3774 blame_grep(struct view *view, struct line *line)
3776         struct blame *blame = line->data;
3777         struct blame_commit *commit = blame->commit;
3778         regmatch_t pmatch;
3780 #define MATCH(text, on)                                                 \
3781         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3783         if (commit) {
3784                 char buf[DATE_COLS + 1];
3786                 if (MATCH(commit->title, 1) ||
3787                     MATCH(commit->author, opt_author) ||
3788                     MATCH(commit->id, opt_date))
3789                         return TRUE;
3791                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3792                     MATCH(buf, 1))
3793                         return TRUE;
3794         }
3796         return MATCH(blame->text, 1);
3798 #undef MATCH
3801 static void
3802 blame_select(struct view *view, struct line *line)
3804         struct blame *blame = line->data;
3805         struct blame_commit *commit = blame->commit;
3807         if (!commit)
3808                 return;
3810         if (!strcmp(commit->id, NULL_ID))
3811                 string_ncopy(ref_commit, "HEAD", 4);
3812         else
3813                 string_copy_rev(ref_commit, commit->id);
3816 static struct view_ops blame_ops = {
3817         "line",
3818         blame_open,
3819         blame_read,
3820         blame_draw,
3821         blame_request,
3822         blame_grep,
3823         blame_select,
3824 };
3826 /*
3827  * Status backend
3828  */
3830 struct status {
3831         char status;
3832         struct {
3833                 mode_t mode;
3834                 char rev[SIZEOF_REV];
3835                 char name[SIZEOF_STR];
3836         } old;
3837         struct {
3838                 mode_t mode;
3839                 char rev[SIZEOF_REV];
3840                 char name[SIZEOF_STR];
3841         } new;
3842 };
3844 static char status_onbranch[SIZEOF_STR];
3845 static struct status stage_status;
3846 static enum line_type stage_line_type;
3847 static size_t stage_chunks;
3848 static int *stage_chunk;
3850 /* This should work even for the "On branch" line. */
3851 static inline bool
3852 status_has_none(struct view *view, struct line *line)
3854         return line < view->line + view->lines && !line[1].data;
3857 /* Get fields from the diff line:
3858  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3859  */
3860 static inline bool
3861 status_get_diff(struct status *file, char *buf, size_t bufsize)
3863         char *old_mode = buf +  1;
3864         char *new_mode = buf +  8;
3865         char *old_rev  = buf + 15;
3866         char *new_rev  = buf + 56;
3867         char *status   = buf + 97;
3869         if (bufsize < 99 ||
3870             old_mode[-1] != ':' ||
3871             new_mode[-1] != ' ' ||
3872             old_rev[-1]  != ' ' ||
3873             new_rev[-1]  != ' ' ||
3874             status[-1]   != ' ')
3875                 return FALSE;
3877         file->status = *status;
3879         string_copy_rev(file->old.rev, old_rev);
3880         string_copy_rev(file->new.rev, new_rev);
3882         file->old.mode = strtoul(old_mode, NULL, 8);
3883         file->new.mode = strtoul(new_mode, NULL, 8);
3885         file->old.name[0] = file->new.name[0] = 0;
3887         return TRUE;
3890 static bool
3891 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3893         struct status *file = NULL;
3894         struct status *unmerged = NULL;
3895         char buf[SIZEOF_STR * 4];
3896         size_t bufsize = 0;
3897         FILE *pipe;
3899         pipe = popen(cmd, "r");
3900         if (!pipe)
3901                 return FALSE;
3903         add_line_data(view, NULL, type);
3905         while (!feof(pipe) && !ferror(pipe)) {
3906                 char *sep;
3907                 size_t readsize;
3909                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3910                 if (!readsize)
3911                         break;
3912                 bufsize += readsize;
3914                 /* Process while we have NUL chars. */
3915                 while ((sep = memchr(buf, 0, bufsize))) {
3916                         size_t sepsize = sep - buf + 1;
3918                         if (!file) {
3919                                 if (!realloc_lines(view, view->line_size + 1))
3920                                         goto error_out;
3922                                 file = calloc(1, sizeof(*file));
3923                                 if (!file)
3924                                         goto error_out;
3926                                 add_line_data(view, file, type);
3927                         }
3929                         /* Parse diff info part. */
3930                         if (status) {
3931                                 file->status = status;
3932                                 if (status == 'A')
3933                                         string_copy(file->old.rev, NULL_ID);
3935                         } else if (!file->status) {
3936                                 if (!status_get_diff(file, buf, sepsize))
3937                                         goto error_out;
3939                                 bufsize -= sepsize;
3940                                 memmove(buf, sep + 1, bufsize);
3942                                 sep = memchr(buf, 0, bufsize);
3943                                 if (!sep)
3944                                         break;
3945                                 sepsize = sep - buf + 1;
3947                                 /* Collapse all 'M'odified entries that
3948                                  * follow a associated 'U'nmerged entry.
3949                                  */
3950                                 if (file->status == 'U') {
3951                                         unmerged = file;
3953                                 } else if (unmerged) {
3954                                         int collapse = !strcmp(buf, unmerged->new.name);
3956                                         unmerged = NULL;
3957                                         if (collapse) {
3958                                                 free(file);
3959                                                 view->lines--;
3960                                                 continue;
3961                                         }
3962                                 }
3963                         }
3965                         /* Grab the old name for rename/copy. */
3966                         if (!*file->old.name &&
3967                             (file->status == 'R' || file->status == 'C')) {
3968                                 sepsize = sep - buf + 1;
3969                                 string_ncopy(file->old.name, buf, sepsize);
3970                                 bufsize -= sepsize;
3971                                 memmove(buf, sep + 1, bufsize);
3973                                 sep = memchr(buf, 0, bufsize);
3974                                 if (!sep)
3975                                         break;
3976                                 sepsize = sep - buf + 1;
3977                         }
3979                         /* git-ls-files just delivers a NUL separated
3980                          * list of file names similar to the second half
3981                          * of the git-diff-* output. */
3982                         string_ncopy(file->new.name, buf, sepsize);
3983                         if (!*file->old.name)
3984                                 string_copy(file->old.name, file->new.name);
3985                         bufsize -= sepsize;
3986                         memmove(buf, sep + 1, bufsize);
3987                         file = NULL;
3988                 }
3989         }
3991         if (ferror(pipe)) {
3992 error_out:
3993                 pclose(pipe);
3994                 return FALSE;
3995         }
3997         if (!view->line[view->lines - 1].data)
3998                 add_line_data(view, NULL, LINE_STAT_NONE);
4000         pclose(pipe);
4001         return TRUE;
4004 /* Don't show unmerged entries in the staged section. */
4005 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4006 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4007 #define STATUS_LIST_OTHER_CMD \
4008         "git ls-files -z --others --exclude-standard"
4009 #define STATUS_LIST_NO_HEAD_CMD \
4010         "git ls-files -z --cached --exclude-standard"
4012 #define STATUS_DIFF_INDEX_SHOW_CMD \
4013         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4015 #define STATUS_DIFF_FILES_SHOW_CMD \
4016         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4018 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4019         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4021 /* First parse staged info using git-diff-index(1), then parse unstaged
4022  * info using git-diff-files(1), and finally untracked files using
4023  * git-ls-files(1). */
4024 static bool
4025 status_open(struct view *view)
4027         unsigned long prev_lineno = view->lineno;
4028         size_t i;
4030         for (i = 0; i < view->lines; i++)
4031                 free(view->line[i].data);
4032         free(view->line);
4033         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
4034         view->line = NULL;
4036         if (!realloc_lines(view, view->line_size + 7))
4037                 return FALSE;
4039         add_line_data(view, NULL, LINE_STAT_HEAD);
4040         if (opt_no_head)
4041                 string_copy(status_onbranch, "Initial commit");
4042         else if (!*opt_head)
4043                 string_copy(status_onbranch, "Not currently on any branch");
4044         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4045                 return FALSE;
4047         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4049         if (opt_no_head) {
4050                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4051                         return FALSE;
4052         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4053                 return FALSE;
4054         }
4056         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4057             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4058                 return FALSE;
4060         /* If all went well restore the previous line number to stay in
4061          * the context or select a line with something that can be
4062          * updated. */
4063         if (prev_lineno >= view->lines)
4064                 prev_lineno = view->lines - 1;
4065         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4066                 prev_lineno++;
4067         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4068                 prev_lineno--;
4070         /* If the above fails, always skip the "On branch" line. */
4071         if (prev_lineno < view->lines)
4072                 view->lineno = prev_lineno;
4073         else
4074                 view->lineno = 1;
4076         if (view->lineno < view->offset)
4077                 view->offset = view->lineno;
4078         else if (view->offset + view->height <= view->lineno)
4079                 view->offset = view->lineno - view->height + 1;
4081         return TRUE;
4084 static bool
4085 status_draw(struct view *view, struct line *line, unsigned int lineno)
4087         struct status *status = line->data;
4088         enum line_type type;
4089         char *text;
4091         if (!status) {
4092                 switch (line->type) {
4093                 case LINE_STAT_STAGED:
4094                         type = LINE_STAT_SECTION;
4095                         text = "Changes to be committed:";
4096                         break;
4098                 case LINE_STAT_UNSTAGED:
4099                         type = LINE_STAT_SECTION;
4100                         text = "Changed but not updated:";
4101                         break;
4103                 case LINE_STAT_UNTRACKED:
4104                         type = LINE_STAT_SECTION;
4105                         text = "Untracked files:";
4106                         break;
4108                 case LINE_STAT_NONE:
4109                         type = LINE_DEFAULT;
4110                         text = "    (no files)";
4111                         break;
4113                 case LINE_STAT_HEAD:
4114                         type = LINE_STAT_HEAD;
4115                         text = status_onbranch;
4116                         break;
4118                 default:
4119                         return FALSE;
4120                 }
4121         } else {
4122                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4124                 buf[0] = status->status;
4125                 if (draw_text(view, line->type, buf, TRUE))
4126                         return TRUE;
4127                 type = LINE_DEFAULT;
4128                 text = status->new.name;
4129         }
4131         draw_text(view, type, text, TRUE);
4132         return TRUE;
4135 static enum request
4136 status_enter(struct view *view, struct line *line)
4138         struct status *status = line->data;
4139         char oldpath[SIZEOF_STR] = "";
4140         char newpath[SIZEOF_STR] = "";
4141         char *info;
4142         size_t cmdsize = 0;
4143         enum open_flags split;
4145         if (line->type == LINE_STAT_NONE ||
4146             (!status && line[1].type == LINE_STAT_NONE)) {
4147                 report("No file to diff");
4148                 return REQ_NONE;
4149         }
4151         if (status) {
4152                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4153                         return REQ_QUIT;
4154                 /* Diffs for unmerged entries are empty when pasing the
4155                  * new path, so leave it empty. */
4156                 if (status->status != 'U' &&
4157                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4158                         return REQ_QUIT;
4159         }
4161         if (opt_cdup[0] &&
4162             line->type != LINE_STAT_UNTRACKED &&
4163             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4164                 return REQ_QUIT;
4166         switch (line->type) {
4167         case LINE_STAT_STAGED:
4168                 if (opt_no_head) {
4169                         if (!string_format_from(opt_cmd, &cmdsize,
4170                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4171                                                 newpath))
4172                                 return REQ_QUIT;
4173                 } else {
4174                         if (!string_format_from(opt_cmd, &cmdsize,
4175                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4176                                                 oldpath, newpath))
4177                                 return REQ_QUIT;
4178                 }
4180                 if (status)
4181                         info = "Staged changes to %s";
4182                 else
4183                         info = "Staged changes";
4184                 break;
4186         case LINE_STAT_UNSTAGED:
4187                 if (!string_format_from(opt_cmd, &cmdsize,
4188                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4189                         return REQ_QUIT;
4190                 if (status)
4191                         info = "Unstaged changes to %s";
4192                 else
4193                         info = "Unstaged changes";
4194                 break;
4196         case LINE_STAT_UNTRACKED:
4197                 if (opt_pipe)
4198                         return REQ_QUIT;
4200                 if (!status) {
4201                         report("No file to show");
4202                         return REQ_NONE;
4203                 }
4205                 opt_pipe = fopen(status->new.name, "r");
4206                 info = "Untracked file %s";
4207                 break;
4209         case LINE_STAT_HEAD:
4210                 return REQ_NONE;
4212         default:
4213                 die("line type %d not handled in switch", line->type);
4214         }
4216         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4217         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4218         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4219                 if (status) {
4220                         stage_status = *status;
4221                 } else {
4222                         memset(&stage_status, 0, sizeof(stage_status));
4223                 }
4225                 stage_line_type = line->type;
4226                 stage_chunks = 0;
4227                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4228         }
4230         return REQ_NONE;
4233 static bool
4234 status_exists(struct status *status, enum line_type type)
4236         struct view *view = VIEW(REQ_VIEW_STATUS);
4237         struct line *line;
4239         for (line = view->line; line < view->line + view->lines; line++) {
4240                 struct status *pos = line->data;
4242                 if (line->type == type && pos &&
4243                     !strcmp(status->new.name, pos->new.name))
4244                         return TRUE;
4245         }
4247         return FALSE;
4251 static FILE *
4252 status_update_prepare(enum line_type type)
4254         char cmd[SIZEOF_STR];
4255         size_t cmdsize = 0;
4257         if (opt_cdup[0] &&
4258             type != LINE_STAT_UNTRACKED &&
4259             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4260                 return NULL;
4262         switch (type) {
4263         case LINE_STAT_STAGED:
4264                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4265                 break;
4267         case LINE_STAT_UNSTAGED:
4268         case LINE_STAT_UNTRACKED:
4269                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4270                 break;
4272         default:
4273                 die("line type %d not handled in switch", type);
4274         }
4276         return popen(cmd, "w");
4279 static bool
4280 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4282         char buf[SIZEOF_STR];
4283         size_t bufsize = 0;
4284         size_t written = 0;
4286         switch (type) {
4287         case LINE_STAT_STAGED:
4288                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4289                                         status->old.mode,
4290                                         status->old.rev,
4291                                         status->old.name, 0))
4292                         return FALSE;
4293                 break;
4295         case LINE_STAT_UNSTAGED:
4296         case LINE_STAT_UNTRACKED:
4297                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4298                         return FALSE;
4299                 break;
4301         default:
4302                 die("line type %d not handled in switch", type);
4303         }
4305         while (!ferror(pipe) && written < bufsize) {
4306                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4307         }
4309         return written == bufsize;
4312 static bool
4313 status_update_file(struct status *status, enum line_type type)
4315         FILE *pipe = status_update_prepare(type);
4316         bool result;
4318         if (!pipe)
4319                 return FALSE;
4321         result = status_update_write(pipe, status, type);
4322         pclose(pipe);
4323         return result;
4326 static bool
4327 status_update_files(struct view *view, struct line *line)
4329         FILE *pipe = status_update_prepare(line->type);
4330         bool result = TRUE;
4331         struct line *pos = view->line + view->lines;
4332         int files = 0;
4333         int file, done;
4335         if (!pipe)
4336                 return FALSE;
4338         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4339                 files++;
4341         for (file = 0, done = 0; result && file < files; line++, file++) {
4342                 int almost_done = file * 100 / files;
4344                 if (almost_done > done) {
4345                         done = almost_done;
4346                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4347                                       file, files, done);
4348                         update_view_title(view);
4349                 }
4350                 result = status_update_write(pipe, line->data, line->type);
4351         }
4353         pclose(pipe);
4354         return result;
4357 static bool
4358 status_update(struct view *view)
4360         struct line *line = &view->line[view->lineno];
4362         assert(view->lines);
4364         if (!line->data) {
4365                 /* This should work even for the "On branch" line. */
4366                 if (line < view->line + view->lines && !line[1].data) {
4367                         report("Nothing to update");
4368                         return FALSE;
4369                 }
4371                 if (!status_update_files(view, line + 1)) {
4372                         report("Failed to update file status");
4373                         return FALSE;
4374                 }
4376         } else if (!status_update_file(line->data, line->type)) {
4377                 report("Failed to update file status");
4378                 return FALSE;
4379         }
4381         return TRUE;
4384 static bool
4385 status_checkout(struct status *status, enum line_type type, bool has_none)
4387         if (!status || type != LINE_STAT_UNSTAGED) {
4388                 if (type == LINE_STAT_STAGED) {
4389                         report("Cannot checkout staged files");
4390                 } else if (type == LINE_STAT_UNTRACKED) {
4391                         report("Cannot checkout untracked files");
4392                 } else if (has_none) {
4393                         report("Nothing to checkout");
4394                 } else {
4395                         report("Cannot checkout multiple files");
4396                 }
4397                 return FALSE;
4399         } else {
4400                 char cmd[SIZEOF_STR];
4401                 char file_sq[SIZEOF_STR];
4403                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4404                     !string_format(cmd, "git checkout %s%s", opt_cdup, file_sq))
4405                         return FALSE;
4407                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4408         }
4411 static enum request
4412 status_request(struct view *view, enum request request, struct line *line)
4414         struct status *status = line->data;
4416         switch (request) {
4417         case REQ_STATUS_UPDATE:
4418                 if (!status_update(view))
4419                         return REQ_NONE;
4420                 break;
4422         case REQ_STATUS_CHECKOUT:
4423                 if (!status_checkout(status, line->type, status_has_none(view, line)))
4424                         return REQ_NONE;
4425                 break;
4427         case REQ_STATUS_MERGE:
4428                 if (!status || status->status != 'U') {
4429                         report("Merging only possible for files with unmerged status ('U').");
4430                         return REQ_NONE;
4431                 }
4432                 open_mergetool(status->new.name);
4433                 break;
4435         case REQ_EDIT:
4436                 if (!status)
4437                         return request;
4439                 open_editor(status->status != '?', status->new.name);
4440                 break;
4442         case REQ_VIEW_BLAME:
4443                 if (status) {
4444                         string_copy(opt_file, status->new.name);
4445                         opt_ref[0] = 0;
4446                 }
4447                 return request;
4449         case REQ_ENTER:
4450                 /* After returning the status view has been split to
4451                  * show the stage view. No further reloading is
4452                  * necessary. */
4453                 status_enter(view, line);
4454                 return REQ_NONE;
4456         case REQ_REFRESH:
4457                 /* Simply reload the view. */
4458                 break;
4460         default:
4461                 return request;
4462         }
4464         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4466         return REQ_NONE;
4469 static void
4470 status_select(struct view *view, struct line *line)
4472         struct status *status = line->data;
4473         char file[SIZEOF_STR] = "all files";
4474         char *text;
4475         char *key;
4477         if (status && !string_format(file, "'%s'", status->new.name))
4478                 return;
4480         if (!status && line[1].type == LINE_STAT_NONE)
4481                 line++;
4483         switch (line->type) {
4484         case LINE_STAT_STAGED:
4485                 text = "Press %s to unstage %s for commit";
4486                 break;
4488         case LINE_STAT_UNSTAGED:
4489                 text = "Press %s to stage %s for commit";
4490                 break;
4492         case LINE_STAT_UNTRACKED:
4493                 text = "Press %s to stage %s for addition";
4494                 break;
4496         case LINE_STAT_HEAD:
4497         case LINE_STAT_NONE:
4498                 text = "Nothing to update";
4499                 break;
4501         default:
4502                 die("line type %d not handled in switch", line->type);
4503         }
4505         if (status && status->status == 'U') {
4506                 text = "Press %s to resolve conflict in %s";
4507                 key = get_key(REQ_STATUS_MERGE);
4509         } else {
4510                 key = get_key(REQ_STATUS_UPDATE);
4511         }
4513         string_format(view->ref, text, key, file);
4516 static bool
4517 status_grep(struct view *view, struct line *line)
4519         struct status *status = line->data;
4520         enum { S_STATUS, S_NAME, S_END } state;
4521         char buf[2] = "?";
4522         regmatch_t pmatch;
4524         if (!status)
4525                 return FALSE;
4527         for (state = S_STATUS; state < S_END; state++) {
4528                 char *text;
4530                 switch (state) {
4531                 case S_NAME:    text = status->new.name;        break;
4532                 case S_STATUS:
4533                         buf[0] = status->status;
4534                         text = buf;
4535                         break;
4537                 default:
4538                         return FALSE;
4539                 }
4541                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4542                         return TRUE;
4543         }
4545         return FALSE;
4548 static struct view_ops status_ops = {
4549         "file",
4550         status_open,
4551         NULL,
4552         status_draw,
4553         status_request,
4554         status_grep,
4555         status_select,
4556 };
4559 static bool
4560 stage_diff_line(FILE *pipe, struct line *line)
4562         char *buf = line->data;
4563         size_t bufsize = strlen(buf);
4564         size_t written = 0;
4566         while (!ferror(pipe) && written < bufsize) {
4567                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4568         }
4570         fputc('\n', pipe);
4572         return written == bufsize;
4575 static bool
4576 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4578         while (line < end) {
4579                 if (!stage_diff_line(pipe, line++))
4580                         return FALSE;
4581                 if (line->type == LINE_DIFF_CHUNK ||
4582                     line->type == LINE_DIFF_HEADER)
4583                         break;
4584         }
4586         return TRUE;
4589 static struct line *
4590 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4592         for (; view->line < line; line--)
4593                 if (line->type == type)
4594                         return line;
4596         return NULL;
4599 static bool
4600 stage_update_chunk(struct view *view, struct line *chunk)
4602         char cmd[SIZEOF_STR];
4603         size_t cmdsize = 0;
4604         struct line *diff_hdr;
4605         FILE *pipe;
4607         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4608         if (!diff_hdr)
4609                 return FALSE;
4611         if (opt_cdup[0] &&
4612             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4613                 return FALSE;
4615         if (!string_format_from(cmd, &cmdsize,
4616                                 "git apply --whitespace=nowarn --cached %s - && "
4617                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4618                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4619                 return FALSE;
4621         pipe = popen(cmd, "w");
4622         if (!pipe)
4623                 return FALSE;
4625         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4626             !stage_diff_write(pipe, chunk, view->line + view->lines))
4627                 chunk = NULL;
4629         pclose(pipe);
4631         return chunk ? TRUE : FALSE;
4634 static bool
4635 stage_update(struct view *view, struct line *line)
4637         struct line *chunk = NULL;
4639         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4640                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4642         if (chunk) {
4643                 if (!stage_update_chunk(view, chunk)) {
4644                         report("Failed to apply chunk");
4645                         return FALSE;
4646                 }
4648         } else if (!stage_status.status) {
4649                 view = VIEW(REQ_VIEW_STATUS);
4651                 for (line = view->line; line < view->line + view->lines; line++)
4652                         if (line->type == stage_line_type)
4653                                 break;
4655                 if (!status_update_files(view, line + 1)) {
4656                         report("Failed to update files");
4657                         return FALSE;
4658                 }
4660         } else if (!status_update_file(&stage_status, stage_line_type)) {
4661                 report("Failed to update file");
4662                 return FALSE;
4663         }
4665         return TRUE;
4668 static void
4669 stage_next(struct view *view, struct line *line)
4671         int i;
4673         if (!stage_chunks) {
4674                 static size_t alloc = 0;
4675                 int *tmp;
4677                 for (line = view->line; line < view->line + view->lines; line++) {
4678                         if (line->type != LINE_DIFF_CHUNK)
4679                                 continue;
4681                         tmp = realloc_items(stage_chunk, &alloc,
4682                                             stage_chunks, sizeof(*tmp));
4683                         if (!tmp) {
4684                                 report("Allocation failure");
4685                                 return;
4686                         }
4688                         stage_chunk = tmp;
4689                         stage_chunk[stage_chunks++] = line - view->line;
4690                 }
4691         }
4693         for (i = 0; i < stage_chunks; i++) {
4694                 if (stage_chunk[i] > view->lineno) {
4695                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4696                         report("Chunk %d of %d", i + 1, stage_chunks);
4697                         return;
4698                 }
4699         }
4701         report("No next chunk found");
4704 static enum request
4705 stage_request(struct view *view, enum request request, struct line *line)
4707         switch (request) {
4708         case REQ_STATUS_UPDATE:
4709                 if (!stage_update(view, line))
4710                         return REQ_NONE;
4711                 break;
4713         case REQ_STATUS_CHECKOUT:
4714                 if (!status_checkout(stage_status.status ? &stage_status : NULL,
4715                                      stage_line_type, FALSE))
4716                         return REQ_NONE;
4717                 break;
4719         case REQ_STAGE_NEXT:
4720                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4721                         report("File is untracked; press %s to add",
4722                                get_key(REQ_STATUS_UPDATE));
4723                         return REQ_NONE;
4724                 }
4725                 stage_next(view, line);
4726                 return REQ_NONE;
4728         case REQ_EDIT:
4729                 if (!stage_status.new.name[0])
4730                         return request;
4732                 open_editor(stage_status.status != '?', stage_status.new.name);
4733                 break;
4735         case REQ_REFRESH:
4736                 /* Reload everything ... */
4737                 break;
4739         case REQ_VIEW_BLAME:
4740                 if (stage_status.new.name[0]) {
4741                         string_copy(opt_file, stage_status.new.name);
4742                         opt_ref[0] = 0;
4743                 }
4744                 return request;
4746         case REQ_ENTER:
4747                 return pager_request(view, request, line);
4749         default:
4750                 return request;
4751         }
4753         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4755         /* Check whether the staged entry still exists, and close the
4756          * stage view if it doesn't. */
4757         if (!status_exists(&stage_status, stage_line_type))
4758                 return REQ_VIEW_CLOSE;
4760         if (stage_line_type == LINE_STAT_UNTRACKED)
4761                 opt_pipe = fopen(stage_status.new.name, "r");
4762         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4764         return REQ_NONE;
4767 static struct view_ops stage_ops = {
4768         "line",
4769         NULL,
4770         pager_read,
4771         pager_draw,
4772         stage_request,
4773         pager_grep,
4774         pager_select,
4775 };
4778 /*
4779  * Revision graph
4780  */
4782 struct commit {
4783         char id[SIZEOF_REV];            /* SHA1 ID. */
4784         char title[128];                /* First line of the commit message. */
4785         char author[75];                /* Author of the commit. */
4786         struct tm time;                 /* Date from the author ident. */
4787         struct ref **refs;              /* Repository references. */
4788         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4789         size_t graph_size;              /* The width of the graph array. */
4790         bool has_parents;               /* Rewritten --parents seen. */
4791 };
4793 /* Size of rev graph with no  "padding" columns */
4794 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4796 struct rev_graph {
4797         struct rev_graph *prev, *next, *parents;
4798         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4799         size_t size;
4800         struct commit *commit;
4801         size_t pos;
4802         unsigned int boundary:1;
4803 };
4805 /* Parents of the commit being visualized. */
4806 static struct rev_graph graph_parents[4];
4808 /* The current stack of revisions on the graph. */
4809 static struct rev_graph graph_stacks[4] = {
4810         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4811         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4812         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4813         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4814 };
4816 static inline bool
4817 graph_parent_is_merge(struct rev_graph *graph)
4819         return graph->parents->size > 1;
4822 static inline void
4823 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4825         struct commit *commit = graph->commit;
4827         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4828                 commit->graph[commit->graph_size++] = symbol;
4831 static void
4832 clear_rev_graph(struct rev_graph *graph)
4834         graph->boundary = 0;
4835         graph->size = graph->pos = 0;
4836         graph->commit = NULL;
4837         memset(graph->parents, 0, sizeof(*graph->parents));
4840 static void
4841 done_rev_graph(struct rev_graph *graph)
4843         if (graph_parent_is_merge(graph) &&
4844             graph->pos < graph->size - 1 &&
4845             graph->next->size == graph->size + graph->parents->size - 1) {
4846                 size_t i = graph->pos + graph->parents->size - 1;
4848                 graph->commit->graph_size = i * 2;
4849                 while (i < graph->next->size - 1) {
4850                         append_to_rev_graph(graph, ' ');
4851                         append_to_rev_graph(graph, '\\');
4852                         i++;
4853                 }
4854         }
4856         clear_rev_graph(graph);
4859 static void
4860 push_rev_graph(struct rev_graph *graph, char *parent)
4862         int i;
4864         /* "Collapse" duplicate parents lines.
4865          *
4866          * FIXME: This needs to also update update the drawn graph but
4867          * for now it just serves as a method for pruning graph lines. */
4868         for (i = 0; i < graph->size; i++)
4869                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4870                         return;
4872         if (graph->size < SIZEOF_REVITEMS) {
4873                 string_copy_rev(graph->rev[graph->size++], parent);
4874         }
4877 static chtype
4878 get_rev_graph_symbol(struct rev_graph *graph)
4880         chtype symbol;
4882         if (graph->boundary)
4883                 symbol = REVGRAPH_BOUND;
4884         else if (graph->parents->size == 0)
4885                 symbol = REVGRAPH_INIT;
4886         else if (graph_parent_is_merge(graph))
4887                 symbol = REVGRAPH_MERGE;
4888         else if (graph->pos >= graph->size)
4889                 symbol = REVGRAPH_BRANCH;
4890         else
4891                 symbol = REVGRAPH_COMMIT;
4893         return symbol;
4896 static void
4897 draw_rev_graph(struct rev_graph *graph)
4899         struct rev_filler {
4900                 chtype separator, line;
4901         };
4902         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4903         static struct rev_filler fillers[] = {
4904                 { ' ',  '|' },
4905                 { '`',  '.' },
4906                 { '\'', ' ' },
4907                 { '/',  ' ' },
4908         };
4909         chtype symbol = get_rev_graph_symbol(graph);
4910         struct rev_filler *filler;
4911         size_t i;
4913         if (opt_line_graphics)
4914                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4916         filler = &fillers[DEFAULT];
4918         for (i = 0; i < graph->pos; i++) {
4919                 append_to_rev_graph(graph, filler->line);
4920                 if (graph_parent_is_merge(graph->prev) &&
4921                     graph->prev->pos == i)
4922                         filler = &fillers[RSHARP];
4924                 append_to_rev_graph(graph, filler->separator);
4925         }
4927         /* Place the symbol for this revision. */
4928         append_to_rev_graph(graph, symbol);
4930         if (graph->prev->size > graph->size)
4931                 filler = &fillers[RDIAG];
4932         else
4933                 filler = &fillers[DEFAULT];
4935         i++;
4937         for (; i < graph->size; i++) {
4938                 append_to_rev_graph(graph, filler->separator);
4939                 append_to_rev_graph(graph, filler->line);
4940                 if (graph_parent_is_merge(graph->prev) &&
4941                     i < graph->prev->pos + graph->parents->size)
4942                         filler = &fillers[RSHARP];
4943                 if (graph->prev->size > graph->size)
4944                         filler = &fillers[LDIAG];
4945         }
4947         if (graph->prev->size > graph->size) {
4948                 append_to_rev_graph(graph, filler->separator);
4949                 if (filler->line != ' ')
4950                         append_to_rev_graph(graph, filler->line);
4951         }
4954 /* Prepare the next rev graph */
4955 static void
4956 prepare_rev_graph(struct rev_graph *graph)
4958         size_t i;
4960         /* First, traverse all lines of revisions up to the active one. */
4961         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4962                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4963                         break;
4965                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4966         }
4968         /* Interleave the new revision parent(s). */
4969         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4970                 push_rev_graph(graph->next, graph->parents->rev[i]);
4972         /* Lastly, put any remaining revisions. */
4973         for (i = graph->pos + 1; i < graph->size; i++)
4974                 push_rev_graph(graph->next, graph->rev[i]);
4977 static void
4978 update_rev_graph(struct rev_graph *graph)
4980         /* If this is the finalizing update ... */
4981         if (graph->commit)
4982                 prepare_rev_graph(graph);
4984         /* Graph visualization needs a one rev look-ahead,
4985          * so the first update doesn't visualize anything. */
4986         if (!graph->prev->commit)
4987                 return;
4989         draw_rev_graph(graph->prev);
4990         done_rev_graph(graph->prev->prev);
4994 /*
4995  * Main view backend
4996  */
4998 static bool
4999 main_draw(struct view *view, struct line *line, unsigned int lineno)
5001         struct commit *commit = line->data;
5003         if (!*commit->author)
5004                 return FALSE;
5006         if (opt_date && draw_date(view, &commit->time))
5007                 return TRUE;
5009         if (opt_author &&
5010             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5011                 return TRUE;
5013         if (opt_rev_graph && commit->graph_size &&
5014             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5015                 return TRUE;
5017         if (opt_show_refs && commit->refs) {
5018                 size_t i = 0;
5020                 do {
5021                         enum line_type type;
5023                         if (commit->refs[i]->head)
5024                                 type = LINE_MAIN_HEAD;
5025                         else if (commit->refs[i]->ltag)
5026                                 type = LINE_MAIN_LOCAL_TAG;
5027                         else if (commit->refs[i]->tag)
5028                                 type = LINE_MAIN_TAG;
5029                         else if (commit->refs[i]->tracked)
5030                                 type = LINE_MAIN_TRACKED;
5031                         else if (commit->refs[i]->remote)
5032                                 type = LINE_MAIN_REMOTE;
5033                         else
5034                                 type = LINE_MAIN_REF;
5036                         if (draw_text(view, type, "[", TRUE) ||
5037                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5038                             draw_text(view, type, "]", TRUE))
5039                                 return TRUE;
5041                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5042                                 return TRUE;
5043                 } while (commit->refs[i++]->next);
5044         }
5046         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5047         return TRUE;
5050 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5051 static bool
5052 main_read(struct view *view, char *line)
5054         static struct rev_graph *graph = graph_stacks;
5055         enum line_type type;
5056         struct commit *commit;
5058         if (!line) {
5059                 int i;
5061                 if (!view->lines && !view->parent)
5062                         die("No revisions match the given arguments.");
5063                 if (view->lines > 0) {
5064                         commit = view->line[view->lines - 1].data;
5065                         if (!*commit->author) {
5066                                 view->lines--;
5067                                 free(commit);
5068                                 graph->commit = NULL;
5069                         }
5070                 }
5071                 update_rev_graph(graph);
5073                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5074                         clear_rev_graph(&graph_stacks[i]);
5075                 return TRUE;
5076         }
5078         type = get_line_type(line);
5079         if (type == LINE_COMMIT) {
5080                 commit = calloc(1, sizeof(struct commit));
5081                 if (!commit)
5082                         return FALSE;
5084                 line += STRING_SIZE("commit ");
5085                 if (*line == '-') {
5086                         graph->boundary = 1;
5087                         line++;
5088                 }
5090                 string_copy_rev(commit->id, line);
5091                 commit->refs = get_refs(commit->id);
5092                 graph->commit = commit;
5093                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5095                 while ((line = strchr(line, ' '))) {
5096                         line++;
5097                         push_rev_graph(graph->parents, line);
5098                         commit->has_parents = TRUE;
5099                 }
5100                 return TRUE;
5101         }
5103         if (!view->lines)
5104                 return TRUE;
5105         commit = view->line[view->lines - 1].data;
5107         switch (type) {
5108         case LINE_PARENT:
5109                 if (commit->has_parents)
5110                         break;
5111                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5112                 break;
5114         case LINE_AUTHOR:
5115         {
5116                 /* Parse author lines where the name may be empty:
5117                  *      author  <email@address.tld> 1138474660 +0100
5118                  */
5119                 char *ident = line + STRING_SIZE("author ");
5120                 char *nameend = strchr(ident, '<');
5121                 char *emailend = strchr(ident, '>');
5123                 if (!nameend || !emailend)
5124                         break;
5126                 update_rev_graph(graph);
5127                 graph = graph->next;
5129                 *nameend = *emailend = 0;
5130                 ident = chomp_string(ident);
5131                 if (!*ident) {
5132                         ident = chomp_string(nameend + 1);
5133                         if (!*ident)
5134                                 ident = "Unknown";
5135                 }
5137                 string_ncopy(commit->author, ident, strlen(ident));
5139                 /* Parse epoch and timezone */
5140                 if (emailend[1] == ' ') {
5141                         char *secs = emailend + 2;
5142                         char *zone = strchr(secs, ' ');
5143                         time_t time = (time_t) atol(secs);
5145                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5146                                 long tz;
5148                                 zone++;
5149                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5150                                 tz += ('0' - zone[2]) * 60 * 60;
5151                                 tz += ('0' - zone[3]) * 60;
5152                                 tz += ('0' - zone[4]) * 60;
5154                                 if (zone[0] == '-')
5155                                         tz = -tz;
5157                                 time -= tz;
5158                         }
5160                         gmtime_r(&time, &commit->time);
5161                 }
5162                 break;
5163         }
5164         default:
5165                 /* Fill in the commit title if it has not already been set. */
5166                 if (commit->title[0])
5167                         break;
5169                 /* Require titles to start with a non-space character at the
5170                  * offset used by git log. */
5171                 if (strncmp(line, "    ", 4))
5172                         break;
5173                 line += 4;
5174                 /* Well, if the title starts with a whitespace character,
5175                  * try to be forgiving.  Otherwise we end up with no title. */
5176                 while (isspace(*line))
5177                         line++;
5178                 if (*line == '\0')
5179                         break;
5180                 /* FIXME: More graceful handling of titles; append "..." to
5181                  * shortened titles, etc. */
5183                 string_ncopy(commit->title, line, strlen(line));
5184         }
5186         return TRUE;
5189 static enum request
5190 main_request(struct view *view, enum request request, struct line *line)
5192         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5194         switch (request) {
5195         case REQ_ENTER:
5196                 open_view(view, REQ_VIEW_DIFF, flags);
5197                 break;
5198         case REQ_REFRESH:
5199                 load_refs();
5200                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5201                 break;
5202         default:
5203                 return request;
5204         }
5206         return REQ_NONE;
5209 static bool
5210 grep_refs(struct ref **refs, regex_t *regex)
5212         regmatch_t pmatch;
5213         size_t i = 0;
5215         if (!refs)
5216                 return FALSE;
5217         do {
5218                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5219                         return TRUE;
5220         } while (refs[i++]->next);
5222         return FALSE;
5225 static bool
5226 main_grep(struct view *view, struct line *line)
5228         struct commit *commit = line->data;
5229         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5230         char buf[DATE_COLS + 1];
5231         regmatch_t pmatch;
5233         for (state = S_TITLE; state < S_END; state++) {
5234                 char *text;
5236                 switch (state) {
5237                 case S_TITLE:   text = commit->title;   break;
5238                 case S_AUTHOR:
5239                         if (!opt_author)
5240                                 continue;
5241                         text = commit->author;
5242                         break;
5243                 case S_DATE:
5244                         if (!opt_date)
5245                                 continue;
5246                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5247                                 continue;
5248                         text = buf;
5249                         break;
5250                 case S_REFS:
5251                         if (!opt_show_refs)
5252                                 continue;
5253                         if (grep_refs(commit->refs, view->regex) == TRUE)
5254                                 return TRUE;
5255                         continue;
5256                 default:
5257                         return FALSE;
5258                 }
5260                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5261                         return TRUE;
5262         }
5264         return FALSE;
5267 static void
5268 main_select(struct view *view, struct line *line)
5270         struct commit *commit = line->data;
5272         string_copy_rev(view->ref, commit->id);
5273         string_copy_rev(ref_commit, view->ref);
5276 static struct view_ops main_ops = {
5277         "commit",
5278         NULL,
5279         main_read,
5280         main_draw,
5281         main_request,
5282         main_grep,
5283         main_select,
5284 };
5287 /*
5288  * Unicode / UTF-8 handling
5289  *
5290  * NOTE: Much of the following code for dealing with unicode is derived from
5291  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5292  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5293  */
5295 /* I've (over)annotated a lot of code snippets because I am not entirely
5296  * confident that the approach taken by this small UTF-8 interface is correct.
5297  * --jonas */
5299 static inline int
5300 unicode_width(unsigned long c)
5302         if (c >= 0x1100 &&
5303            (c <= 0x115f                         /* Hangul Jamo */
5304             || c == 0x2329
5305             || c == 0x232a
5306             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5307                                                 /* CJK ... Yi */
5308             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5309             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5310             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5311             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5312             || (c >= 0xffe0  && c <= 0xffe6)
5313             || (c >= 0x20000 && c <= 0x2fffd)
5314             || (c >= 0x30000 && c <= 0x3fffd)))
5315                 return 2;
5317         if (c == '\t')
5318                 return opt_tab_size;
5320         return 1;
5323 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5324  * Illegal bytes are set one. */
5325 static const unsigned char utf8_bytes[256] = {
5326         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,
5327         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,
5328         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,
5329         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,
5330         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,
5331         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,
5332         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,
5333         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,
5334 };
5336 /* Decode UTF-8 multi-byte representation into a unicode character. */
5337 static inline unsigned long
5338 utf8_to_unicode(const char *string, size_t length)
5340         unsigned long unicode;
5342         switch (length) {
5343         case 1:
5344                 unicode  =   string[0];
5345                 break;
5346         case 2:
5347                 unicode  =  (string[0] & 0x1f) << 6;
5348                 unicode +=  (string[1] & 0x3f);
5349                 break;
5350         case 3:
5351                 unicode  =  (string[0] & 0x0f) << 12;
5352                 unicode += ((string[1] & 0x3f) << 6);
5353                 unicode +=  (string[2] & 0x3f);
5354                 break;
5355         case 4:
5356                 unicode  =  (string[0] & 0x0f) << 18;
5357                 unicode += ((string[1] & 0x3f) << 12);
5358                 unicode += ((string[2] & 0x3f) << 6);
5359                 unicode +=  (string[3] & 0x3f);
5360                 break;
5361         case 5:
5362                 unicode  =  (string[0] & 0x0f) << 24;
5363                 unicode += ((string[1] & 0x3f) << 18);
5364                 unicode += ((string[2] & 0x3f) << 12);
5365                 unicode += ((string[3] & 0x3f) << 6);
5366                 unicode +=  (string[4] & 0x3f);
5367                 break;
5368         case 6:
5369                 unicode  =  (string[0] & 0x01) << 30;
5370                 unicode += ((string[1] & 0x3f) << 24);
5371                 unicode += ((string[2] & 0x3f) << 18);
5372                 unicode += ((string[3] & 0x3f) << 12);
5373                 unicode += ((string[4] & 0x3f) << 6);
5374                 unicode +=  (string[5] & 0x3f);
5375                 break;
5376         default:
5377                 die("Invalid unicode length");
5378         }
5380         /* Invalid characters could return the special 0xfffd value but NUL
5381          * should be just as good. */
5382         return unicode > 0xffff ? 0 : unicode;
5385 /* Calculates how much of string can be shown within the given maximum width
5386  * and sets trimmed parameter to non-zero value if all of string could not be
5387  * shown. If the reserve flag is TRUE, it will reserve at least one
5388  * trailing character, which can be useful when drawing a delimiter.
5389  *
5390  * Returns the number of bytes to output from string to satisfy max_width. */
5391 static size_t
5392 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5394         const char *start = string;
5395         const char *end = strchr(string, '\0');
5396         unsigned char last_bytes = 0;
5397         size_t last_ucwidth = 0;
5399         *width = 0;
5400         *trimmed = 0;
5402         while (string < end) {
5403                 int c = *(unsigned char *) string;
5404                 unsigned char bytes = utf8_bytes[c];
5405                 size_t ucwidth;
5406                 unsigned long unicode;
5408                 if (string + bytes > end)
5409                         break;
5411                 /* Change representation to figure out whether
5412                  * it is a single- or double-width character. */
5414                 unicode = utf8_to_unicode(string, bytes);
5415                 /* FIXME: Graceful handling of invalid unicode character. */
5416                 if (!unicode)
5417                         break;
5419                 ucwidth = unicode_width(unicode);
5420                 *width  += ucwidth;
5421                 if (*width > max_width) {
5422                         *trimmed = 1;
5423                         *width -= ucwidth;
5424                         if (reserve && *width == max_width) {
5425                                 string -= last_bytes;
5426                                 *width -= last_ucwidth;
5427                         }
5428                         break;
5429                 }
5431                 string  += bytes;
5432                 last_bytes = bytes;
5433                 last_ucwidth = ucwidth;
5434         }
5436         return string - start;
5440 /*
5441  * Status management
5442  */
5444 /* Whether or not the curses interface has been initialized. */
5445 static bool cursed = FALSE;
5447 /* The status window is used for polling keystrokes. */
5448 static WINDOW *status_win;
5450 static bool status_empty = TRUE;
5452 /* Update status and title window. */
5453 static void
5454 report(const char *msg, ...)
5456         struct view *view = display[current_view];
5458         if (input_mode)
5459                 return;
5461         if (!view) {
5462                 char buf[SIZEOF_STR];
5463                 va_list args;
5465                 va_start(args, msg);
5466                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5467                         buf[sizeof(buf) - 1] = 0;
5468                         buf[sizeof(buf) - 2] = '.';
5469                         buf[sizeof(buf) - 3] = '.';
5470                         buf[sizeof(buf) - 4] = '.';
5471                 }
5472                 va_end(args);
5473                 die("%s", buf);
5474         }
5476         if (!status_empty || *msg) {
5477                 va_list args;
5479                 va_start(args, msg);
5481                 wmove(status_win, 0, 0);
5482                 if (*msg) {
5483                         vwprintw(status_win, msg, args);
5484                         status_empty = FALSE;
5485                 } else {
5486                         status_empty = TRUE;
5487                 }
5488                 wclrtoeol(status_win);
5489                 wrefresh(status_win);
5491                 va_end(args);
5492         }
5494         update_view_title(view);
5495         update_display_cursor(view);
5498 /* Controls when nodelay should be in effect when polling user input. */
5499 static void
5500 set_nonblocking_input(bool loading)
5502         static unsigned int loading_views;
5504         if ((loading == FALSE && loading_views-- == 1) ||
5505             (loading == TRUE  && loading_views++ == 0))
5506                 nodelay(status_win, loading);
5509 static void
5510 init_display(void)
5512         int x, y;
5514         /* Initialize the curses library */
5515         if (isatty(STDIN_FILENO)) {
5516                 cursed = !!initscr();
5517         } else {
5518                 /* Leave stdin and stdout alone when acting as a pager. */
5519                 FILE *io = fopen("/dev/tty", "r+");
5521                 if (!io)
5522                         die("Failed to open /dev/tty");
5523                 cursed = !!newterm(NULL, io, io);
5524         }
5526         if (!cursed)
5527                 die("Failed to initialize curses");
5529         nonl();         /* Tell curses not to do NL->CR/NL on output */
5530         cbreak();       /* Take input chars one at a time, no wait for \n */
5531         noecho();       /* Don't echo input */
5532         leaveok(stdscr, TRUE);
5534         if (has_colors())
5535                 init_colors();
5537         getmaxyx(stdscr, y, x);
5538         status_win = newwin(1, 0, y - 1, 0);
5539         if (!status_win)
5540                 die("Failed to create status window");
5542         /* Enable keyboard mapping */
5543         keypad(status_win, TRUE);
5544         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5546         TABSIZE = opt_tab_size;
5547         if (opt_line_graphics) {
5548                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5549         }
5552 static bool
5553 prompt_yesno(const char *prompt)
5555         enum { WAIT, STOP, CANCEL  } status = WAIT;
5556         bool answer = FALSE;
5558         while (status == WAIT) {
5559                 struct view *view;
5560                 int i, key;
5562                 input_mode = TRUE;
5564                 foreach_view (view, i)
5565                         update_view(view);
5567                 input_mode = FALSE;
5569                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5570                 wclrtoeol(status_win);
5572                 /* Refresh, accept single keystroke of input */
5573                 key = wgetch(status_win);
5574                 switch (key) {
5575                 case ERR:
5576                         break;
5578                 case 'y':
5579                 case 'Y':
5580                         answer = TRUE;
5581                         status = STOP;
5582                         break;
5584                 case KEY_ESC:
5585                 case KEY_RETURN:
5586                 case KEY_ENTER:
5587                 case KEY_BACKSPACE:
5588                 case 'n':
5589                 case 'N':
5590                 case '\n':
5591                 default:
5592                         answer = FALSE;
5593                         status = CANCEL;
5594                 }
5595         }
5597         /* Clear the status window */
5598         status_empty = FALSE;
5599         report("");
5601         return answer;
5604 static char *
5605 read_prompt(const char *prompt)
5607         enum { READING, STOP, CANCEL } status = READING;
5608         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5609         int pos = 0;
5611         while (status == READING) {
5612                 struct view *view;
5613                 int i, key;
5615                 input_mode = TRUE;
5617                 foreach_view (view, i)
5618                         update_view(view);
5620                 input_mode = FALSE;
5622                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5623                 wclrtoeol(status_win);
5625                 /* Refresh, accept single keystroke of input */
5626                 key = wgetch(status_win);
5627                 switch (key) {
5628                 case KEY_RETURN:
5629                 case KEY_ENTER:
5630                 case '\n':
5631                         status = pos ? STOP : CANCEL;
5632                         break;
5634                 case KEY_BACKSPACE:
5635                         if (pos > 0)
5636                                 pos--;
5637                         else
5638                                 status = CANCEL;
5639                         break;
5641                 case KEY_ESC:
5642                         status = CANCEL;
5643                         break;
5645                 case ERR:
5646                         break;
5648                 default:
5649                         if (pos >= sizeof(buf)) {
5650                                 report("Input string too long");
5651                                 return NULL;
5652                         }
5654                         if (isprint(key))
5655                                 buf[pos++] = (char) key;
5656                 }
5657         }
5659         /* Clear the status window */
5660         status_empty = FALSE;
5661         report("");
5663         if (status == CANCEL)
5664                 return NULL;
5666         buf[pos++] = 0;
5668         return buf;
5671 /*
5672  * Repository references
5673  */
5675 static struct ref *refs = NULL;
5676 static size_t refs_alloc = 0;
5677 static size_t refs_size = 0;
5679 /* Id <-> ref store */
5680 static struct ref ***id_refs = NULL;
5681 static size_t id_refs_alloc = 0;
5682 static size_t id_refs_size = 0;
5684 static struct ref **
5685 get_refs(char *id)
5687         struct ref ***tmp_id_refs;
5688         struct ref **ref_list = NULL;
5689         size_t ref_list_alloc = 0;
5690         size_t ref_list_size = 0;
5691         size_t i;
5693         for (i = 0; i < id_refs_size; i++)
5694                 if (!strcmp(id, id_refs[i][0]->id))
5695                         return id_refs[i];
5697         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5698                                     sizeof(*id_refs));
5699         if (!tmp_id_refs)
5700                 return NULL;
5702         id_refs = tmp_id_refs;
5704         for (i = 0; i < refs_size; i++) {
5705                 struct ref **tmp;
5707                 if (strcmp(id, refs[i].id))
5708                         continue;
5710                 tmp = realloc_items(ref_list, &ref_list_alloc,
5711                                     ref_list_size + 1, sizeof(*ref_list));
5712                 if (!tmp) {
5713                         if (ref_list)
5714                                 free(ref_list);
5715                         return NULL;
5716                 }
5718                 ref_list = tmp;
5719                 if (ref_list_size > 0)
5720                         ref_list[ref_list_size - 1]->next = 1;
5721                 ref_list[ref_list_size] = &refs[i];
5723                 /* XXX: The properties of the commit chains ensures that we can
5724                  * safely modify the shared ref. The repo references will
5725                  * always be similar for the same id. */
5726                 ref_list[ref_list_size]->next = 0;
5727                 ref_list_size++;
5728         }
5730         if (ref_list)
5731                 id_refs[id_refs_size++] = ref_list;
5733         return ref_list;
5736 static int
5737 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5739         struct ref *ref;
5740         bool tag = FALSE;
5741         bool ltag = FALSE;
5742         bool remote = FALSE;
5743         bool tracked = FALSE;
5744         bool check_replace = FALSE;
5745         bool head = FALSE;
5747         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5748                 if (!strcmp(name + namelen - 3, "^{}")) {
5749                         namelen -= 3;
5750                         name[namelen] = 0;
5751                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5752                                 check_replace = TRUE;
5753                 } else {
5754                         ltag = TRUE;
5755                 }
5757                 tag = TRUE;
5758                 namelen -= STRING_SIZE("refs/tags/");
5759                 name    += STRING_SIZE("refs/tags/");
5761         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5762                 remote = TRUE;
5763                 namelen -= STRING_SIZE("refs/remotes/");
5764                 name    += STRING_SIZE("refs/remotes/");
5765                 tracked  = !strcmp(opt_remote, name);
5767         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5768                 namelen -= STRING_SIZE("refs/heads/");
5769                 name    += STRING_SIZE("refs/heads/");
5770                 head     = !strncmp(opt_head, name, namelen);
5772         } else if (!strcmp(name, "HEAD")) {
5773                 opt_no_head = FALSE;
5774                 return OK;
5775         }
5777         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5778                 /* it's an annotated tag, replace the previous sha1 with the
5779                  * resolved commit id; relies on the fact git-ls-remote lists
5780                  * the commit id of an annotated tag right beofre the commit id
5781                  * it points to. */
5782                 refs[refs_size - 1].ltag = ltag;
5783                 string_copy_rev(refs[refs_size - 1].id, id);
5785                 return OK;
5786         }
5787         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5788         if (!refs)
5789                 return ERR;
5791         ref = &refs[refs_size++];
5792         ref->name = malloc(namelen + 1);
5793         if (!ref->name)
5794                 return ERR;
5796         strncpy(ref->name, name, namelen);
5797         ref->name[namelen] = 0;
5798         ref->head = head;
5799         ref->tag = tag;
5800         ref->ltag = ltag;
5801         ref->remote = remote;
5802         ref->tracked = tracked;
5803         string_copy_rev(ref->id, id);
5805         return OK;
5808 static int
5809 load_refs(void)
5811         const char *cmd_env = getenv("TIG_LS_REMOTE");
5812         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5814         if (!*opt_git_dir)
5815                 return OK;
5817         while (refs_size > 0)
5818                 free(refs[--refs_size].name);
5819         while (id_refs_size > 0)
5820                 free(id_refs[--id_refs_size]);
5822         return read_properties(popen(cmd, "r"), "\t", read_ref);
5825 static int
5826 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5828         if (!strcmp(name, "i18n.commitencoding"))
5829                 string_ncopy(opt_encoding, value, valuelen);
5831         if (!strcmp(name, "core.editor"))
5832                 string_ncopy(opt_editor, value, valuelen);
5834         /* branch.<head>.remote */
5835         if (*opt_head &&
5836             !strncmp(name, "branch.", 7) &&
5837             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5838             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5839                 string_ncopy(opt_remote, value, valuelen);
5841         if (*opt_head && *opt_remote &&
5842             !strncmp(name, "branch.", 7) &&
5843             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5844             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5845                 size_t from = strlen(opt_remote);
5847                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5848                         value += STRING_SIZE("refs/heads/");
5849                         valuelen -= STRING_SIZE("refs/heads/");
5850                 }
5852                 if (!string_format_from(opt_remote, &from, "/%s", value))
5853                         opt_remote[0] = 0;
5854         }
5856         return OK;
5859 static int
5860 load_git_config(void)
5862         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5863                                "=", read_repo_config_option);
5866 static int
5867 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5869         if (!opt_git_dir[0]) {
5870                 string_ncopy(opt_git_dir, name, namelen);
5872         } else if (opt_is_inside_work_tree == -1) {
5873                 /* This can be 3 different values depending on the
5874                  * version of git being used. If git-rev-parse does not
5875                  * understand --is-inside-work-tree it will simply echo
5876                  * the option else either "true" or "false" is printed.
5877                  * Default to true for the unknown case. */
5878                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5880         } else if (opt_cdup[0] == ' ') {
5881                 string_ncopy(opt_cdup, name, namelen);
5882         } else {
5883                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5884                         namelen -= STRING_SIZE("refs/heads/");
5885                         name    += STRING_SIZE("refs/heads/");
5886                         string_ncopy(opt_head, name, namelen);
5887                 }
5888         }
5890         return OK;
5893 static int
5894 load_repo_info(void)
5896         int result;
5897         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5898                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5900         /* XXX: The line outputted by "--show-cdup" can be empty so
5901          * initialize it to something invalid to make it possible to
5902          * detect whether it has been set or not. */
5903         opt_cdup[0] = ' ';
5905         result = read_properties(pipe, "=", read_repo_info);
5906         if (opt_cdup[0] == ' ')
5907                 opt_cdup[0] = 0;
5909         return result;
5912 static int
5913 read_properties(FILE *pipe, const char *separators,
5914                 int (*read_property)(char *, size_t, char *, size_t))
5916         char buffer[BUFSIZ];
5917         char *name;
5918         int state = OK;
5920         if (!pipe)
5921                 return ERR;
5923         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5924                 char *value;
5925                 size_t namelen;
5926                 size_t valuelen;
5928                 name = chomp_string(name);
5929                 namelen = strcspn(name, separators);
5931                 if (name[namelen]) {
5932                         name[namelen] = 0;
5933                         value = chomp_string(name + namelen + 1);
5934                         valuelen = strlen(value);
5936                 } else {
5937                         value = "";
5938                         valuelen = 0;
5939                 }
5941                 state = read_property(name, namelen, value, valuelen);
5942         }
5944         if (state != ERR && ferror(pipe))
5945                 state = ERR;
5947         pclose(pipe);
5949         return state;
5953 /*
5954  * Main
5955  */
5957 static void __NORETURN
5958 quit(int sig)
5960         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5961         if (cursed)
5962                 endwin();
5963         exit(0);
5966 static void __NORETURN
5967 die(const char *err, ...)
5969         va_list args;
5971         endwin();
5973         va_start(args, err);
5974         fputs("tig: ", stderr);
5975         vfprintf(stderr, err, args);
5976         fputs("\n", stderr);
5977         va_end(args);
5979         exit(1);
5982 static void
5983 warn(const char *msg, ...)
5985         va_list args;
5987         va_start(args, msg);
5988         fputs("tig warning: ", stderr);
5989         vfprintf(stderr, msg, args);
5990         fputs("\n", stderr);
5991         va_end(args);
5994 int
5995 main(int argc, char *argv[])
5997         struct view *view;
5998         enum request request;
5999         size_t i;
6001         signal(SIGINT, quit);
6003         if (setlocale(LC_ALL, "")) {
6004                 char *codeset = nl_langinfo(CODESET);
6006                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6007         }
6009         if (load_repo_info() == ERR)
6010                 die("Failed to load repo info.");
6012         if (load_options() == ERR)
6013                 die("Failed to load user config.");
6015         if (load_git_config() == ERR)
6016                 die("Failed to load repo config.");
6018         request = parse_options(argc, argv);
6019         if (request == REQ_NONE)
6020                 return 0;
6022         /* Require a git repository unless when running in pager mode. */
6023         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6024                 die("Not a git repository");
6026         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6027                 opt_utf8 = FALSE;
6029         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6030                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6031                 if (opt_iconv == ICONV_NONE)
6032                         die("Failed to initialize character set conversion");
6033         }
6035         if (load_refs() == ERR)
6036                 die("Failed to load refs.");
6038         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
6039                 view->cmd_env = getenv(view->cmd_env);
6041         init_display();
6043         while (view_driver(display[current_view], request)) {
6044                 int key;
6045                 int i;
6047                 foreach_view (view, i)
6048                         update_view(view);
6050                 /* Refresh, accept single keystroke of input */
6051                 key = wgetch(status_win);
6053                 /* wgetch() with nodelay() enabled returns ERR when there's no
6054                  * input. */
6055                 if (key == ERR) {
6056                         request = REQ_NONE;
6057                         continue;
6058                 }
6060                 request = get_keybinding(display[current_view]->keymap, key);
6062                 /* Some low-level request handling. This keeps access to
6063                  * status_win restricted. */
6064                 switch (request) {
6065                 case REQ_PROMPT:
6066                 {
6067                         char *cmd = read_prompt(":");
6069                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6070                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6071                                         request = REQ_VIEW_DIFF;
6072                                 } else {
6073                                         request = REQ_VIEW_PAGER;
6074                                 }
6076                                 /* Always reload^Wrerun commands from the prompt. */
6077                                 open_view(view, request, OPEN_RELOAD);
6078                         }
6080                         request = REQ_NONE;
6081                         break;
6082                 }
6083                 case REQ_SEARCH:
6084                 case REQ_SEARCH_BACK:
6085                 {
6086                         const char *prompt = request == REQ_SEARCH
6087                                            ? "/" : "?";
6088                         char *search = read_prompt(prompt);
6090                         if (search)
6091                                 string_ncopy(opt_search, search, strlen(search));
6092                         else
6093                                 request = REQ_NONE;
6094                         break;
6095                 }
6096                 case REQ_SCREEN_RESIZE:
6097                 {
6098                         int height, width;
6100                         getmaxyx(stdscr, height, width);
6102                         /* Resize the status view and let the view driver take
6103                          * care of resizing the displayed views. */
6104                         wresize(status_win, 1, width);
6105                         mvwin(status_win, height - 1, 0);
6106                         wrefresh(status_win);
6107                         break;
6108                 }
6109                 default:
6110                         break;
6111                 }
6112         }
6114         quit(0);
6116         return 0;