Code

update_view: Check the pipes and call end_update() before redrawing
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD     \
129         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132         "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135         TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD    \
138         "git ls-tree %s %s"
140 #define TIG_BLOB_CMD    \
141         "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD    ""
145 #define TIG_PAGER_CMD   ""
146 #define TIG_STATUS_CMD  ""
147 #define TIG_STAGE_CMD   ""
148 #define TIG_BLAME_CMD   ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB         '\t'
152 #define KEY_RETURN      '\r'
153 #define KEY_ESC         27
156 struct ref {
157         char *name;             /* Ref name; tag or head names are shortened. */
158         char id[SIZEOF_REV];    /* Commit SHA1 ID */
159         unsigned int head:1;    /* Is it the current HEAD? */
160         unsigned int tag:1;     /* Is it a tag? */
161         unsigned int ltag:1;    /* If so, is the tag local? */
162         unsigned int remote:1;  /* Is it a remote ref? */
163         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164         unsigned int next:1;    /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170         const char *name;
171         int namelen;
172         int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177                  int *value, const char *name, int namelen)
180         int i;
182         for (i = 0; i < map_size; i++)
183                 if (namelen == map[i].namelen &&
184                     !strncasecmp(name, map[i].name, namelen)) {
185                         *value = map[i].value;
186                         return OK;
187                 }
189         return ERR;
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224         int namelen;
226         while (isspace(*name))
227                 name++;
229         namelen = strlen(name) - 1;
230         while (namelen > 0 && isspace(name[namelen]))
231                 name[namelen--] = 0;
233         return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239         va_list args;
240         size_t pos = bufpos ? *bufpos : 0;
242         va_start(args, fmt);
243         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244         va_end(args);
246         if (bufpos)
247                 *bufpos = pos;
249         return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253         string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256         string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261         size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265         /* Diff-Header == DIFF_HEADER */
266         for (i = 0; i < len; i++) {
267                 if (toupper(str1[i]) == toupper(str2[i]))
268                         continue;
270                 if (string_enum_sep(str1[i]) &&
271                     string_enum_sep(str2[i]))
272                         continue;
274                 return str1[i] - str2[i];
275         }
277         return 0;
280 #define prefixcmp(str1, str2) \
281         strncmp(str1, str2, STRING_SIZE(str2))
283 /* Shell quoting
284  *
285  * NOTE: The following is a slightly modified copy of the git project's shell
286  * quoting routines found in the quote.c file.
287  *
288  * Help to copy the thing properly quoted for the shell safety.  any single
289  * quote is replaced with '\'', any exclamation point is replaced with '\!',
290  * and the whole thing is enclosed in a
291  *
292  * E.g.
293  *  original     sq_quote     result
294  *  name     ==> name      ==> 'name'
295  *  a b      ==> a b       ==> 'a b'
296  *  a'b      ==> a'\''b    ==> 'a'\''b'
297  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
298  */
300 static size_t
301 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
303         char c;
305 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
307         BUFPUT('\'');
308         while ((c = *src++)) {
309                 if (c == '\'' || c == '!') {
310                         BUFPUT('\'');
311                         BUFPUT('\\');
312                         BUFPUT(c);
313                         BUFPUT('\'');
314                 } else {
315                         BUFPUT(c);
316                 }
317         }
318         BUFPUT('\'');
320         if (bufsize < SIZEOF_STR)
321                 buf[bufsize] = 0;
323         return bufsize;
327 /*
328  * User requests
329  */
331 #define REQ_INFO \
332         /* XXX: Keep the view request first and in sync with views[]. */ \
333         REQ_GROUP("View switching") \
334         REQ_(VIEW_MAIN,         "Show main view"), \
335         REQ_(VIEW_DIFF,         "Show diff view"), \
336         REQ_(VIEW_LOG,          "Show log view"), \
337         REQ_(VIEW_TREE,         "Show tree view"), \
338         REQ_(VIEW_BLOB,         "Show blob view"), \
339         REQ_(VIEW_BLAME,        "Show blame view"), \
340         REQ_(VIEW_HELP,         "Show help page"), \
341         REQ_(VIEW_PAGER,        "Show pager view"), \
342         REQ_(VIEW_STATUS,       "Show status view"), \
343         REQ_(VIEW_STAGE,        "Show stage view"), \
344         \
345         REQ_GROUP("View manipulation") \
346         REQ_(ENTER,             "Enter current line and scroll"), \
347         REQ_(NEXT,              "Move to next"), \
348         REQ_(PREVIOUS,          "Move to previous"), \
349         REQ_(VIEW_NEXT,         "Move focus to next view"), \
350         REQ_(REFRESH,           "Reload and refresh"), \
351         REQ_(MAXIMIZE,          "Maximize the current view"), \
352         REQ_(VIEW_CLOSE,        "Close the current view"), \
353         REQ_(QUIT,              "Close all views and quit"), \
354         \
355         REQ_GROUP("View specific requests") \
356         REQ_(STATUS_UPDATE,     "Update file status"), \
357         REQ_(STATUS_REVERT,     "Revert file changes"), \
358         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
359         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
360         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
361         \
362         REQ_GROUP("Cursor navigation") \
363         REQ_(MOVE_UP,           "Move cursor one line up"), \
364         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
365         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
366         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
367         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
368         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
369         \
370         REQ_GROUP("Scrolling") \
371         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
372         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
373         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
374         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
375         \
376         REQ_GROUP("Searching") \
377         REQ_(SEARCH,            "Search the view"), \
378         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
379         REQ_(FIND_NEXT,         "Find next search match"), \
380         REQ_(FIND_PREV,         "Find previous search match"), \
381         \
382         REQ_GROUP("Option manipulation") \
383         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
384         REQ_(TOGGLE_DATE,       "Toggle date display"), \
385         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
386         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
387         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
388         \
389         REQ_GROUP("Misc") \
390         REQ_(PROMPT,            "Bring up the prompt"), \
391         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
392         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
393         REQ_(SHOW_VERSION,      "Show version information"), \
394         REQ_(STOP_LOADING,      "Stop all loading views"), \
395         REQ_(EDIT,              "Open in editor"), \
396         REQ_(NONE,              "Do nothing")
399 /* User action requests. */
400 enum request {
401 #define REQ_GROUP(help)
402 #define REQ_(req, help) REQ_##req
404         /* Offset all requests to avoid conflicts with ncurses getch values. */
405         REQ_OFFSET = KEY_MAX + 1,
406         REQ_INFO
408 #undef  REQ_GROUP
409 #undef  REQ_
410 };
412 struct request_info {
413         enum request request;
414         const char *name;
415         int namelen;
416         const char *help;
417 };
419 static struct request_info req_info[] = {
420 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
421 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
422         REQ_INFO
423 #undef  REQ_GROUP
424 #undef  REQ_
425 };
427 static enum request
428 get_request(const char *name)
430         int namelen = strlen(name);
431         int i;
433         for (i = 0; i < ARRAY_SIZE(req_info); i++)
434                 if (req_info[i].namelen == namelen &&
435                     !string_enum_compare(req_info[i].name, name, namelen))
436                         return req_info[i].request;
438         return REQ_NONE;
442 /*
443  * Options
444  */
446 static const char usage[] =
447 "tig " TIG_VERSION " (" __DATE__ ")\n"
448 "\n"
449 "Usage: tig        [options] [revs] [--] [paths]\n"
450 "   or: tig show   [options] [revs] [--] [paths]\n"
451 "   or: tig blame  [rev] path\n"
452 "   or: tig status\n"
453 "   or: tig <      [git command output]\n"
454 "\n"
455 "Options:\n"
456 "  -v, --version   Show version and exit\n"
457 "  -h, --help      Show help message and exit";
459 /* Option and state variables. */
460 static bool opt_date                    = TRUE;
461 static bool opt_author                  = TRUE;
462 static bool opt_line_number             = FALSE;
463 static bool opt_line_graphics           = TRUE;
464 static bool opt_rev_graph               = FALSE;
465 static bool opt_show_refs               = TRUE;
466 static int opt_num_interval             = NUMBER_INTERVAL;
467 static int opt_tab_size                 = TAB_SIZE;
468 static int opt_author_cols              = AUTHOR_COLS-1;
469 static char opt_cmd[SIZEOF_STR]         = "";
470 static char opt_path[SIZEOF_STR]        = "";
471 static char opt_file[SIZEOF_STR]        = "";
472 static char opt_ref[SIZEOF_REF]         = "";
473 static char opt_head[SIZEOF_REF]        = "";
474 static char opt_remote[SIZEOF_REF]      = "";
475 static bool opt_no_head                 = TRUE;
476 static FILE *opt_pipe                   = NULL;
477 static char opt_encoding[20]            = "UTF-8";
478 static bool opt_utf8                    = TRUE;
479 static char opt_codeset[20]             = "UTF-8";
480 static iconv_t opt_iconv                = ICONV_NONE;
481 static char opt_search[SIZEOF_STR]      = "";
482 static char opt_cdup[SIZEOF_STR]        = "";
483 static char opt_git_dir[SIZEOF_STR]     = "";
484 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
485 static char opt_editor[SIZEOF_STR]      = "";
487 static enum request
488 parse_options(int argc, const char *argv[])
490         enum request request = REQ_VIEW_MAIN;
491         size_t buf_size;
492         const char *subcommand;
493         bool seen_dashdash = FALSE;
494         int i;
496         if (!isatty(STDIN_FILENO)) {
497                 opt_pipe = stdin;
498                 return REQ_VIEW_PAGER;
499         }
501         if (argc <= 1)
502                 return REQ_VIEW_MAIN;
504         subcommand = argv[1];
505         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
506                 if (!strcmp(subcommand, "-S"))
507                         warn("`-S' has been deprecated; use `tig status' instead");
508                 if (argc > 2)
509                         warn("ignoring arguments after `%s'", subcommand);
510                 return REQ_VIEW_STATUS;
512         } else if (!strcmp(subcommand, "blame")) {
513                 if (argc <= 2 || argc > 4)
514                         die("invalid number of options to blame\n\n%s", usage);
516                 i = 2;
517                 if (argc == 4) {
518                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
519                         i++;
520                 }
522                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
523                 return REQ_VIEW_BLAME;
525         } else if (!strcmp(subcommand, "show")) {
526                 request = REQ_VIEW_DIFF;
528         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
529                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
530                 warn("`tig %s' has been deprecated", subcommand);
532         } else {
533                 subcommand = NULL;
534         }
536         if (!subcommand)
537                 /* XXX: This is vulnerable to the user overriding
538                  * options required for the main view parser. */
539                 string_copy(opt_cmd, TIG_MAIN_BASE);
540         else
541                 string_format(opt_cmd, "git %s", subcommand);
543         buf_size = strlen(opt_cmd);
545         for (i = 1 + !!subcommand; i < argc; i++) {
546                 const char *opt = argv[i];
548                 if (seen_dashdash || !strcmp(opt, "--")) {
549                         seen_dashdash = TRUE;
551                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
552                         printf("tig version %s\n", TIG_VERSION);
553                         return REQ_NONE;
555                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
556                         printf("%s\n", usage);
557                         return REQ_NONE;
558                 }
560                 opt_cmd[buf_size++] = ' ';
561                 buf_size = sq_quote(opt_cmd, buf_size, opt);
562                 if (buf_size >= sizeof(opt_cmd))
563                         die("command too long");
564         }
566         opt_cmd[buf_size] = 0;
568         return request;
572 /*
573  * Line-oriented content detection.
574  */
576 #define LINE_INFO \
577 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
578 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
579 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
580 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
581 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
582 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
583 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
584 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
585 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
586 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
587 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
588 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
589 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
590 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
591 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
592 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
593 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
594 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
595 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
596 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
597 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
598 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
599 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
600 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
601 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
602 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
603 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
604 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
606 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
607 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
608 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
610 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
611 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
612 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
613 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
614 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
615 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
616 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
617 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
619 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
620 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
621 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
622 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
623 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
624 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
625 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
626 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
627 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
628 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
629 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
630 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
632 enum line_type {
633 #define LINE(type, line, fg, bg, attr) \
634         LINE_##type
635         LINE_INFO,
636         LINE_NONE
637 #undef  LINE
638 };
640 struct line_info {
641         const char *name;       /* Option name. */
642         int namelen;            /* Size of option name. */
643         const char *line;       /* The start of line to match. */
644         int linelen;            /* Size of string to match. */
645         int fg, bg, attr;       /* Color and text attributes for the lines. */
646 };
648 static struct line_info line_info[] = {
649 #define LINE(type, line, fg, bg, attr) \
650         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
651         LINE_INFO
652 #undef  LINE
653 };
655 static enum line_type
656 get_line_type(const char *line)
658         int linelen = strlen(line);
659         enum line_type type;
661         for (type = 0; type < ARRAY_SIZE(line_info); type++)
662                 /* Case insensitive search matches Signed-off-by lines better. */
663                 if (linelen >= line_info[type].linelen &&
664                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
665                         return type;
667         return LINE_DEFAULT;
670 static inline int
671 get_line_attr(enum line_type type)
673         assert(type < ARRAY_SIZE(line_info));
674         return COLOR_PAIR(type) | line_info[type].attr;
677 static struct line_info *
678 get_line_info(const char *name)
680         size_t namelen = strlen(name);
681         enum line_type type;
683         for (type = 0; type < ARRAY_SIZE(line_info); type++)
684                 if (namelen == line_info[type].namelen &&
685                     !string_enum_compare(line_info[type].name, name, namelen))
686                         return &line_info[type];
688         return NULL;
691 static void
692 init_colors(void)
694         int default_bg = line_info[LINE_DEFAULT].bg;
695         int default_fg = line_info[LINE_DEFAULT].fg;
696         enum line_type type;
698         start_color();
700         if (assume_default_colors(default_fg, default_bg) == ERR) {
701                 default_bg = COLOR_BLACK;
702                 default_fg = COLOR_WHITE;
703         }
705         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
706                 struct line_info *info = &line_info[type];
707                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
708                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
710                 init_pair(type, fg, bg);
711         }
714 struct line {
715         enum line_type type;
717         /* State flags */
718         unsigned int selected:1;
719         unsigned int dirty:1;
721         void *data;             /* User data */
722 };
725 /*
726  * Keys
727  */
729 struct keybinding {
730         int alias;
731         enum request request;
732         struct keybinding *next;
733 };
735 static struct keybinding default_keybindings[] = {
736         /* View switching */
737         { 'm',          REQ_VIEW_MAIN },
738         { 'd',          REQ_VIEW_DIFF },
739         { 'l',          REQ_VIEW_LOG },
740         { 't',          REQ_VIEW_TREE },
741         { 'f',          REQ_VIEW_BLOB },
742         { 'B',          REQ_VIEW_BLAME },
743         { 'p',          REQ_VIEW_PAGER },
744         { 'h',          REQ_VIEW_HELP },
745         { 'S',          REQ_VIEW_STATUS },
746         { 'c',          REQ_VIEW_STAGE },
748         /* View manipulation */
749         { 'q',          REQ_VIEW_CLOSE },
750         { KEY_TAB,      REQ_VIEW_NEXT },
751         { KEY_RETURN,   REQ_ENTER },
752         { KEY_UP,       REQ_PREVIOUS },
753         { KEY_DOWN,     REQ_NEXT },
754         { 'R',          REQ_REFRESH },
755         { KEY_F(5),     REQ_REFRESH },
756         { 'O',          REQ_MAXIMIZE },
758         /* Cursor navigation */
759         { 'k',          REQ_MOVE_UP },
760         { 'j',          REQ_MOVE_DOWN },
761         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
762         { KEY_END,      REQ_MOVE_LAST_LINE },
763         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
764         { ' ',          REQ_MOVE_PAGE_DOWN },
765         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
766         { 'b',          REQ_MOVE_PAGE_UP },
767         { '-',          REQ_MOVE_PAGE_UP },
769         /* Scrolling */
770         { KEY_IC,       REQ_SCROLL_LINE_UP },
771         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
772         { 'w',          REQ_SCROLL_PAGE_UP },
773         { 's',          REQ_SCROLL_PAGE_DOWN },
775         /* Searching */
776         { '/',          REQ_SEARCH },
777         { '?',          REQ_SEARCH_BACK },
778         { 'n',          REQ_FIND_NEXT },
779         { 'N',          REQ_FIND_PREV },
781         /* Misc */
782         { 'Q',          REQ_QUIT },
783         { 'z',          REQ_STOP_LOADING },
784         { 'v',          REQ_SHOW_VERSION },
785         { 'r',          REQ_SCREEN_REDRAW },
786         { '.',          REQ_TOGGLE_LINENO },
787         { 'D',          REQ_TOGGLE_DATE },
788         { 'A',          REQ_TOGGLE_AUTHOR },
789         { 'g',          REQ_TOGGLE_REV_GRAPH },
790         { 'F',          REQ_TOGGLE_REFS },
791         { ':',          REQ_PROMPT },
792         { 'u',          REQ_STATUS_UPDATE },
793         { '!',          REQ_STATUS_REVERT },
794         { 'M',          REQ_STATUS_MERGE },
795         { '@',          REQ_STAGE_NEXT },
796         { ',',          REQ_TREE_PARENT },
797         { 'e',          REQ_EDIT },
799         /* Using the ncurses SIGWINCH handler. */
800         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
801 };
803 #define KEYMAP_INFO \
804         KEYMAP_(GENERIC), \
805         KEYMAP_(MAIN), \
806         KEYMAP_(DIFF), \
807         KEYMAP_(LOG), \
808         KEYMAP_(TREE), \
809         KEYMAP_(BLOB), \
810         KEYMAP_(BLAME), \
811         KEYMAP_(PAGER), \
812         KEYMAP_(HELP), \
813         KEYMAP_(STATUS), \
814         KEYMAP_(STAGE)
816 enum keymap {
817 #define KEYMAP_(name) KEYMAP_##name
818         KEYMAP_INFO
819 #undef  KEYMAP_
820 };
822 static struct int_map keymap_table[] = {
823 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
824         KEYMAP_INFO
825 #undef  KEYMAP_
826 };
828 #define set_keymap(map, name) \
829         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
831 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
833 static void
834 add_keybinding(enum keymap keymap, enum request request, int key)
836         struct keybinding *keybinding;
838         keybinding = calloc(1, sizeof(*keybinding));
839         if (!keybinding)
840                 die("Failed to allocate keybinding");
842         keybinding->alias = key;
843         keybinding->request = request;
844         keybinding->next = keybindings[keymap];
845         keybindings[keymap] = keybinding;
848 /* Looks for a key binding first in the given map, then in the generic map, and
849  * lastly in the default keybindings. */
850 static enum request
851 get_keybinding(enum keymap keymap, int key)
853         struct keybinding *kbd;
854         int i;
856         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
857                 if (kbd->alias == key)
858                         return kbd->request;
860         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
861                 if (kbd->alias == key)
862                         return kbd->request;
864         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
865                 if (default_keybindings[i].alias == key)
866                         return default_keybindings[i].request;
868         return (enum request) key;
872 struct key {
873         const char *name;
874         int value;
875 };
877 static struct key key_table[] = {
878         { "Enter",      KEY_RETURN },
879         { "Space",      ' ' },
880         { "Backspace",  KEY_BACKSPACE },
881         { "Tab",        KEY_TAB },
882         { "Escape",     KEY_ESC },
883         { "Left",       KEY_LEFT },
884         { "Right",      KEY_RIGHT },
885         { "Up",         KEY_UP },
886         { "Down",       KEY_DOWN },
887         { "Insert",     KEY_IC },
888         { "Delete",     KEY_DC },
889         { "Hash",       '#' },
890         { "Home",       KEY_HOME },
891         { "End",        KEY_END },
892         { "PageUp",     KEY_PPAGE },
893         { "PageDown",   KEY_NPAGE },
894         { "F1",         KEY_F(1) },
895         { "F2",         KEY_F(2) },
896         { "F3",         KEY_F(3) },
897         { "F4",         KEY_F(4) },
898         { "F5",         KEY_F(5) },
899         { "F6",         KEY_F(6) },
900         { "F7",         KEY_F(7) },
901         { "F8",         KEY_F(8) },
902         { "F9",         KEY_F(9) },
903         { "F10",        KEY_F(10) },
904         { "F11",        KEY_F(11) },
905         { "F12",        KEY_F(12) },
906 };
908 static int
909 get_key_value(const char *name)
911         int i;
913         for (i = 0; i < ARRAY_SIZE(key_table); i++)
914                 if (!strcasecmp(key_table[i].name, name))
915                         return key_table[i].value;
917         if (strlen(name) == 1 && isprint(*name))
918                 return (int) *name;
920         return ERR;
923 static const char *
924 get_key_name(int key_value)
926         static char key_char[] = "'X'";
927         const char *seq = NULL;
928         int key;
930         for (key = 0; key < ARRAY_SIZE(key_table); key++)
931                 if (key_table[key].value == key_value)
932                         seq = key_table[key].name;
934         if (seq == NULL &&
935             key_value < 127 &&
936             isprint(key_value)) {
937                 key_char[1] = (char) key_value;
938                 seq = key_char;
939         }
941         return seq ? seq : "(no key)";
944 static const char *
945 get_key(enum request request)
947         static char buf[BUFSIZ];
948         size_t pos = 0;
949         char *sep = "";
950         int i;
952         buf[pos] = 0;
954         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
955                 struct keybinding *keybinding = &default_keybindings[i];
957                 if (keybinding->request != request)
958                         continue;
960                 if (!string_format_from(buf, &pos, "%s%s", sep,
961                                         get_key_name(keybinding->alias)))
962                         return "Too many keybindings!";
963                 sep = ", ";
964         }
966         return buf;
969 struct run_request {
970         enum keymap keymap;
971         int key;
972         char cmd[SIZEOF_STR];
973 };
975 static struct run_request *run_request;
976 static size_t run_requests;
978 static enum request
979 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
981         struct run_request *req;
982         char cmd[SIZEOF_STR];
983         size_t bufpos;
985         for (bufpos = 0; argc > 0; argc--, argv++)
986                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
987                         return REQ_NONE;
989         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
990         if (!req)
991                 return REQ_NONE;
993         run_request = req;
994         req = &run_request[run_requests++];
995         string_copy(req->cmd, cmd);
996         req->keymap = keymap;
997         req->key = key;
999         return REQ_NONE + run_requests;
1002 static struct run_request *
1003 get_run_request(enum request request)
1005         if (request <= REQ_NONE)
1006                 return NULL;
1007         return &run_request[request - REQ_NONE - 1];
1010 static void
1011 add_builtin_run_requests(void)
1013         struct {
1014                 enum keymap keymap;
1015                 int key;
1016                 const char *argv[1];
1017         } reqs[] = {
1018                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1019                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1020         };
1021         int i;
1023         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1024                 enum request req;
1026                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1027                 if (req != REQ_NONE)
1028                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1029         }
1032 /*
1033  * User config file handling.
1034  */
1036 static struct int_map color_map[] = {
1037 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1038         COLOR_MAP(DEFAULT),
1039         COLOR_MAP(BLACK),
1040         COLOR_MAP(BLUE),
1041         COLOR_MAP(CYAN),
1042         COLOR_MAP(GREEN),
1043         COLOR_MAP(MAGENTA),
1044         COLOR_MAP(RED),
1045         COLOR_MAP(WHITE),
1046         COLOR_MAP(YELLOW),
1047 };
1049 #define set_color(color, name) \
1050         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1052 static struct int_map attr_map[] = {
1053 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1054         ATTR_MAP(NORMAL),
1055         ATTR_MAP(BLINK),
1056         ATTR_MAP(BOLD),
1057         ATTR_MAP(DIM),
1058         ATTR_MAP(REVERSE),
1059         ATTR_MAP(STANDOUT),
1060         ATTR_MAP(UNDERLINE),
1061 };
1063 #define set_attribute(attr, name) \
1064         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1066 static int   config_lineno;
1067 static bool  config_errors;
1068 static const char *config_msg;
1070 /* Wants: object fgcolor bgcolor [attr] */
1071 static int
1072 option_color_command(int argc, const char *argv[])
1074         struct line_info *info;
1076         if (argc != 3 && argc != 4) {
1077                 config_msg = "Wrong number of arguments given to color command";
1078                 return ERR;
1079         }
1081         info = get_line_info(argv[0]);
1082         if (!info) {
1083                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1084                         info = get_line_info("delimiter");
1086                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1087                         info = get_line_info("date");
1089                 } else {
1090                         config_msg = "Unknown color name";
1091                         return ERR;
1092                 }
1093         }
1095         if (set_color(&info->fg, argv[1]) == ERR ||
1096             set_color(&info->bg, argv[2]) == ERR) {
1097                 config_msg = "Unknown color";
1098                 return ERR;
1099         }
1101         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1102                 config_msg = "Unknown attribute";
1103                 return ERR;
1104         }
1106         return OK;
1109 static bool parse_bool(const char *s)
1111         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1112                 !strcmp(s, "yes")) ? TRUE : FALSE;
1115 static int
1116 parse_int(const char *s, int default_value, int min, int max)
1118         int value = atoi(s);
1120         return (value < min || value > max) ? default_value : value;
1123 /* Wants: name = value */
1124 static int
1125 option_set_command(int argc, const char *argv[])
1127         if (argc != 3) {
1128                 config_msg = "Wrong number of arguments given to set command";
1129                 return ERR;
1130         }
1132         if (strcmp(argv[1], "=")) {
1133                 config_msg = "No value assigned";
1134                 return ERR;
1135         }
1137         if (!strcmp(argv[0], "show-author")) {
1138                 opt_author = parse_bool(argv[2]);
1139                 return OK;
1140         }
1142         if (!strcmp(argv[0], "show-date")) {
1143                 opt_date = parse_bool(argv[2]);
1144                 return OK;
1145         }
1147         if (!strcmp(argv[0], "show-rev-graph")) {
1148                 opt_rev_graph = parse_bool(argv[2]);
1149                 return OK;
1150         }
1152         if (!strcmp(argv[0], "show-refs")) {
1153                 opt_show_refs = parse_bool(argv[2]);
1154                 return OK;
1155         }
1157         if (!strcmp(argv[0], "show-line-numbers")) {
1158                 opt_line_number = parse_bool(argv[2]);
1159                 return OK;
1160         }
1162         if (!strcmp(argv[0], "line-graphics")) {
1163                 opt_line_graphics = parse_bool(argv[2]);
1164                 return OK;
1165         }
1167         if (!strcmp(argv[0], "line-number-interval")) {
1168                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1169                 return OK;
1170         }
1172         if (!strcmp(argv[0], "author-width")) {
1173                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1174                 return OK;
1175         }
1177         if (!strcmp(argv[0], "tab-size")) {
1178                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1179                 return OK;
1180         }
1182         if (!strcmp(argv[0], "commit-encoding")) {
1183                 const char *arg = argv[2];
1184                 int arglen = strlen(arg);
1186                 switch (arg[0]) {
1187                 case '"':
1188                 case '\'':
1189                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1190                                 config_msg = "Unmatched quotation";
1191                                 return ERR;
1192                         }
1193                         arg += 1; arglen -= 2;
1194                 default:
1195                         string_ncopy(opt_encoding, arg, strlen(arg));
1196                         return OK;
1197                 }
1198         }
1200         config_msg = "Unknown variable name";
1201         return ERR;
1204 /* Wants: mode request key */
1205 static int
1206 option_bind_command(int argc, const char *argv[])
1208         enum request request;
1209         int keymap;
1210         int key;
1212         if (argc < 3) {
1213                 config_msg = "Wrong number of arguments given to bind command";
1214                 return ERR;
1215         }
1217         if (set_keymap(&keymap, argv[0]) == ERR) {
1218                 config_msg = "Unknown key map";
1219                 return ERR;
1220         }
1222         key = get_key_value(argv[1]);
1223         if (key == ERR) {
1224                 config_msg = "Unknown key";
1225                 return ERR;
1226         }
1228         request = get_request(argv[2]);
1229         if (request == REQ_NONE) {
1230                 const char *obsolete[] = { "cherry-pick" };
1231                 size_t namelen = strlen(argv[2]);
1232                 int i;
1234                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1235                         if (namelen == strlen(obsolete[i]) &&
1236                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1237                                 config_msg = "Obsolete request name";
1238                                 return ERR;
1239                         }
1240                 }
1241         }
1242         if (request == REQ_NONE && *argv[2]++ == '!')
1243                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1244         if (request == REQ_NONE) {
1245                 config_msg = "Unknown request name";
1246                 return ERR;
1247         }
1249         add_keybinding(keymap, request, key);
1251         return OK;
1254 static int
1255 set_option(const char *opt, char *value)
1257         const char *argv[SIZEOF_ARG];
1258         int valuelen;
1259         int argc = 0;
1261         /* Tokenize */
1262         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1263                 argv[argc++] = value;
1264                 value += valuelen;
1266                 /* Nothing more to tokenize or last available token. */
1267                 if (!*value || argc >= ARRAY_SIZE(argv))
1268                         break;
1270                 *value++ = 0;
1271                 while (isspace(*value))
1272                         value++;
1273         }
1275         if (!strcmp(opt, "color"))
1276                 return option_color_command(argc, argv);
1278         if (!strcmp(opt, "set"))
1279                 return option_set_command(argc, argv);
1281         if (!strcmp(opt, "bind"))
1282                 return option_bind_command(argc, argv);
1284         config_msg = "Unknown option command";
1285         return ERR;
1288 static int
1289 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1291         int status = OK;
1293         config_lineno++;
1294         config_msg = "Internal error";
1296         /* Check for comment markers, since read_properties() will
1297          * only ensure opt and value are split at first " \t". */
1298         optlen = strcspn(opt, "#");
1299         if (optlen == 0)
1300                 return OK;
1302         if (opt[optlen] != 0) {
1303                 config_msg = "No option value";
1304                 status = ERR;
1306         }  else {
1307                 /* Look for comment endings in the value. */
1308                 size_t len = strcspn(value, "#");
1310                 if (len < valuelen) {
1311                         valuelen = len;
1312                         value[valuelen] = 0;
1313                 }
1315                 status = set_option(opt, value);
1316         }
1318         if (status == ERR) {
1319                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1320                         config_lineno, (int) optlen, opt, config_msg);
1321                 config_errors = TRUE;
1322         }
1324         /* Always keep going if errors are encountered. */
1325         return OK;
1328 static void
1329 load_option_file(const char *path)
1331         FILE *file;
1333         /* It's ok that the file doesn't exist. */
1334         file = fopen(path, "r");
1335         if (!file)
1336                 return;
1338         config_lineno = 0;
1339         config_errors = FALSE;
1341         if (read_properties(file, " \t", read_option) == ERR ||
1342             config_errors == TRUE)
1343                 fprintf(stderr, "Errors while loading %s.\n", path);
1346 static int
1347 load_options(void)
1349         const char *home = getenv("HOME");
1350         const char *tigrc_user = getenv("TIGRC_USER");
1351         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1352         char buf[SIZEOF_STR];
1354         add_builtin_run_requests();
1356         if (!tigrc_system) {
1357                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1358                         return ERR;
1359                 tigrc_system = buf;
1360         }
1361         load_option_file(tigrc_system);
1363         if (!tigrc_user) {
1364                 if (!home || !string_format(buf, "%s/.tigrc", home))
1365                         return ERR;
1366                 tigrc_user = buf;
1367         }
1368         load_option_file(tigrc_user);
1370         return OK;
1374 /*
1375  * The viewer
1376  */
1378 struct view;
1379 struct view_ops;
1381 /* The display array of active views and the index of the current view. */
1382 static struct view *display[2];
1383 static unsigned int current_view;
1385 /* Reading from the prompt? */
1386 static bool input_mode = FALSE;
1388 #define foreach_displayed_view(view, i) \
1389         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1391 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1393 /* Current head and commit ID */
1394 static char ref_blob[SIZEOF_REF]        = "";
1395 static char ref_commit[SIZEOF_REF]      = "HEAD";
1396 static char ref_head[SIZEOF_REF]        = "HEAD";
1398 struct view {
1399         const char *name;       /* View name */
1400         const char *cmd_fmt;    /* Default command line format */
1401         const char *cmd_env;    /* Command line set via environment */
1402         const char *id;         /* Points to either of ref_{head,commit,blob} */
1404         struct view_ops *ops;   /* View operations */
1406         enum keymap keymap;     /* What keymap does this view have */
1407         bool git_dir;           /* Whether the view requires a git directory. */
1409         char cmd[SIZEOF_STR];   /* Command buffer */
1410         char ref[SIZEOF_REF];   /* Hovered commit reference */
1411         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1413         int height, width;      /* The width and height of the main window */
1414         WINDOW *win;            /* The main window */
1415         WINDOW *title;          /* The title window living below the main window */
1417         /* Navigation */
1418         unsigned long offset;   /* Offset of the window top */
1419         unsigned long lineno;   /* Current line number */
1421         /* Searching */
1422         char grep[SIZEOF_STR];  /* Search string */
1423         regex_t *regex;         /* Pre-compiled regex */
1425         /* If non-NULL, points to the view that opened this view. If this view
1426          * is closed tig will switch back to the parent view. */
1427         struct view *parent;
1429         /* Buffering */
1430         size_t lines;           /* Total number of lines */
1431         struct line *line;      /* Line index */
1432         size_t line_alloc;      /* Total number of allocated lines */
1433         size_t line_size;       /* Total number of used lines */
1434         unsigned int digits;    /* Number of digits in the lines member. */
1436         /* Drawing */
1437         struct line *curline;   /* Line currently being drawn. */
1438         enum line_type curtype; /* Attribute currently used for drawing. */
1439         unsigned long col;      /* Column when drawing. */
1441         /* Loading */
1442         FILE *pipe;
1443         time_t start_time;
1444 };
1446 struct view_ops {
1447         /* What type of content being displayed. Used in the title bar. */
1448         const char *type;
1449         /* Open and reads in all view content. */
1450         bool (*open)(struct view *view);
1451         /* Read one line; updates view->line. */
1452         bool (*read)(struct view *view, char *data);
1453         /* Draw one line; @lineno must be < view->height. */
1454         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1455         /* Depending on view handle a special requests. */
1456         enum request (*request)(struct view *view, enum request request, struct line *line);
1457         /* Search for regex in a line. */
1458         bool (*grep)(struct view *view, struct line *line);
1459         /* Select line */
1460         void (*select)(struct view *view, struct line *line);
1461 };
1463 static struct view_ops blame_ops;
1464 static struct view_ops blob_ops;
1465 static struct view_ops help_ops;
1466 static struct view_ops log_ops;
1467 static struct view_ops main_ops;
1468 static struct view_ops pager_ops;
1469 static struct view_ops stage_ops;
1470 static struct view_ops status_ops;
1471 static struct view_ops tree_ops;
1473 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1474         { name, cmd, #env, ref, ops, map, git }
1476 #define VIEW_(id, name, ops, git, ref) \
1477         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1480 static struct view views[] = {
1481         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1482         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1483         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1484         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1485         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1486         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1487         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1488         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1489         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1490         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1491 };
1493 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1494 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1496 #define foreach_view(view, i) \
1497         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1499 #define view_is_displayed(view) \
1500         (view == display[0] || view == display[1])
1503 enum line_graphic {
1504         LINE_GRAPHIC_VLINE
1505 };
1507 static int line_graphics[] = {
1508         /* LINE_GRAPHIC_VLINE: */ '|'
1509 };
1511 static inline void
1512 set_view_attr(struct view *view, enum line_type type)
1514         if (!view->curline->selected && view->curtype != type) {
1515                 wattrset(view->win, get_line_attr(type));
1516                 wchgat(view->win, -1, 0, type, NULL);
1517                 view->curtype = type;
1518         }
1521 static int
1522 draw_chars(struct view *view, enum line_type type, const char *string,
1523            int max_len, bool use_tilde)
1525         int len = 0;
1526         int col = 0;
1527         int trimmed = FALSE;
1529         if (max_len <= 0)
1530                 return 0;
1532         if (opt_utf8) {
1533                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1534         } else {
1535                 col = len = strlen(string);
1536                 if (len > max_len) {
1537                         if (use_tilde) {
1538                                 max_len -= 1;
1539                         }
1540                         col = len = max_len;
1541                         trimmed = TRUE;
1542                 }
1543         }
1545         set_view_attr(view, type);
1546         waddnstr(view->win, string, len);
1547         if (trimmed && use_tilde) {
1548                 set_view_attr(view, LINE_DELIMITER);
1549                 waddch(view->win, '~');
1550                 col++;
1551         }
1553         return col;
1556 static int
1557 draw_space(struct view *view, enum line_type type, int max, int spaces)
1559         static char space[] = "                    ";
1560         int col = 0;
1562         spaces = MIN(max, spaces);
1564         while (spaces > 0) {
1565                 int len = MIN(spaces, sizeof(space) - 1);
1567                 col += draw_chars(view, type, space, spaces, FALSE);
1568                 spaces -= len;
1569         }
1571         return col;
1574 static bool
1575 draw_lineno(struct view *view, unsigned int lineno)
1577         char number[10];
1578         int digits3 = view->digits < 3 ? 3 : view->digits;
1579         int max_number = MIN(digits3, STRING_SIZE(number));
1580         int max = view->width - view->col;
1581         int col;
1583         if (max < max_number)
1584                 max_number = max;
1586         lineno += view->offset + 1;
1587         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1588                 static char fmt[] = "%1ld";
1590                 if (view->digits <= 9)
1591                         fmt[1] = '0' + digits3;
1593                 if (!string_format(number, fmt, lineno))
1594                         number[0] = 0;
1595                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1596         } else {
1597                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1598         }
1600         if (col < max) {
1601                 set_view_attr(view, LINE_DEFAULT);
1602                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1603                 col++;
1604         }
1606         if (col < max)
1607                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1608         view->col += col;
1610         return view->width - view->col <= 0;
1613 static bool
1614 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1616         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1617         return view->width - view->col <= 0;
1620 static bool
1621 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1623         int max = view->width - view->col;
1624         int i;
1626         if (max < size)
1627                 size = max;
1629         set_view_attr(view, type);
1630         /* Using waddch() instead of waddnstr() ensures that
1631          * they'll be rendered correctly for the cursor line. */
1632         for (i = 0; i < size; i++)
1633                 waddch(view->win, graphic[i]);
1635         view->col += size;
1636         if (size < max) {
1637                 waddch(view->win, ' ');
1638                 view->col++;
1639         }
1641         return view->width - view->col <= 0;
1644 static bool
1645 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1647         int max = MIN(view->width - view->col, len);
1648         int col;
1650         if (text)
1651                 col = draw_chars(view, type, text, max - 1, trim);
1652         else
1653                 col = draw_space(view, type, max - 1, max - 1);
1655         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1656         return view->width - view->col <= 0;
1659 static bool
1660 draw_date(struct view *view, struct tm *time)
1662         char buf[DATE_COLS];
1663         char *date;
1664         int timelen = 0;
1666         if (time)
1667                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1668         date = timelen ? buf : NULL;
1670         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1673 static bool
1674 draw_view_line(struct view *view, unsigned int lineno)
1676         struct line *line;
1677         bool selected = (view->offset + lineno == view->lineno);
1678         bool draw_ok;
1680         assert(view_is_displayed(view));
1682         if (view->offset + lineno >= view->lines)
1683                 return FALSE;
1685         line = &view->line[view->offset + lineno];
1687         wmove(view->win, lineno, 0);
1688         view->col = 0;
1689         view->curline = line;
1690         view->curtype = LINE_NONE;
1691         line->selected = FALSE;
1693         if (selected) {
1694                 set_view_attr(view, LINE_CURSOR);
1695                 line->selected = TRUE;
1696                 view->ops->select(view, line);
1697         } else if (line->selected) {
1698                 wclrtoeol(view->win);
1699         }
1701         scrollok(view->win, FALSE);
1702         draw_ok = view->ops->draw(view, line, lineno);
1703         scrollok(view->win, TRUE);
1705         return draw_ok;
1708 static void
1709 redraw_view_dirty(struct view *view)
1711         bool dirty = FALSE;
1712         int lineno;
1714         for (lineno = 0; lineno < view->height; lineno++) {
1715                 struct line *line = &view->line[view->offset + lineno];
1717                 if (!line->dirty)
1718                         continue;
1719                 line->dirty = 0;
1720                 dirty = TRUE;
1721                 if (!draw_view_line(view, lineno))
1722                         break;
1723         }
1725         if (!dirty)
1726                 return;
1727         redrawwin(view->win);
1728         if (input_mode)
1729                 wnoutrefresh(view->win);
1730         else
1731                 wrefresh(view->win);
1734 static void
1735 redraw_view_from(struct view *view, int lineno)
1737         assert(0 <= lineno && lineno < view->height);
1739         for (; lineno < view->height; lineno++) {
1740                 if (!draw_view_line(view, lineno))
1741                         break;
1742         }
1744         redrawwin(view->win);
1745         if (input_mode)
1746                 wnoutrefresh(view->win);
1747         else
1748                 wrefresh(view->win);
1751 static void
1752 redraw_view(struct view *view)
1754         wclear(view->win);
1755         redraw_view_from(view, 0);
1759 static void
1760 update_view_title(struct view *view)
1762         char buf[SIZEOF_STR];
1763         char state[SIZEOF_STR];
1764         size_t bufpos = 0, statelen = 0;
1766         assert(view_is_displayed(view));
1768         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1769                 unsigned int view_lines = view->offset + view->height;
1770                 unsigned int lines = view->lines
1771                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1772                                    : 0;
1774                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1775                                    view->ops->type,
1776                                    view->lineno + 1,
1777                                    view->lines,
1778                                    lines);
1780                 if (view->pipe) {
1781                         time_t secs = time(NULL) - view->start_time;
1783                         /* Three git seconds are a long time ... */
1784                         if (secs > 2)
1785                                 string_format_from(state, &statelen, " %lds", secs);
1786                 }
1787         }
1789         string_format_from(buf, &bufpos, "[%s]", view->name);
1790         if (*view->ref && bufpos < view->width) {
1791                 size_t refsize = strlen(view->ref);
1792                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1794                 if (minsize < view->width)
1795                         refsize = view->width - minsize + 7;
1796                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1797         }
1799         if (statelen && bufpos < view->width) {
1800                 string_format_from(buf, &bufpos, " %s", state);
1801         }
1803         if (view == display[current_view])
1804                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1805         else
1806                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1808         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1809         wclrtoeol(view->title);
1810         wmove(view->title, 0, view->width - 1);
1812         if (input_mode)
1813                 wnoutrefresh(view->title);
1814         else
1815                 wrefresh(view->title);
1818 static void
1819 resize_display(void)
1821         int offset, i;
1822         struct view *base = display[0];
1823         struct view *view = display[1] ? display[1] : display[0];
1825         /* Setup window dimensions */
1827         getmaxyx(stdscr, base->height, base->width);
1829         /* Make room for the status window. */
1830         base->height -= 1;
1832         if (view != base) {
1833                 /* Horizontal split. */
1834                 view->width   = base->width;
1835                 view->height  = SCALE_SPLIT_VIEW(base->height);
1836                 base->height -= view->height;
1838                 /* Make room for the title bar. */
1839                 view->height -= 1;
1840         }
1842         /* Make room for the title bar. */
1843         base->height -= 1;
1845         offset = 0;
1847         foreach_displayed_view (view, i) {
1848                 if (!view->win) {
1849                         view->win = newwin(view->height, 0, offset, 0);
1850                         if (!view->win)
1851                                 die("Failed to create %s view", view->name);
1853                         scrollok(view->win, TRUE);
1855                         view->title = newwin(1, 0, offset + view->height, 0);
1856                         if (!view->title)
1857                                 die("Failed to create title window");
1859                 } else {
1860                         wresize(view->win, view->height, view->width);
1861                         mvwin(view->win,   offset, 0);
1862                         mvwin(view->title, offset + view->height, 0);
1863                 }
1865                 offset += view->height + 1;
1866         }
1869 static void
1870 redraw_display(void)
1872         struct view *view;
1873         int i;
1875         foreach_displayed_view (view, i) {
1876                 redraw_view(view);
1877                 update_view_title(view);
1878         }
1881 static void
1882 update_display_cursor(struct view *view)
1884         /* Move the cursor to the right-most column of the cursor line.
1885          *
1886          * XXX: This could turn out to be a bit expensive, but it ensures that
1887          * the cursor does not jump around. */
1888         if (view->lines) {
1889                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1890                 wrefresh(view->win);
1891         }
1894 /*
1895  * Navigation
1896  */
1898 /* Scrolling backend */
1899 static void
1900 do_scroll_view(struct view *view, int lines)
1902         bool redraw_current_line = FALSE;
1904         /* The rendering expects the new offset. */
1905         view->offset += lines;
1907         assert(0 <= view->offset && view->offset < view->lines);
1908         assert(lines);
1910         /* Move current line into the view. */
1911         if (view->lineno < view->offset) {
1912                 view->lineno = view->offset;
1913                 redraw_current_line = TRUE;
1914         } else if (view->lineno >= view->offset + view->height) {
1915                 view->lineno = view->offset + view->height - 1;
1916                 redraw_current_line = TRUE;
1917         }
1919         assert(view->offset <= view->lineno && view->lineno < view->lines);
1921         /* Redraw the whole screen if scrolling is pointless. */
1922         if (view->height < ABS(lines)) {
1923                 redraw_view(view);
1925         } else {
1926                 int line = lines > 0 ? view->height - lines : 0;
1927                 int end = line + ABS(lines);
1929                 wscrl(view->win, lines);
1931                 for (; line < end; line++) {
1932                         if (!draw_view_line(view, line))
1933                                 break;
1934                 }
1936                 if (redraw_current_line)
1937                         draw_view_line(view, view->lineno - view->offset);
1938         }
1940         redrawwin(view->win);
1941         wrefresh(view->win);
1942         report("");
1945 /* Scroll frontend */
1946 static void
1947 scroll_view(struct view *view, enum request request)
1949         int lines = 1;
1951         assert(view_is_displayed(view));
1953         switch (request) {
1954         case REQ_SCROLL_PAGE_DOWN:
1955                 lines = view->height;
1956         case REQ_SCROLL_LINE_DOWN:
1957                 if (view->offset + lines > view->lines)
1958                         lines = view->lines - view->offset;
1960                 if (lines == 0 || view->offset + view->height >= view->lines) {
1961                         report("Cannot scroll beyond the last line");
1962                         return;
1963                 }
1964                 break;
1966         case REQ_SCROLL_PAGE_UP:
1967                 lines = view->height;
1968         case REQ_SCROLL_LINE_UP:
1969                 if (lines > view->offset)
1970                         lines = view->offset;
1972                 if (lines == 0) {
1973                         report("Cannot scroll beyond the first line");
1974                         return;
1975                 }
1977                 lines = -lines;
1978                 break;
1980         default:
1981                 die("request %d not handled in switch", request);
1982         }
1984         do_scroll_view(view, lines);
1987 /* Cursor moving */
1988 static void
1989 move_view(struct view *view, enum request request)
1991         int scroll_steps = 0;
1992         int steps;
1994         switch (request) {
1995         case REQ_MOVE_FIRST_LINE:
1996                 steps = -view->lineno;
1997                 break;
1999         case REQ_MOVE_LAST_LINE:
2000                 steps = view->lines - view->lineno - 1;
2001                 break;
2003         case REQ_MOVE_PAGE_UP:
2004                 steps = view->height > view->lineno
2005                       ? -view->lineno : -view->height;
2006                 break;
2008         case REQ_MOVE_PAGE_DOWN:
2009                 steps = view->lineno + view->height >= view->lines
2010                       ? view->lines - view->lineno - 1 : view->height;
2011                 break;
2013         case REQ_MOVE_UP:
2014                 steps = -1;
2015                 break;
2017         case REQ_MOVE_DOWN:
2018                 steps = 1;
2019                 break;
2021         default:
2022                 die("request %d not handled in switch", request);
2023         }
2025         if (steps <= 0 && view->lineno == 0) {
2026                 report("Cannot move beyond the first line");
2027                 return;
2029         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2030                 report("Cannot move beyond the last line");
2031                 return;
2032         }
2034         /* Move the current line */
2035         view->lineno += steps;
2036         assert(0 <= view->lineno && view->lineno < view->lines);
2038         /* Check whether the view needs to be scrolled */
2039         if (view->lineno < view->offset ||
2040             view->lineno >= view->offset + view->height) {
2041                 scroll_steps = steps;
2042                 if (steps < 0 && -steps > view->offset) {
2043                         scroll_steps = -view->offset;
2045                 } else if (steps > 0) {
2046                         if (view->lineno == view->lines - 1 &&
2047                             view->lines > view->height) {
2048                                 scroll_steps = view->lines - view->offset - 1;
2049                                 if (scroll_steps >= view->height)
2050                                         scroll_steps -= view->height - 1;
2051                         }
2052                 }
2053         }
2055         if (!view_is_displayed(view)) {
2056                 view->offset += scroll_steps;
2057                 assert(0 <= view->offset && view->offset < view->lines);
2058                 view->ops->select(view, &view->line[view->lineno]);
2059                 return;
2060         }
2062         /* Repaint the old "current" line if we be scrolling */
2063         if (ABS(steps) < view->height)
2064                 draw_view_line(view, view->lineno - steps - view->offset);
2066         if (scroll_steps) {
2067                 do_scroll_view(view, scroll_steps);
2068                 return;
2069         }
2071         /* Draw the current line */
2072         draw_view_line(view, view->lineno - view->offset);
2074         redrawwin(view->win);
2075         wrefresh(view->win);
2076         report("");
2080 /*
2081  * Searching
2082  */
2084 static void search_view(struct view *view, enum request request);
2086 static bool
2087 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2089         assert(view_is_displayed(view));
2091         if (!view->ops->grep(view, line))
2092                 return FALSE;
2094         if (lineno - view->offset >= view->height) {
2095                 view->offset = lineno;
2096                 view->lineno = lineno;
2097                 redraw_view(view);
2099         } else {
2100                 unsigned long old_lineno = view->lineno - view->offset;
2102                 view->lineno = lineno;
2103                 draw_view_line(view, old_lineno);
2105                 draw_view_line(view, view->lineno - view->offset);
2106                 redrawwin(view->win);
2107                 wrefresh(view->win);
2108         }
2110         report("Line %ld matches '%s'", lineno + 1, view->grep);
2111         return TRUE;
2114 static void
2115 find_next(struct view *view, enum request request)
2117         unsigned long lineno = view->lineno;
2118         int direction;
2120         if (!*view->grep) {
2121                 if (!*opt_search)
2122                         report("No previous search");
2123                 else
2124                         search_view(view, request);
2125                 return;
2126         }
2128         switch (request) {
2129         case REQ_SEARCH:
2130         case REQ_FIND_NEXT:
2131                 direction = 1;
2132                 break;
2134         case REQ_SEARCH_BACK:
2135         case REQ_FIND_PREV:
2136                 direction = -1;
2137                 break;
2139         default:
2140                 return;
2141         }
2143         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2144                 lineno += direction;
2146         /* Note, lineno is unsigned long so will wrap around in which case it
2147          * will become bigger than view->lines. */
2148         for (; lineno < view->lines; lineno += direction) {
2149                 struct line *line = &view->line[lineno];
2151                 if (find_next_line(view, lineno, line))
2152                         return;
2153         }
2155         report("No match found for '%s'", view->grep);
2158 static void
2159 search_view(struct view *view, enum request request)
2161         int regex_err;
2163         if (view->regex) {
2164                 regfree(view->regex);
2165                 *view->grep = 0;
2166         } else {
2167                 view->regex = calloc(1, sizeof(*view->regex));
2168                 if (!view->regex)
2169                         return;
2170         }
2172         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2173         if (regex_err != 0) {
2174                 char buf[SIZEOF_STR] = "unknown error";
2176                 regerror(regex_err, view->regex, buf, sizeof(buf));
2177                 report("Search failed: %s", buf);
2178                 return;
2179         }
2181         string_copy(view->grep, opt_search);
2183         find_next(view, request);
2186 /*
2187  * Incremental updating
2188  */
2190 static void
2191 reset_view(struct view *view)
2193         int i;
2195         for (i = 0; i < view->lines; i++)
2196                 free(view->line[i].data);
2197         free(view->line);
2199         view->line = NULL;
2200         view->offset = 0;
2201         view->lines  = 0;
2202         view->lineno = 0;
2203         view->line_size = 0;
2204         view->line_alloc = 0;
2205         view->vid[0] = 0;
2208 static void
2209 end_update(struct view *view, bool force)
2211         if (!view->pipe)
2212                 return;
2213         while (!view->ops->read(view, NULL))
2214                 if (!force)
2215                         return;
2216         set_nonblocking_input(FALSE);
2217         if (view->pipe == stdin)
2218                 fclose(view->pipe);
2219         else
2220                 pclose(view->pipe);
2221         view->pipe = NULL;
2224 static bool
2225 begin_update(struct view *view, bool refresh)
2227         if (opt_cmd[0]) {
2228                 string_copy(view->cmd, opt_cmd);
2229                 opt_cmd[0] = 0;
2230                 /* When running random commands, initially show the
2231                  * command in the title. However, it maybe later be
2232                  * overwritten if a commit line is selected. */
2233                 if (view == VIEW(REQ_VIEW_PAGER))
2234                         string_copy(view->ref, view->cmd);
2235                 else
2236                         view->ref[0] = 0;
2238         } else if (view == VIEW(REQ_VIEW_TREE)) {
2239                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2240                 char path[SIZEOF_STR];
2242                 if (strcmp(view->vid, view->id))
2243                         opt_path[0] = path[0] = 0;
2244                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2245                         return FALSE;
2247                 if (!string_format(view->cmd, format, view->id, path))
2248                         return FALSE;
2250         } else if (!refresh) {
2251                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2252                 const char *id = view->id;
2254                 if (!string_format(view->cmd, format, id, id, id, id, id))
2255                         return FALSE;
2257                 /* Put the current ref_* value to the view title ref
2258                  * member. This is needed by the blob view. Most other
2259                  * views sets it automatically after loading because the
2260                  * first line is a commit line. */
2261                 string_copy_rev(view->ref, view->id);
2262         }
2264         /* Special case for the pager view. */
2265         if (opt_pipe) {
2266                 view->pipe = opt_pipe;
2267                 opt_pipe = NULL;
2268         } else {
2269                 view->pipe = popen(view->cmd, "r");
2270         }
2272         if (!view->pipe)
2273                 return FALSE;
2275         set_nonblocking_input(TRUE);
2276         reset_view(view);
2277         string_copy_rev(view->vid, view->id);
2279         view->start_time = time(NULL);
2281         return TRUE;
2284 #define ITEM_CHUNK_SIZE 256
2285 static void *
2286 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2288         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2289         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2291         if (mem == NULL || num_chunks != num_chunks_new) {
2292                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2293                 mem = realloc(mem, *size * item_size);
2294         }
2296         return mem;
2299 static struct line *
2300 realloc_lines(struct view *view, size_t line_size)
2302         size_t alloc = view->line_alloc;
2303         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2304                                          sizeof(*view->line));
2306         if (!tmp)
2307                 return NULL;
2309         view->line = tmp;
2310         view->line_alloc = alloc;
2311         view->line_size = line_size;
2312         return view->line;
2315 static bool
2316 update_view(struct view *view)
2318         char in_buffer[BUFSIZ];
2319         char out_buffer[BUFSIZ * 2];
2320         char *line;
2321         /* The number of lines to read. If too low it will cause too much
2322          * redrawing (and possible flickering), if too high responsiveness
2323          * will suffer. */
2324         unsigned long lines = view->height;
2325         int redraw_from = -1;
2327         if (!view->pipe)
2328                 return TRUE;
2330         /* Only redraw if lines are visible. */
2331         if (view->offset + view->height >= view->lines)
2332                 redraw_from = view->lines - view->offset;
2334         /* FIXME: This is probably not perfect for backgrounded views. */
2335         if (!realloc_lines(view, view->lines + lines))
2336                 goto alloc_error;
2338         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2339                 size_t linelen = strlen(line);
2341                 if (linelen)
2342                         line[linelen - 1] = 0;
2344                 if (opt_iconv != ICONV_NONE) {
2345                         ICONV_CONST char *inbuf = line;
2346                         size_t inlen = linelen;
2348                         char *outbuf = out_buffer;
2349                         size_t outlen = sizeof(out_buffer);
2351                         size_t ret;
2353                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2354                         if (ret != (size_t) -1) {
2355                                 line = out_buffer;
2356                                 linelen = strlen(out_buffer);
2357                         }
2358                 }
2360                 if (!view->ops->read(view, line))
2361                         goto alloc_error;
2363                 if (lines-- == 1)
2364                         break;
2365         }
2367         {
2368                 int digits;
2370                 lines = view->lines;
2371                 for (digits = 0; lines; digits++)
2372                         lines /= 10;
2374                 /* Keep the displayed view in sync with line number scaling. */
2375                 if (digits != view->digits) {
2376                         view->digits = digits;
2377                         redraw_from = 0;
2378                 }
2379         }
2381         if (ferror(view->pipe) && errno != 0) {
2382                 report("Failed to read: %s", strerror(errno));
2383                 end_update(view, TRUE);
2385         } else if (feof(view->pipe)) {
2386                 report("");
2387                 end_update(view, FALSE);
2388         }
2390         if (view == VIEW(REQ_VIEW_TREE)) {
2391                 /* Clear the view and redraw everything since the tree sorting
2392                  * might have rearranged things. */
2393                 redraw_view(view);
2395         } else if (redraw_from >= 0) {
2396                 /* If this is an incremental update, redraw the previous line
2397                  * since for commits some members could have changed when
2398                  * loading the main view. */
2399                 if (redraw_from > 0)
2400                         redraw_from--;
2402                 /* Since revision graph visualization requires knowledge
2403                  * about the parent commit, it causes a further one-off
2404                  * needed to be redrawn for incremental updates. */
2405                 if (redraw_from > 0 && opt_rev_graph)
2406                         redraw_from--;
2408                 /* Incrementally draw avoids flickering. */
2409                 redraw_view_from(view, redraw_from);
2410         }
2412         if (view == VIEW(REQ_VIEW_BLAME))
2413                 redraw_view_dirty(view);
2415         /* Update the title _after_ the redraw so that if the redraw picks up a
2416          * commit reference in view->ref it'll be available here. */
2417         update_view_title(view);
2418         return TRUE;
2420 alloc_error:
2421         report("Allocation failure");
2422         end_update(view, TRUE);
2423         return FALSE;
2426 static struct line *
2427 add_line_data(struct view *view, void *data, enum line_type type)
2429         struct line *line = &view->line[view->lines++];
2431         memset(line, 0, sizeof(*line));
2432         line->type = type;
2433         line->data = data;
2435         return line;
2438 static struct line *
2439 add_line_text(struct view *view, const char *text, enum line_type type)
2441         char *data = text ? strdup(text) : NULL;
2443         return data ? add_line_data(view, data, type) : NULL;
2447 /*
2448  * View opening
2449  */
2451 enum open_flags {
2452         OPEN_DEFAULT = 0,       /* Use default view switching. */
2453         OPEN_SPLIT = 1,         /* Split current view. */
2454         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2455         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2456         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2457         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2458 };
2460 static void
2461 open_view(struct view *prev, enum request request, enum open_flags flags)
2463         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2464         bool split = !!(flags & OPEN_SPLIT);
2465         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2466         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2467         struct view *view = VIEW(request);
2468         int nviews = displayed_views();
2469         struct view *base_view = display[0];
2471         if (view == prev && nviews == 1 && !reload) {
2472                 report("Already in %s view", view->name);
2473                 return;
2474         }
2476         if (view->git_dir && !opt_git_dir[0]) {
2477                 report("The %s view is disabled in pager view", view->name);
2478                 return;
2479         }
2481         if (split) {
2482                 display[1] = view;
2483                 if (!backgrounded)
2484                         current_view = 1;
2485         } else if (!nomaximize) {
2486                 /* Maximize the current view. */
2487                 memset(display, 0, sizeof(display));
2488                 current_view = 0;
2489                 display[current_view] = view;
2490         }
2492         /* Resize the view when switching between split- and full-screen,
2493          * or when switching between two different full-screen views. */
2494         if (nviews != displayed_views() ||
2495             (nviews == 1 && base_view != display[0]))
2496                 resize_display();
2498         if (view->pipe)
2499                 end_update(view, TRUE);
2501         if (view->ops->open) {
2502                 if (!view->ops->open(view)) {
2503                         report("Failed to load %s view", view->name);
2504                         return;
2505                 }
2507         } else if ((reload || strcmp(view->vid, view->id)) &&
2508                    !begin_update(view, flags & OPEN_REFRESH)) {
2509                 report("Failed to load %s view", view->name);
2510                 return;
2511         }
2513         if (split && prev->lineno - prev->offset >= prev->height) {
2514                 /* Take the title line into account. */
2515                 int lines = prev->lineno - prev->offset - prev->height + 1;
2517                 /* Scroll the view that was split if the current line is
2518                  * outside the new limited view. */
2519                 do_scroll_view(prev, lines);
2520         }
2522         if (prev && view != prev) {
2523                 if (split && !backgrounded) {
2524                         /* "Blur" the previous view. */
2525                         update_view_title(prev);
2526                 }
2528                 view->parent = prev;
2529         }
2531         if (view->pipe && view->lines == 0) {
2532                 /* Clear the old view and let the incremental updating refill
2533                  * the screen. */
2534                 werase(view->win);
2535                 report("");
2536         } else if (view_is_displayed(view)) {
2537                 redraw_view(view);
2538                 report("");
2539         }
2541         /* If the view is backgrounded the above calls to report()
2542          * won't redraw the view title. */
2543         if (backgrounded)
2544                 update_view_title(view);
2547 static bool
2548 run_confirm(const char *cmd, const char *prompt)
2550         bool confirmation = prompt_yesno(prompt);
2552         if (confirmation)
2553                 system(cmd);
2555         return confirmation;
2558 static void
2559 open_external_viewer(const char *cmd)
2561         def_prog_mode();           /* save current tty modes */
2562         endwin();                  /* restore original tty modes */
2563         system(cmd);
2564         fprintf(stderr, "Press Enter to continue");
2565         getc(stdin);
2566         reset_prog_mode();
2567         redraw_display();
2570 static void
2571 open_mergetool(const char *file)
2573         char cmd[SIZEOF_STR];
2574         char file_sq[SIZEOF_STR];
2576         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2577             string_format(cmd, "git mergetool %s", file_sq)) {
2578                 open_external_viewer(cmd);
2579         }
2582 static void
2583 open_editor(bool from_root, const char *file)
2585         char cmd[SIZEOF_STR];
2586         char file_sq[SIZEOF_STR];
2587         const char *editor;
2588         char *prefix = from_root ? opt_cdup : "";
2590         editor = getenv("GIT_EDITOR");
2591         if (!editor && *opt_editor)
2592                 editor = opt_editor;
2593         if (!editor)
2594                 editor = getenv("VISUAL");
2595         if (!editor)
2596                 editor = getenv("EDITOR");
2597         if (!editor)
2598                 editor = "vi";
2600         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2601             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2602                 open_external_viewer(cmd);
2603         }
2606 static void
2607 open_run_request(enum request request)
2609         struct run_request *req = get_run_request(request);
2610         char buf[SIZEOF_STR * 2];
2611         size_t bufpos;
2612         char *cmd;
2614         if (!req) {
2615                 report("Unknown run request");
2616                 return;
2617         }
2619         bufpos = 0;
2620         cmd = req->cmd;
2622         while (cmd) {
2623                 char *next = strstr(cmd, "%(");
2624                 int len = next - cmd;
2625                 char *value;
2627                 if (!next) {
2628                         len = strlen(cmd);
2629                         value = "";
2631                 } else if (!strncmp(next, "%(head)", 7)) {
2632                         value = ref_head;
2634                 } else if (!strncmp(next, "%(commit)", 9)) {
2635                         value = ref_commit;
2637                 } else if (!strncmp(next, "%(blob)", 7)) {
2638                         value = ref_blob;
2640                 } else {
2641                         report("Unknown replacement in run request: `%s`", req->cmd);
2642                         return;
2643                 }
2645                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2646                         return;
2648                 if (next)
2649                         next = strchr(next, ')') + 1;
2650                 cmd = next;
2651         }
2653         open_external_viewer(buf);
2656 /*
2657  * User request switch noodle
2658  */
2660 static int
2661 view_driver(struct view *view, enum request request)
2663         int i;
2665         if (request == REQ_NONE) {
2666                 doupdate();
2667                 return TRUE;
2668         }
2670         if (request > REQ_NONE) {
2671                 open_run_request(request);
2672                 /* FIXME: When all views can refresh always do this. */
2673                 if (view == VIEW(REQ_VIEW_STATUS) ||
2674                     view == VIEW(REQ_VIEW_MAIN) ||
2675                     view == VIEW(REQ_VIEW_LOG) ||
2676                     view == VIEW(REQ_VIEW_STAGE))
2677                         request = REQ_REFRESH;
2678                 else
2679                         return TRUE;
2680         }
2682         if (view && view->lines) {
2683                 request = view->ops->request(view, request, &view->line[view->lineno]);
2684                 if (request == REQ_NONE)
2685                         return TRUE;
2686         }
2688         switch (request) {
2689         case REQ_MOVE_UP:
2690         case REQ_MOVE_DOWN:
2691         case REQ_MOVE_PAGE_UP:
2692         case REQ_MOVE_PAGE_DOWN:
2693         case REQ_MOVE_FIRST_LINE:
2694         case REQ_MOVE_LAST_LINE:
2695                 move_view(view, request);
2696                 break;
2698         case REQ_SCROLL_LINE_DOWN:
2699         case REQ_SCROLL_LINE_UP:
2700         case REQ_SCROLL_PAGE_DOWN:
2701         case REQ_SCROLL_PAGE_UP:
2702                 scroll_view(view, request);
2703                 break;
2705         case REQ_VIEW_BLAME:
2706                 if (!opt_file[0]) {
2707                         report("No file chosen, press %s to open tree view",
2708                                get_key(REQ_VIEW_TREE));
2709                         break;
2710                 }
2711                 open_view(view, request, OPEN_DEFAULT);
2712                 break;
2714         case REQ_VIEW_BLOB:
2715                 if (!ref_blob[0]) {
2716                         report("No file chosen, press %s to open tree view",
2717                                get_key(REQ_VIEW_TREE));
2718                         break;
2719                 }
2720                 open_view(view, request, OPEN_DEFAULT);
2721                 break;
2723         case REQ_VIEW_PAGER:
2724                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2725                         report("No pager content, press %s to run command from prompt",
2726                                get_key(REQ_PROMPT));
2727                         break;
2728                 }
2729                 open_view(view, request, OPEN_DEFAULT);
2730                 break;
2732         case REQ_VIEW_STAGE:
2733                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2734                         report("No stage content, press %s to open the status view and choose file",
2735                                get_key(REQ_VIEW_STATUS));
2736                         break;
2737                 }
2738                 open_view(view, request, OPEN_DEFAULT);
2739                 break;
2741         case REQ_VIEW_STATUS:
2742                 if (opt_is_inside_work_tree == FALSE) {
2743                         report("The status view requires a working tree");
2744                         break;
2745                 }
2746                 open_view(view, request, OPEN_DEFAULT);
2747                 break;
2749         case REQ_VIEW_MAIN:
2750         case REQ_VIEW_DIFF:
2751         case REQ_VIEW_LOG:
2752         case REQ_VIEW_TREE:
2753         case REQ_VIEW_HELP:
2754                 open_view(view, request, OPEN_DEFAULT);
2755                 break;
2757         case REQ_NEXT:
2758         case REQ_PREVIOUS:
2759                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2761                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2762                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2763                    (view == VIEW(REQ_VIEW_DIFF) &&
2764                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2765                    (view == VIEW(REQ_VIEW_STAGE) &&
2766                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2767                    (view == VIEW(REQ_VIEW_BLOB) &&
2768                      view->parent == VIEW(REQ_VIEW_TREE))) {
2769                         int line;
2771                         view = view->parent;
2772                         line = view->lineno;
2773                         move_view(view, request);
2774                         if (view_is_displayed(view))
2775                                 update_view_title(view);
2776                         if (line != view->lineno)
2777                                 view->ops->request(view, REQ_ENTER,
2778                                                    &view->line[view->lineno]);
2780                 } else {
2781                         move_view(view, request);
2782                 }
2783                 break;
2785         case REQ_VIEW_NEXT:
2786         {
2787                 int nviews = displayed_views();
2788                 int next_view = (current_view + 1) % nviews;
2790                 if (next_view == current_view) {
2791                         report("Only one view is displayed");
2792                         break;
2793                 }
2795                 current_view = next_view;
2796                 /* Blur out the title of the previous view. */
2797                 update_view_title(view);
2798                 report("");
2799                 break;
2800         }
2801         case REQ_REFRESH:
2802                 report("Refreshing is not yet supported for the %s view", view->name);
2803                 break;
2805         case REQ_MAXIMIZE:
2806                 if (displayed_views() == 2)
2807                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2808                 break;
2810         case REQ_TOGGLE_LINENO:
2811                 opt_line_number = !opt_line_number;
2812                 redraw_display();
2813                 break;
2815         case REQ_TOGGLE_DATE:
2816                 opt_date = !opt_date;
2817                 redraw_display();
2818                 break;
2820         case REQ_TOGGLE_AUTHOR:
2821                 opt_author = !opt_author;
2822                 redraw_display();
2823                 break;
2825         case REQ_TOGGLE_REV_GRAPH:
2826                 opt_rev_graph = !opt_rev_graph;
2827                 redraw_display();
2828                 break;
2830         case REQ_TOGGLE_REFS:
2831                 opt_show_refs = !opt_show_refs;
2832                 redraw_display();
2833                 break;
2835         case REQ_SEARCH:
2836         case REQ_SEARCH_BACK:
2837                 search_view(view, request);
2838                 break;
2840         case REQ_FIND_NEXT:
2841         case REQ_FIND_PREV:
2842                 find_next(view, request);
2843                 break;
2845         case REQ_STOP_LOADING:
2846                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2847                         view = &views[i];
2848                         if (view->pipe)
2849                                 report("Stopped loading the %s view", view->name),
2850                         end_update(view, TRUE);
2851                 }
2852                 break;
2854         case REQ_SHOW_VERSION:
2855                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2856                 return TRUE;
2858         case REQ_SCREEN_RESIZE:
2859                 resize_display();
2860                 /* Fall-through */
2861         case REQ_SCREEN_REDRAW:
2862                 redraw_display();
2863                 break;
2865         case REQ_EDIT:
2866                 report("Nothing to edit");
2867                 break;
2869         case REQ_ENTER:
2870                 report("Nothing to enter");
2871                 break;
2873         case REQ_VIEW_CLOSE:
2874                 /* XXX: Mark closed views by letting view->parent point to the
2875                  * view itself. Parents to closed view should never be
2876                  * followed. */
2877                 if (view->parent &&
2878                     view->parent->parent != view->parent) {
2879                         memset(display, 0, sizeof(display));
2880                         current_view = 0;
2881                         display[current_view] = view->parent;
2882                         view->parent = view;
2883                         resize_display();
2884                         redraw_display();
2885                         report("");
2886                         break;
2887                 }
2888                 /* Fall-through */
2889         case REQ_QUIT:
2890                 return FALSE;
2892         default:
2893                 report("Unknown key, press 'h' for help");
2894                 return TRUE;
2895         }
2897         return TRUE;
2901 /*
2902  * Pager backend
2903  */
2905 static bool
2906 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2908         char *text = line->data;
2910         if (opt_line_number && draw_lineno(view, lineno))
2911                 return TRUE;
2913         draw_text(view, line->type, text, TRUE);
2914         return TRUE;
2917 static bool
2918 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2920         char refbuf[SIZEOF_STR];
2921         char *ref = NULL;
2922         FILE *pipe;
2924         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2925                 return TRUE;
2927         pipe = popen(refbuf, "r");
2928         if (!pipe)
2929                 return TRUE;
2931         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2932                 ref = chomp_string(ref);
2933         pclose(pipe);
2935         if (!ref || !*ref)
2936                 return TRUE;
2938         /* This is the only fatal call, since it can "corrupt" the buffer. */
2939         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2940                 return FALSE;
2942         return TRUE;
2945 static void
2946 add_pager_refs(struct view *view, struct line *line)
2948         char buf[SIZEOF_STR];
2949         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2950         struct ref **refs;
2951         size_t bufpos = 0, refpos = 0;
2952         const char *sep = "Refs: ";
2953         bool is_tag = FALSE;
2955         assert(line->type == LINE_COMMIT);
2957         refs = get_refs(commit_id);
2958         if (!refs) {
2959                 if (view == VIEW(REQ_VIEW_DIFF))
2960                         goto try_add_describe_ref;
2961                 return;
2962         }
2964         do {
2965                 struct ref *ref = refs[refpos];
2966                 const char *fmt = ref->tag    ? "%s[%s]" :
2967                                   ref->remote ? "%s<%s>" : "%s%s";
2969                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2970                         return;
2971                 sep = ", ";
2972                 if (ref->tag)
2973                         is_tag = TRUE;
2974         } while (refs[refpos++]->next);
2976         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2977 try_add_describe_ref:
2978                 /* Add <tag>-g<commit_id> "fake" reference. */
2979                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2980                         return;
2981         }
2983         if (bufpos == 0)
2984                 return;
2986         if (!realloc_lines(view, view->line_size + 1))
2987                 return;
2989         add_line_text(view, buf, LINE_PP_REFS);
2992 static bool
2993 pager_read(struct view *view, char *data)
2995         struct line *line;
2997         if (!data)
2998                 return TRUE;
3000         line = add_line_text(view, data, get_line_type(data));
3001         if (!line)
3002                 return FALSE;
3004         if (line->type == LINE_COMMIT &&
3005             (view == VIEW(REQ_VIEW_DIFF) ||
3006              view == VIEW(REQ_VIEW_LOG)))
3007                 add_pager_refs(view, line);
3009         return TRUE;
3012 static enum request
3013 pager_request(struct view *view, enum request request, struct line *line)
3015         int split = 0;
3017         if (request != REQ_ENTER)
3018                 return request;
3020         if (line->type == LINE_COMMIT &&
3021            (view == VIEW(REQ_VIEW_LOG) ||
3022             view == VIEW(REQ_VIEW_PAGER))) {
3023                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3024                 split = 1;
3025         }
3027         /* Always scroll the view even if it was split. That way
3028          * you can use Enter to scroll through the log view and
3029          * split open each commit diff. */
3030         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3032         /* FIXME: A minor workaround. Scrolling the view will call report("")
3033          * but if we are scrolling a non-current view this won't properly
3034          * update the view title. */
3035         if (split)
3036                 update_view_title(view);
3038         return REQ_NONE;
3041 static bool
3042 pager_grep(struct view *view, struct line *line)
3044         regmatch_t pmatch;
3045         char *text = line->data;
3047         if (!*text)
3048                 return FALSE;
3050         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3051                 return FALSE;
3053         return TRUE;
3056 static void
3057 pager_select(struct view *view, struct line *line)
3059         if (line->type == LINE_COMMIT) {
3060                 char *text = (char *)line->data + STRING_SIZE("commit ");
3062                 if (view != VIEW(REQ_VIEW_PAGER))
3063                         string_copy_rev(view->ref, text);
3064                 string_copy_rev(ref_commit, text);
3065         }
3068 static struct view_ops pager_ops = {
3069         "line",
3070         NULL,
3071         pager_read,
3072         pager_draw,
3073         pager_request,
3074         pager_grep,
3075         pager_select,
3076 };
3078 static enum request
3079 log_request(struct view *view, enum request request, struct line *line)
3081         switch (request) {
3082         case REQ_REFRESH:
3083                 load_refs();
3084                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3085                 return REQ_NONE;
3086         default:
3087                 return pager_request(view, request, line);
3088         }
3091 static struct view_ops log_ops = {
3092         "line",
3093         NULL,
3094         pager_read,
3095         pager_draw,
3096         log_request,
3097         pager_grep,
3098         pager_select,
3099 };
3102 /*
3103  * Help backend
3104  */
3106 static bool
3107 help_open(struct view *view)
3109         char buf[BUFSIZ];
3110         int lines = ARRAY_SIZE(req_info) + 2;
3111         int i;
3113         if (view->lines > 0)
3114                 return TRUE;
3116         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3117                 if (!req_info[i].request)
3118                         lines++;
3120         lines += run_requests + 1;
3122         view->line = calloc(lines, sizeof(*view->line));
3123         if (!view->line)
3124                 return FALSE;
3126         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3128         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3129                 const char *key;
3131                 if (req_info[i].request == REQ_NONE)
3132                         continue;
3134                 if (!req_info[i].request) {
3135                         add_line_text(view, "", LINE_DEFAULT);
3136                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3137                         continue;
3138                 }
3140                 key = get_key(req_info[i].request);
3141                 if (!*key)
3142                         key = "(no key defined)";
3144                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3145                         continue;
3147                 add_line_text(view, buf, LINE_DEFAULT);
3148         }
3150         if (run_requests) {
3151                 add_line_text(view, "", LINE_DEFAULT);
3152                 add_line_text(view, "External commands:", LINE_DEFAULT);
3153         }
3155         for (i = 0; i < run_requests; i++) {
3156                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3157                 const char *key;
3159                 if (!req)
3160                         continue;
3162                 key = get_key_name(req->key);
3163                 if (!*key)
3164                         key = "(no key defined)";
3166                 if (!string_format(buf, "    %-10s %-14s `%s`",
3167                                    keymap_table[req->keymap].name,
3168                                    key, req->cmd))
3169                         continue;
3171                 add_line_text(view, buf, LINE_DEFAULT);
3172         }
3174         return TRUE;
3177 static struct view_ops help_ops = {
3178         "line",
3179         help_open,
3180         NULL,
3181         pager_draw,
3182         pager_request,
3183         pager_grep,
3184         pager_select,
3185 };
3188 /*
3189  * Tree backend
3190  */
3192 struct tree_stack_entry {
3193         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3194         unsigned long lineno;           /* Line number to restore */
3195         char *name;                     /* Position of name in opt_path */
3196 };
3198 /* The top of the path stack. */
3199 static struct tree_stack_entry *tree_stack = NULL;
3200 unsigned long tree_lineno = 0;
3202 static void
3203 pop_tree_stack_entry(void)
3205         struct tree_stack_entry *entry = tree_stack;
3207         tree_lineno = entry->lineno;
3208         entry->name[0] = 0;
3209         tree_stack = entry->prev;
3210         free(entry);
3213 static void
3214 push_tree_stack_entry(const char *name, unsigned long lineno)
3216         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3217         size_t pathlen = strlen(opt_path);
3219         if (!entry)
3220                 return;
3222         entry->prev = tree_stack;
3223         entry->name = opt_path + pathlen;
3224         tree_stack = entry;
3226         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3227                 pop_tree_stack_entry();
3228                 return;
3229         }
3231         /* Move the current line to the first tree entry. */
3232         tree_lineno = 1;
3233         entry->lineno = lineno;
3236 /* Parse output from git-ls-tree(1):
3237  *
3238  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3239  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3240  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3241  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3242  */
3244 #define SIZEOF_TREE_ATTR \
3245         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3247 #define TREE_UP_FORMAT "040000 tree %s\t.."
3249 static int
3250 tree_compare_entry(enum line_type type1, const char *name1,
3251                    enum line_type type2, const char *name2)
3253         if (type1 != type2) {
3254                 if (type1 == LINE_TREE_DIR)
3255                         return -1;
3256                 return 1;
3257         }
3259         return strcmp(name1, name2);
3262 static const char *
3263 tree_path(struct line *line)
3265         const char *path = line->data;
3267         return path + SIZEOF_TREE_ATTR;
3270 static bool
3271 tree_read(struct view *view, char *text)
3273         size_t textlen = text ? strlen(text) : 0;
3274         char buf[SIZEOF_STR];
3275         unsigned long pos;
3276         enum line_type type;
3277         bool first_read = view->lines == 0;
3279         if (!text)
3280                 return TRUE;
3281         if (textlen <= SIZEOF_TREE_ATTR)
3282                 return FALSE;
3284         type = text[STRING_SIZE("100644 ")] == 't'
3285              ? LINE_TREE_DIR : LINE_TREE_FILE;
3287         if (first_read) {
3288                 /* Add path info line */
3289                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3290                     !realloc_lines(view, view->line_size + 1) ||
3291                     !add_line_text(view, buf, LINE_DEFAULT))
3292                         return FALSE;
3294                 /* Insert "link" to parent directory. */
3295                 if (*opt_path) {
3296                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3297                             !realloc_lines(view, view->line_size + 1) ||
3298                             !add_line_text(view, buf, LINE_TREE_DIR))
3299                                 return FALSE;
3300                 }
3301         }
3303         /* Strip the path part ... */
3304         if (*opt_path) {
3305                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3306                 size_t striplen = strlen(opt_path);
3307                 char *path = text + SIZEOF_TREE_ATTR;
3309                 if (pathlen > striplen)
3310                         memmove(path, path + striplen,
3311                                 pathlen - striplen + 1);
3312         }
3314         /* Skip "Directory ..." and ".." line. */
3315         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3316                 struct line *line = &view->line[pos];
3317                 const char *path1 = tree_path(line);
3318                 char *path2 = text + SIZEOF_TREE_ATTR;
3319                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3321                 if (cmp <= 0)
3322                         continue;
3324                 text = strdup(text);
3325                 if (!text)
3326                         return FALSE;
3328                 if (view->lines > pos)
3329                         memmove(&view->line[pos + 1], &view->line[pos],
3330                                 (view->lines - pos) * sizeof(*line));
3332                 line = &view->line[pos];
3333                 line->data = text;
3334                 line->type = type;
3335                 view->lines++;
3336                 return TRUE;
3337         }
3339         if (!add_line_text(view, text, type))
3340                 return FALSE;
3342         if (tree_lineno > view->lineno) {
3343                 view->lineno = tree_lineno;
3344                 tree_lineno = 0;
3345         }
3347         return TRUE;
3350 static enum request
3351 tree_request(struct view *view, enum request request, struct line *line)
3353         enum open_flags flags;
3355         if (request == REQ_VIEW_BLAME) {
3356                 const char *filename = tree_path(line);
3358                 if (line->type == LINE_TREE_DIR) {
3359                         report("Cannot show blame for directory %s", opt_path);
3360                         return REQ_NONE;
3361                 }
3363                 string_copy(opt_ref, view->vid);
3364                 string_format(opt_file, "%s%s", opt_path, filename);
3365                 return request;
3366         }
3367         if (request == REQ_TREE_PARENT) {
3368                 if (*opt_path) {
3369                         /* fake 'cd  ..' */
3370                         request = REQ_ENTER;
3371                         line = &view->line[1];
3372                 } else {
3373                         /* quit view if at top of tree */
3374                         return REQ_VIEW_CLOSE;
3375                 }
3376         }
3377         if (request != REQ_ENTER)
3378                 return request;
3380         /* Cleanup the stack if the tree view is at a different tree. */
3381         while (!*opt_path && tree_stack)
3382                 pop_tree_stack_entry();
3384         switch (line->type) {
3385         case LINE_TREE_DIR:
3386                 /* Depending on whether it is a subdir or parent (updir?) link
3387                  * mangle the path buffer. */
3388                 if (line == &view->line[1] && *opt_path) {
3389                         pop_tree_stack_entry();
3391                 } else {
3392                         const char *basename = tree_path(line);
3394                         push_tree_stack_entry(basename, view->lineno);
3395                 }
3397                 /* Trees and subtrees share the same ID, so they are not not
3398                  * unique like blobs. */
3399                 flags = OPEN_RELOAD;
3400                 request = REQ_VIEW_TREE;
3401                 break;
3403         case LINE_TREE_FILE:
3404                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3405                 request = REQ_VIEW_BLOB;
3406                 break;
3408         default:
3409                 return TRUE;
3410         }
3412         open_view(view, request, flags);
3413         if (request == REQ_VIEW_TREE) {
3414                 view->lineno = tree_lineno;
3415         }
3417         return REQ_NONE;
3420 static void
3421 tree_select(struct view *view, struct line *line)
3423         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3425         if (line->type == LINE_TREE_FILE) {
3426                 string_copy_rev(ref_blob, text);
3428         } else if (line->type != LINE_TREE_DIR) {
3429                 return;
3430         }
3432         string_copy_rev(view->ref, text);
3435 static struct view_ops tree_ops = {
3436         "file",
3437         NULL,
3438         tree_read,
3439         pager_draw,
3440         tree_request,
3441         pager_grep,
3442         tree_select,
3443 };
3445 static bool
3446 blob_read(struct view *view, char *line)
3448         if (!line)
3449                 return TRUE;
3450         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3453 static struct view_ops blob_ops = {
3454         "line",
3455         NULL,
3456         blob_read,
3457         pager_draw,
3458         pager_request,
3459         pager_grep,
3460         pager_select,
3461 };
3463 /*
3464  * Blame backend
3465  *
3466  * Loading the blame view is a two phase job:
3467  *
3468  *  1. File content is read either using opt_file from the
3469  *     filesystem or using git-cat-file.
3470  *  2. Then blame information is incrementally added by
3471  *     reading output from git-blame.
3472  */
3474 struct blame_commit {
3475         char id[SIZEOF_REV];            /* SHA1 ID. */
3476         char title[128];                /* First line of the commit message. */
3477         char author[75];                /* Author of the commit. */
3478         struct tm time;                 /* Date from the author ident. */
3479         char filename[128];             /* Name of file. */
3480 };
3482 struct blame {
3483         struct blame_commit *commit;
3484         unsigned int header:1;
3485         char text[1];
3486 };
3488 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3489 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3491 static bool
3492 blame_open(struct view *view)
3494         char path[SIZEOF_STR];
3495         char ref[SIZEOF_STR] = "";
3497         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3498                 return FALSE;
3500         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3501                 return FALSE;
3503         if (*opt_ref) {
3504                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3505                         return FALSE;
3506         } else {
3507                 view->pipe = fopen(opt_file, "r");
3508                 if (!view->pipe &&
3509                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3510                         return FALSE;
3511         }
3513         if (!view->pipe)
3514                 view->pipe = popen(view->cmd, "r");
3515         if (!view->pipe)
3516                 return FALSE;
3518         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3519                 return FALSE;
3521         reset_view(view);
3522         string_format(view->ref, "%s ...", opt_file);
3523         string_copy_rev(view->vid, opt_file);
3524         set_nonblocking_input(TRUE);
3525         view->start_time = time(NULL);
3527         return TRUE;
3530 static struct blame_commit *
3531 get_blame_commit(struct view *view, const char *id)
3533         size_t i;
3535         for (i = 0; i < view->lines; i++) {
3536                 struct blame *blame = view->line[i].data;
3538                 if (!blame->commit)
3539                         continue;
3541                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3542                         return blame->commit;
3543         }
3545         {
3546                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3548                 if (commit)
3549                         string_ncopy(commit->id, id, SIZEOF_REV);
3550                 return commit;
3551         }
3554 static bool
3555 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3557         const char *pos = *posref;
3559         *posref = NULL;
3560         pos = strchr(pos + 1, ' ');
3561         if (!pos || !isdigit(pos[1]))
3562                 return FALSE;
3563         *number = atoi(pos + 1);
3564         if (*number < min || *number > max)
3565                 return FALSE;
3567         *posref = pos;
3568         return TRUE;
3571 static struct blame_commit *
3572 parse_blame_commit(struct view *view, const char *text, int *blamed)
3574         struct blame_commit *commit;
3575         struct blame *blame;
3576         const char *pos = text + SIZEOF_REV - 1;
3577         size_t lineno;
3578         size_t group;
3580         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3581                 return NULL;
3583         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3584             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3585                 return NULL;
3587         commit = get_blame_commit(view, text);
3588         if (!commit)
3589                 return NULL;
3591         *blamed += group;
3592         while (group--) {
3593                 struct line *line = &view->line[lineno + group - 1];
3595                 blame = line->data;
3596                 blame->commit = commit;
3597                 blame->header = !group;
3598                 line->dirty = 1;
3599         }
3601         return commit;
3604 static bool
3605 blame_read_file(struct view *view, const char *line)
3607         if (!line) {
3608                 FILE *pipe = NULL;
3610                 if (view->lines > 0)
3611                         pipe = popen(view->cmd, "r");
3612                 else if (!view->parent)
3613                         die("No blame exist for %s", view->vid);
3614                 view->cmd[0] = 0;
3615                 if (!pipe) {
3616                         report("Failed to load blame data");
3617                         return TRUE;
3618                 }
3620                 fclose(view->pipe);
3621                 view->pipe = pipe;
3622                 return FALSE;
3624         } else {
3625                 size_t linelen = strlen(line);
3626                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3628                 blame->commit = NULL;
3629                 strncpy(blame->text, line, linelen);
3630                 blame->text[linelen] = 0;
3631                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3632         }
3635 static bool
3636 match_blame_header(const char *name, char **line)
3638         size_t namelen = strlen(name);
3639         bool matched = !strncmp(name, *line, namelen);
3641         if (matched)
3642                 *line += namelen;
3644         return matched;
3647 static bool
3648 blame_read(struct view *view, char *line)
3650         static struct blame_commit *commit = NULL;
3651         static int blamed = 0;
3652         static time_t author_time;
3654         if (*view->cmd)
3655                 return blame_read_file(view, line);
3657         if (!line) {
3658                 /* Reset all! */
3659                 commit = NULL;
3660                 blamed = 0;
3661                 string_format(view->ref, "%s", view->vid);
3662                 if (view_is_displayed(view)) {
3663                         update_view_title(view);
3664                         redraw_view_from(view, 0);
3665                 }
3666                 return TRUE;
3667         }
3669         if (!commit) {
3670                 commit = parse_blame_commit(view, line, &blamed);
3671                 string_format(view->ref, "%s %2d%%", view->vid,
3672                               blamed * 100 / view->lines);
3674         } else if (match_blame_header("author ", &line)) {
3675                 string_ncopy(commit->author, line, strlen(line));
3677         } else if (match_blame_header("author-time ", &line)) {
3678                 author_time = (time_t) atol(line);
3680         } else if (match_blame_header("author-tz ", &line)) {
3681                 long tz;
3683                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3684                 tz += ('0' - line[2]) * 60 * 60;
3685                 tz += ('0' - line[3]) * 60;
3686                 tz += ('0' - line[4]) * 60;
3688                 if (line[0] == '-')
3689                         tz = -tz;
3691                 author_time -= tz;
3692                 gmtime_r(&author_time, &commit->time);
3694         } else if (match_blame_header("summary ", &line)) {
3695                 string_ncopy(commit->title, line, strlen(line));
3697         } else if (match_blame_header("filename ", &line)) {
3698                 string_ncopy(commit->filename, line, strlen(line));
3699                 commit = NULL;
3700         }
3702         return TRUE;
3705 static bool
3706 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3708         struct blame *blame = line->data;
3709         struct tm *time = NULL;
3710         const char *id = NULL, *author = NULL;
3712         if (blame->commit && *blame->commit->filename) {
3713                 id = blame->commit->id;
3714                 author = blame->commit->author;
3715                 time = &blame->commit->time;
3716         }
3718         if (opt_date && draw_date(view, time))
3719                 return TRUE;
3721         if (opt_author &&
3722             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3723                 return TRUE;
3725         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3726                 return TRUE;
3728         if (draw_lineno(view, lineno))
3729                 return TRUE;
3731         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3732         return TRUE;
3735 static enum request
3736 blame_request(struct view *view, enum request request, struct line *line)
3738         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3739         struct blame *blame = line->data;
3741         switch (request) {
3742         case REQ_ENTER:
3743                 if (!blame->commit) {
3744                         report("No commit loaded yet");
3745                         break;
3746                 }
3748                 if (!strcmp(blame->commit->id, NULL_ID)) {
3749                         char path[SIZEOF_STR];
3751                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3752                                 break;
3753                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3754                 }
3756                 open_view(view, REQ_VIEW_DIFF, flags);
3757                 break;
3759         default:
3760                 return request;
3761         }
3763         return REQ_NONE;
3766 static bool
3767 blame_grep(struct view *view, struct line *line)
3769         struct blame *blame = line->data;
3770         struct blame_commit *commit = blame->commit;
3771         regmatch_t pmatch;
3773 #define MATCH(text, on)                                                 \
3774         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3776         if (commit) {
3777                 char buf[DATE_COLS + 1];
3779                 if (MATCH(commit->title, 1) ||
3780                     MATCH(commit->author, opt_author) ||
3781                     MATCH(commit->id, opt_date))
3782                         return TRUE;
3784                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3785                     MATCH(buf, 1))
3786                         return TRUE;
3787         }
3789         return MATCH(blame->text, 1);
3791 #undef MATCH
3794 static void
3795 blame_select(struct view *view, struct line *line)
3797         struct blame *blame = line->data;
3798         struct blame_commit *commit = blame->commit;
3800         if (!commit)
3801                 return;
3803         if (!strcmp(commit->id, NULL_ID))
3804                 string_ncopy(ref_commit, "HEAD", 4);
3805         else
3806                 string_copy_rev(ref_commit, commit->id);
3809 static struct view_ops blame_ops = {
3810         "line",
3811         blame_open,
3812         blame_read,
3813         blame_draw,
3814         blame_request,
3815         blame_grep,
3816         blame_select,
3817 };
3819 /*
3820  * Status backend
3821  */
3823 struct status {
3824         char status;
3825         struct {
3826                 mode_t mode;
3827                 char rev[SIZEOF_REV];
3828                 char name[SIZEOF_STR];
3829         } old;
3830         struct {
3831                 mode_t mode;
3832                 char rev[SIZEOF_REV];
3833                 char name[SIZEOF_STR];
3834         } new;
3835 };
3837 static char status_onbranch[SIZEOF_STR];
3838 static struct status stage_status;
3839 static enum line_type stage_line_type;
3840 static size_t stage_chunks;
3841 static int *stage_chunk;
3843 /* This should work even for the "On branch" line. */
3844 static inline bool
3845 status_has_none(struct view *view, struct line *line)
3847         return line < view->line + view->lines && !line[1].data;
3850 /* Get fields from the diff line:
3851  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3852  */
3853 static inline bool
3854 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3856         const char *old_mode = buf +  1;
3857         const char *new_mode = buf +  8;
3858         const char *old_rev  = buf + 15;
3859         const char *new_rev  = buf + 56;
3860         const char *status   = buf + 97;
3862         if (bufsize < 99 ||
3863             old_mode[-1] != ':' ||
3864             new_mode[-1] != ' ' ||
3865             old_rev[-1]  != ' ' ||
3866             new_rev[-1]  != ' ' ||
3867             status[-1]   != ' ')
3868                 return FALSE;
3870         file->status = *status;
3872         string_copy_rev(file->old.rev, old_rev);
3873         string_copy_rev(file->new.rev, new_rev);
3875         file->old.mode = strtoul(old_mode, NULL, 8);
3876         file->new.mode = strtoul(new_mode, NULL, 8);
3878         file->old.name[0] = file->new.name[0] = 0;
3880         return TRUE;
3883 static bool
3884 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3886         struct status *file = NULL;
3887         struct status *unmerged = NULL;
3888         char buf[SIZEOF_STR * 4];
3889         size_t bufsize = 0;
3890         FILE *pipe;
3892         pipe = popen(cmd, "r");
3893         if (!pipe)
3894                 return FALSE;
3896         add_line_data(view, NULL, type);
3898         while (!feof(pipe) && !ferror(pipe)) {
3899                 char *sep;
3900                 size_t readsize;
3902                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3903                 if (!readsize)
3904                         break;
3905                 bufsize += readsize;
3907                 /* Process while we have NUL chars. */
3908                 while ((sep = memchr(buf, 0, bufsize))) {
3909                         size_t sepsize = sep - buf + 1;
3911                         if (!file) {
3912                                 if (!realloc_lines(view, view->line_size + 1))
3913                                         goto error_out;
3915                                 file = calloc(1, sizeof(*file));
3916                                 if (!file)
3917                                         goto error_out;
3919                                 add_line_data(view, file, type);
3920                         }
3922                         /* Parse diff info part. */
3923                         if (status) {
3924                                 file->status = status;
3925                                 if (status == 'A')
3926                                         string_copy(file->old.rev, NULL_ID);
3928                         } else if (!file->status) {
3929                                 if (!status_get_diff(file, buf, sepsize))
3930                                         goto error_out;
3932                                 bufsize -= sepsize;
3933                                 memmove(buf, sep + 1, bufsize);
3935                                 sep = memchr(buf, 0, bufsize);
3936                                 if (!sep)
3937                                         break;
3938                                 sepsize = sep - buf + 1;
3940                                 /* Collapse all 'M'odified entries that
3941                                  * follow a associated 'U'nmerged entry.
3942                                  */
3943                                 if (file->status == 'U') {
3944                                         unmerged = file;
3946                                 } else if (unmerged) {
3947                                         int collapse = !strcmp(buf, unmerged->new.name);
3949                                         unmerged = NULL;
3950                                         if (collapse) {
3951                                                 free(file);
3952                                                 view->lines--;
3953                                                 continue;
3954                                         }
3955                                 }
3956                         }
3958                         /* Grab the old name for rename/copy. */
3959                         if (!*file->old.name &&
3960                             (file->status == 'R' || file->status == 'C')) {
3961                                 sepsize = sep - buf + 1;
3962                                 string_ncopy(file->old.name, buf, sepsize);
3963                                 bufsize -= sepsize;
3964                                 memmove(buf, sep + 1, bufsize);
3966                                 sep = memchr(buf, 0, bufsize);
3967                                 if (!sep)
3968                                         break;
3969                                 sepsize = sep - buf + 1;
3970                         }
3972                         /* git-ls-files just delivers a NUL separated
3973                          * list of file names similar to the second half
3974                          * of the git-diff-* output. */
3975                         string_ncopy(file->new.name, buf, sepsize);
3976                         if (!*file->old.name)
3977                                 string_copy(file->old.name, file->new.name);
3978                         bufsize -= sepsize;
3979                         memmove(buf, sep + 1, bufsize);
3980                         file = NULL;
3981                 }
3982         }
3984         if (ferror(pipe)) {
3985 error_out:
3986                 pclose(pipe);
3987                 return FALSE;
3988         }
3990         if (!view->line[view->lines - 1].data)
3991                 add_line_data(view, NULL, LINE_STAT_NONE);
3993         pclose(pipe);
3994         return TRUE;
3997 /* Don't show unmerged entries in the staged section. */
3998 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3999 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4000 #define STATUS_LIST_OTHER_CMD \
4001         "git ls-files -z --others --exclude-standard"
4002 #define STATUS_LIST_NO_HEAD_CMD \
4003         "git ls-files -z --cached --exclude-standard"
4005 #define STATUS_DIFF_INDEX_SHOW_CMD \
4006         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4008 #define STATUS_DIFF_FILES_SHOW_CMD \
4009         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4011 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4012         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4014 /* First parse staged info using git-diff-index(1), then parse unstaged
4015  * info using git-diff-files(1), and finally untracked files using
4016  * git-ls-files(1). */
4017 static bool
4018 status_open(struct view *view)
4020         unsigned long prev_lineno = view->lineno;
4022         reset_view(view);
4024         if (!realloc_lines(view, view->line_size + 7))
4025                 return FALSE;
4027         add_line_data(view, NULL, LINE_STAT_HEAD);
4028         if (opt_no_head)
4029                 string_copy(status_onbranch, "Initial commit");
4030         else if (!*opt_head)
4031                 string_copy(status_onbranch, "Not currently on any branch");
4032         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4033                 return FALSE;
4035         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4037         if (opt_no_head) {
4038                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4039                         return FALSE;
4040         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4041                 return FALSE;
4042         }
4044         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4045             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4046                 return FALSE;
4048         /* If all went well restore the previous line number to stay in
4049          * the context or select a line with something that can be
4050          * updated. */
4051         if (prev_lineno >= view->lines)
4052                 prev_lineno = view->lines - 1;
4053         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4054                 prev_lineno++;
4055         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4056                 prev_lineno--;
4058         /* If the above fails, always skip the "On branch" line. */
4059         if (prev_lineno < view->lines)
4060                 view->lineno = prev_lineno;
4061         else
4062                 view->lineno = 1;
4064         if (view->lineno < view->offset)
4065                 view->offset = view->lineno;
4066         else if (view->offset + view->height <= view->lineno)
4067                 view->offset = view->lineno - view->height + 1;
4069         return TRUE;
4072 static bool
4073 status_draw(struct view *view, struct line *line, unsigned int lineno)
4075         struct status *status = line->data;
4076         enum line_type type;
4077         const char *text;
4079         if (!status) {
4080                 switch (line->type) {
4081                 case LINE_STAT_STAGED:
4082                         type = LINE_STAT_SECTION;
4083                         text = "Changes to be committed:";
4084                         break;
4086                 case LINE_STAT_UNSTAGED:
4087                         type = LINE_STAT_SECTION;
4088                         text = "Changed but not updated:";
4089                         break;
4091                 case LINE_STAT_UNTRACKED:
4092                         type = LINE_STAT_SECTION;
4093                         text = "Untracked files:";
4094                         break;
4096                 case LINE_STAT_NONE:
4097                         type = LINE_DEFAULT;
4098                         text = "    (no files)";
4099                         break;
4101                 case LINE_STAT_HEAD:
4102                         type = LINE_STAT_HEAD;
4103                         text = status_onbranch;
4104                         break;
4106                 default:
4107                         return FALSE;
4108                 }
4109         } else {
4110                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4112                 buf[0] = status->status;
4113                 if (draw_text(view, line->type, buf, TRUE))
4114                         return TRUE;
4115                 type = LINE_DEFAULT;
4116                 text = status->new.name;
4117         }
4119         draw_text(view, type, text, TRUE);
4120         return TRUE;
4123 static enum request
4124 status_enter(struct view *view, struct line *line)
4126         struct status *status = line->data;
4127         char oldpath[SIZEOF_STR] = "";
4128         char newpath[SIZEOF_STR] = "";
4129         const char *info;
4130         size_t cmdsize = 0;
4131         enum open_flags split;
4133         if (line->type == LINE_STAT_NONE ||
4134             (!status && line[1].type == LINE_STAT_NONE)) {
4135                 report("No file to diff");
4136                 return REQ_NONE;
4137         }
4139         if (status) {
4140                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4141                         return REQ_QUIT;
4142                 /* Diffs for unmerged entries are empty when pasing the
4143                  * new path, so leave it empty. */
4144                 if (status->status != 'U' &&
4145                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4146                         return REQ_QUIT;
4147         }
4149         if (opt_cdup[0] &&
4150             line->type != LINE_STAT_UNTRACKED &&
4151             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4152                 return REQ_QUIT;
4154         switch (line->type) {
4155         case LINE_STAT_STAGED:
4156                 if (opt_no_head) {
4157                         if (!string_format_from(opt_cmd, &cmdsize,
4158                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4159                                                 newpath))
4160                                 return REQ_QUIT;
4161                 } else {
4162                         if (!string_format_from(opt_cmd, &cmdsize,
4163                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4164                                                 oldpath, newpath))
4165                                 return REQ_QUIT;
4166                 }
4168                 if (status)
4169                         info = "Staged changes to %s";
4170                 else
4171                         info = "Staged changes";
4172                 break;
4174         case LINE_STAT_UNSTAGED:
4175                 if (!string_format_from(opt_cmd, &cmdsize,
4176                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4177                         return REQ_QUIT;
4178                 if (status)
4179                         info = "Unstaged changes to %s";
4180                 else
4181                         info = "Unstaged changes";
4182                 break;
4184         case LINE_STAT_UNTRACKED:
4185                 if (opt_pipe)
4186                         return REQ_QUIT;
4188                 if (!status) {
4189                         report("No file to show");
4190                         return REQ_NONE;
4191                 }
4193                 opt_pipe = fopen(status->new.name, "r");
4194                 info = "Untracked file %s";
4195                 break;
4197         case LINE_STAT_HEAD:
4198                 return REQ_NONE;
4200         default:
4201                 die("line type %d not handled in switch", line->type);
4202         }
4204         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4205         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4206         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4207                 if (status) {
4208                         stage_status = *status;
4209                 } else {
4210                         memset(&stage_status, 0, sizeof(stage_status));
4211                 }
4213                 stage_line_type = line->type;
4214                 stage_chunks = 0;
4215                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4216         }
4218         return REQ_NONE;
4221 static bool
4222 status_exists(struct status *status, enum line_type type)
4224         struct view *view = VIEW(REQ_VIEW_STATUS);
4225         struct line *line;
4227         for (line = view->line; line < view->line + view->lines; line++) {
4228                 struct status *pos = line->data;
4230                 if (line->type == type && pos &&
4231                     !strcmp(status->new.name, pos->new.name))
4232                         return TRUE;
4233         }
4235         return FALSE;
4239 static FILE *
4240 status_update_prepare(enum line_type type)
4242         char cmd[SIZEOF_STR];
4243         size_t cmdsize = 0;
4245         if (opt_cdup[0] &&
4246             type != LINE_STAT_UNTRACKED &&
4247             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4248                 return NULL;
4250         switch (type) {
4251         case LINE_STAT_STAGED:
4252                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4253                 break;
4255         case LINE_STAT_UNSTAGED:
4256         case LINE_STAT_UNTRACKED:
4257                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4258                 break;
4260         default:
4261                 die("line type %d not handled in switch", type);
4262         }
4264         return popen(cmd, "w");
4267 static bool
4268 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4270         char buf[SIZEOF_STR];
4271         size_t bufsize = 0;
4272         size_t written = 0;
4274         switch (type) {
4275         case LINE_STAT_STAGED:
4276                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4277                                         status->old.mode,
4278                                         status->old.rev,
4279                                         status->old.name, 0))
4280                         return FALSE;
4281                 break;
4283         case LINE_STAT_UNSTAGED:
4284         case LINE_STAT_UNTRACKED:
4285                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4286                         return FALSE;
4287                 break;
4289         default:
4290                 die("line type %d not handled in switch", type);
4291         }
4293         while (!ferror(pipe) && written < bufsize) {
4294                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4295         }
4297         return written == bufsize;
4300 static bool
4301 status_update_file(struct status *status, enum line_type type)
4303         FILE *pipe = status_update_prepare(type);
4304         bool result;
4306         if (!pipe)
4307                 return FALSE;
4309         result = status_update_write(pipe, status, type);
4310         pclose(pipe);
4311         return result;
4314 static bool
4315 status_update_files(struct view *view, struct line *line)
4317         FILE *pipe = status_update_prepare(line->type);
4318         bool result = TRUE;
4319         struct line *pos = view->line + view->lines;
4320         int files = 0;
4321         int file, done;
4323         if (!pipe)
4324                 return FALSE;
4326         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4327                 files++;
4329         for (file = 0, done = 0; result && file < files; line++, file++) {
4330                 int almost_done = file * 100 / files;
4332                 if (almost_done > done) {
4333                         done = almost_done;
4334                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4335                                       file, files, done);
4336                         update_view_title(view);
4337                 }
4338                 result = status_update_write(pipe, line->data, line->type);
4339         }
4341         pclose(pipe);
4342         return result;
4345 static bool
4346 status_update(struct view *view)
4348         struct line *line = &view->line[view->lineno];
4350         assert(view->lines);
4352         if (!line->data) {
4353                 /* This should work even for the "On branch" line. */
4354                 if (line < view->line + view->lines && !line[1].data) {
4355                         report("Nothing to update");
4356                         return FALSE;
4357                 }
4359                 if (!status_update_files(view, line + 1)) {
4360                         report("Failed to update file status");
4361                         return FALSE;
4362                 }
4364         } else if (!status_update_file(line->data, line->type)) {
4365                 report("Failed to update file status");
4366                 return FALSE;
4367         }
4369         return TRUE;
4372 static bool
4373 status_revert(struct status *status, enum line_type type, bool has_none)
4375         if (!status || type != LINE_STAT_UNSTAGED) {
4376                 if (type == LINE_STAT_STAGED) {
4377                         report("Cannot revert changes to staged files");
4378                 } else if (type == LINE_STAT_UNTRACKED) {
4379                         report("Cannot revert changes to untracked files");
4380                 } else if (has_none) {
4381                         report("Nothing to revert");
4382                 } else {
4383                         report("Cannot revert changes to multiple files");
4384                 }
4385                 return FALSE;
4387         } else {
4388                 char cmd[SIZEOF_STR];
4389                 char file_sq[SIZEOF_STR];
4391                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4392                     !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4393                         return FALSE;
4395                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4396         }
4399 static enum request
4400 status_request(struct view *view, enum request request, struct line *line)
4402         struct status *status = line->data;
4404         switch (request) {
4405         case REQ_STATUS_UPDATE:
4406                 if (!status_update(view))
4407                         return REQ_NONE;
4408                 break;
4410         case REQ_STATUS_REVERT:
4411                 if (!status_revert(status, line->type, status_has_none(view, line)))
4412                         return REQ_NONE;
4413                 break;
4415         case REQ_STATUS_MERGE:
4416                 if (!status || status->status != 'U') {
4417                         report("Merging only possible for files with unmerged status ('U').");
4418                         return REQ_NONE;
4419                 }
4420                 open_mergetool(status->new.name);
4421                 break;
4423         case REQ_EDIT:
4424                 if (!status)
4425                         return request;
4427                 open_editor(status->status != '?', status->new.name);
4428                 break;
4430         case REQ_VIEW_BLAME:
4431                 if (status) {
4432                         string_copy(opt_file, status->new.name);
4433                         opt_ref[0] = 0;
4434                 }
4435                 return request;
4437         case REQ_ENTER:
4438                 /* After returning the status view has been split to
4439                  * show the stage view. No further reloading is
4440                  * necessary. */
4441                 status_enter(view, line);
4442                 return REQ_NONE;
4444         case REQ_REFRESH:
4445                 /* Simply reload the view. */
4446                 break;
4448         default:
4449                 return request;
4450         }
4452         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4454         return REQ_NONE;
4457 static void
4458 status_select(struct view *view, struct line *line)
4460         struct status *status = line->data;
4461         char file[SIZEOF_STR] = "all files";
4462         const char *text;
4463         const char *key;
4465         if (status && !string_format(file, "'%s'", status->new.name))
4466                 return;
4468         if (!status && line[1].type == LINE_STAT_NONE)
4469                 line++;
4471         switch (line->type) {
4472         case LINE_STAT_STAGED:
4473                 text = "Press %s to unstage %s for commit";
4474                 break;
4476         case LINE_STAT_UNSTAGED:
4477                 text = "Press %s to stage %s for commit";
4478                 break;
4480         case LINE_STAT_UNTRACKED:
4481                 text = "Press %s to stage %s for addition";
4482                 break;
4484         case LINE_STAT_HEAD:
4485         case LINE_STAT_NONE:
4486                 text = "Nothing to update";
4487                 break;
4489         default:
4490                 die("line type %d not handled in switch", line->type);
4491         }
4493         if (status && status->status == 'U') {
4494                 text = "Press %s to resolve conflict in %s";
4495                 key = get_key(REQ_STATUS_MERGE);
4497         } else {
4498                 key = get_key(REQ_STATUS_UPDATE);
4499         }
4501         string_format(view->ref, text, key, file);
4504 static bool
4505 status_grep(struct view *view, struct line *line)
4507         struct status *status = line->data;
4508         enum { S_STATUS, S_NAME, S_END } state;
4509         char buf[2] = "?";
4510         regmatch_t pmatch;
4512         if (!status)
4513                 return FALSE;
4515         for (state = S_STATUS; state < S_END; state++) {
4516                 const char *text;
4518                 switch (state) {
4519                 case S_NAME:    text = status->new.name;        break;
4520                 case S_STATUS:
4521                         buf[0] = status->status;
4522                         text = buf;
4523                         break;
4525                 default:
4526                         return FALSE;
4527                 }
4529                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4530                         return TRUE;
4531         }
4533         return FALSE;
4536 static struct view_ops status_ops = {
4537         "file",
4538         status_open,
4539         NULL,
4540         status_draw,
4541         status_request,
4542         status_grep,
4543         status_select,
4544 };
4547 static bool
4548 stage_diff_line(FILE *pipe, struct line *line)
4550         const char *buf = line->data;
4551         size_t bufsize = strlen(buf);
4552         size_t written = 0;
4554         while (!ferror(pipe) && written < bufsize) {
4555                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4556         }
4558         fputc('\n', pipe);
4560         return written == bufsize;
4563 static bool
4564 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4566         while (line < end) {
4567                 if (!stage_diff_line(pipe, line++))
4568                         return FALSE;
4569                 if (line->type == LINE_DIFF_CHUNK ||
4570                     line->type == LINE_DIFF_HEADER)
4571                         break;
4572         }
4574         return TRUE;
4577 static struct line *
4578 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4580         for (; view->line < line; line--)
4581                 if (line->type == type)
4582                         return line;
4584         return NULL;
4587 static bool
4588 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4590         char cmd[SIZEOF_STR];
4591         size_t cmdsize = 0;
4592         struct line *diff_hdr;
4593         FILE *pipe;
4595         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4596         if (!diff_hdr)
4597                 return FALSE;
4599         if (opt_cdup[0] &&
4600             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4601                 return FALSE;
4603         if (!string_format_from(cmd, &cmdsize,
4604                                 "git apply --whitespace=nowarn %s %s - && "
4605                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4606                                 revert ? "" : "--cached",
4607                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4608                 return FALSE;
4610         pipe = popen(cmd, "w");
4611         if (!pipe)
4612                 return FALSE;
4614         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4615             !stage_diff_write(pipe, chunk, view->line + view->lines))
4616                 chunk = NULL;
4618         pclose(pipe);
4620         return chunk ? TRUE : FALSE;
4623 static bool
4624 stage_update(struct view *view, struct line *line)
4626         struct line *chunk = NULL;
4628         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4629                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4631         if (chunk) {
4632                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4633                         report("Failed to apply chunk");
4634                         return FALSE;
4635                 }
4637         } else if (!stage_status.status) {
4638                 view = VIEW(REQ_VIEW_STATUS);
4640                 for (line = view->line; line < view->line + view->lines; line++)
4641                         if (line->type == stage_line_type)
4642                                 break;
4644                 if (!status_update_files(view, line + 1)) {
4645                         report("Failed to update files");
4646                         return FALSE;
4647                 }
4649         } else if (!status_update_file(&stage_status, stage_line_type)) {
4650                 report("Failed to update file");
4651                 return FALSE;
4652         }
4654         return TRUE;
4657 static bool
4658 stage_revert(struct view *view, struct line *line)
4660         struct line *chunk = NULL;
4662         if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4663                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4665         if (chunk) {
4666                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4667                         return FALSE;
4669                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4670                         report("Failed to revert chunk");
4671                         return FALSE;
4672                 }
4673                 return TRUE;
4675         } else {
4676                 return status_revert(stage_status.status ? &stage_status : NULL,
4677                                      stage_line_type, FALSE);
4678         }
4682 static void
4683 stage_next(struct view *view, struct line *line)
4685         int i;
4687         if (!stage_chunks) {
4688                 static size_t alloc = 0;
4689                 int *tmp;
4691                 for (line = view->line; line < view->line + view->lines; line++) {
4692                         if (line->type != LINE_DIFF_CHUNK)
4693                                 continue;
4695                         tmp = realloc_items(stage_chunk, &alloc,
4696                                             stage_chunks, sizeof(*tmp));
4697                         if (!tmp) {
4698                                 report("Allocation failure");
4699                                 return;
4700                         }
4702                         stage_chunk = tmp;
4703                         stage_chunk[stage_chunks++] = line - view->line;
4704                 }
4705         }
4707         for (i = 0; i < stage_chunks; i++) {
4708                 if (stage_chunk[i] > view->lineno) {
4709                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4710                         report("Chunk %d of %d", i + 1, stage_chunks);
4711                         return;
4712                 }
4713         }
4715         report("No next chunk found");
4718 static enum request
4719 stage_request(struct view *view, enum request request, struct line *line)
4721         switch (request) {
4722         case REQ_STATUS_UPDATE:
4723                 if (!stage_update(view, line))
4724                         return REQ_NONE;
4725                 break;
4727         case REQ_STATUS_REVERT:
4728                 if (!stage_revert(view, line))
4729                         return REQ_NONE;
4730                 break;
4732         case REQ_STAGE_NEXT:
4733                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4734                         report("File is untracked; press %s to add",
4735                                get_key(REQ_STATUS_UPDATE));
4736                         return REQ_NONE;
4737                 }
4738                 stage_next(view, line);
4739                 return REQ_NONE;
4741         case REQ_EDIT:
4742                 if (!stage_status.new.name[0])
4743                         return request;
4745                 open_editor(stage_status.status != '?', stage_status.new.name);
4746                 break;
4748         case REQ_REFRESH:
4749                 /* Reload everything ... */
4750                 break;
4752         case REQ_VIEW_BLAME:
4753                 if (stage_status.new.name[0]) {
4754                         string_copy(opt_file, stage_status.new.name);
4755                         opt_ref[0] = 0;
4756                 }
4757                 return request;
4759         case REQ_ENTER:
4760                 return pager_request(view, request, line);
4762         default:
4763                 return request;
4764         }
4766         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4768         /* Check whether the staged entry still exists, and close the
4769          * stage view if it doesn't. */
4770         if (!status_exists(&stage_status, stage_line_type))
4771                 return REQ_VIEW_CLOSE;
4773         if (stage_line_type == LINE_STAT_UNTRACKED)
4774                 opt_pipe = fopen(stage_status.new.name, "r");
4775         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4777         return REQ_NONE;
4780 static struct view_ops stage_ops = {
4781         "line",
4782         NULL,
4783         pager_read,
4784         pager_draw,
4785         stage_request,
4786         pager_grep,
4787         pager_select,
4788 };
4791 /*
4792  * Revision graph
4793  */
4795 struct commit {
4796         char id[SIZEOF_REV];            /* SHA1 ID. */
4797         char title[128];                /* First line of the commit message. */
4798         char author[75];                /* Author of the commit. */
4799         struct tm time;                 /* Date from the author ident. */
4800         struct ref **refs;              /* Repository references. */
4801         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4802         size_t graph_size;              /* The width of the graph array. */
4803         bool has_parents;               /* Rewritten --parents seen. */
4804 };
4806 /* Size of rev graph with no  "padding" columns */
4807 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4809 struct rev_graph {
4810         struct rev_graph *prev, *next, *parents;
4811         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4812         size_t size;
4813         struct commit *commit;
4814         size_t pos;
4815         unsigned int boundary:1;
4816 };
4818 /* Parents of the commit being visualized. */
4819 static struct rev_graph graph_parents[4];
4821 /* The current stack of revisions on the graph. */
4822 static struct rev_graph graph_stacks[4] = {
4823         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4824         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4825         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4826         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4827 };
4829 static inline bool
4830 graph_parent_is_merge(struct rev_graph *graph)
4832         return graph->parents->size > 1;
4835 static inline void
4836 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4838         struct commit *commit = graph->commit;
4840         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4841                 commit->graph[commit->graph_size++] = symbol;
4844 static void
4845 clear_rev_graph(struct rev_graph *graph)
4847         graph->boundary = 0;
4848         graph->size = graph->pos = 0;
4849         graph->commit = NULL;
4850         memset(graph->parents, 0, sizeof(*graph->parents));
4853 static void
4854 done_rev_graph(struct rev_graph *graph)
4856         if (graph_parent_is_merge(graph) &&
4857             graph->pos < graph->size - 1 &&
4858             graph->next->size == graph->size + graph->parents->size - 1) {
4859                 size_t i = graph->pos + graph->parents->size - 1;
4861                 graph->commit->graph_size = i * 2;
4862                 while (i < graph->next->size - 1) {
4863                         append_to_rev_graph(graph, ' ');
4864                         append_to_rev_graph(graph, '\\');
4865                         i++;
4866                 }
4867         }
4869         clear_rev_graph(graph);
4872 static void
4873 push_rev_graph(struct rev_graph *graph, const char *parent)
4875         int i;
4877         /* "Collapse" duplicate parents lines.
4878          *
4879          * FIXME: This needs to also update update the drawn graph but
4880          * for now it just serves as a method for pruning graph lines. */
4881         for (i = 0; i < graph->size; i++)
4882                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4883                         return;
4885         if (graph->size < SIZEOF_REVITEMS) {
4886                 string_copy_rev(graph->rev[graph->size++], parent);
4887         }
4890 static chtype
4891 get_rev_graph_symbol(struct rev_graph *graph)
4893         chtype symbol;
4895         if (graph->boundary)
4896                 symbol = REVGRAPH_BOUND;
4897         else if (graph->parents->size == 0)
4898                 symbol = REVGRAPH_INIT;
4899         else if (graph_parent_is_merge(graph))
4900                 symbol = REVGRAPH_MERGE;
4901         else if (graph->pos >= graph->size)
4902                 symbol = REVGRAPH_BRANCH;
4903         else
4904                 symbol = REVGRAPH_COMMIT;
4906         return symbol;
4909 static void
4910 draw_rev_graph(struct rev_graph *graph)
4912         struct rev_filler {
4913                 chtype separator, line;
4914         };
4915         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4916         static struct rev_filler fillers[] = {
4917                 { ' ',  '|' },
4918                 { '`',  '.' },
4919                 { '\'', ' ' },
4920                 { '/',  ' ' },
4921         };
4922         chtype symbol = get_rev_graph_symbol(graph);
4923         struct rev_filler *filler;
4924         size_t i;
4926         if (opt_line_graphics)
4927                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4929         filler = &fillers[DEFAULT];
4931         for (i = 0; i < graph->pos; i++) {
4932                 append_to_rev_graph(graph, filler->line);
4933                 if (graph_parent_is_merge(graph->prev) &&
4934                     graph->prev->pos == i)
4935                         filler = &fillers[RSHARP];
4937                 append_to_rev_graph(graph, filler->separator);
4938         }
4940         /* Place the symbol for this revision. */
4941         append_to_rev_graph(graph, symbol);
4943         if (graph->prev->size > graph->size)
4944                 filler = &fillers[RDIAG];
4945         else
4946                 filler = &fillers[DEFAULT];
4948         i++;
4950         for (; i < graph->size; i++) {
4951                 append_to_rev_graph(graph, filler->separator);
4952                 append_to_rev_graph(graph, filler->line);
4953                 if (graph_parent_is_merge(graph->prev) &&
4954                     i < graph->prev->pos + graph->parents->size)
4955                         filler = &fillers[RSHARP];
4956                 if (graph->prev->size > graph->size)
4957                         filler = &fillers[LDIAG];
4958         }
4960         if (graph->prev->size > graph->size) {
4961                 append_to_rev_graph(graph, filler->separator);
4962                 if (filler->line != ' ')
4963                         append_to_rev_graph(graph, filler->line);
4964         }
4967 /* Prepare the next rev graph */
4968 static void
4969 prepare_rev_graph(struct rev_graph *graph)
4971         size_t i;
4973         /* First, traverse all lines of revisions up to the active one. */
4974         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4975                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4976                         break;
4978                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4979         }
4981         /* Interleave the new revision parent(s). */
4982         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4983                 push_rev_graph(graph->next, graph->parents->rev[i]);
4985         /* Lastly, put any remaining revisions. */
4986         for (i = graph->pos + 1; i < graph->size; i++)
4987                 push_rev_graph(graph->next, graph->rev[i]);
4990 static void
4991 update_rev_graph(struct rev_graph *graph)
4993         /* If this is the finalizing update ... */
4994         if (graph->commit)
4995                 prepare_rev_graph(graph);
4997         /* Graph visualization needs a one rev look-ahead,
4998          * so the first update doesn't visualize anything. */
4999         if (!graph->prev->commit)
5000                 return;
5002         draw_rev_graph(graph->prev);
5003         done_rev_graph(graph->prev->prev);
5007 /*
5008  * Main view backend
5009  */
5011 static bool
5012 main_draw(struct view *view, struct line *line, unsigned int lineno)
5014         struct commit *commit = line->data;
5016         if (!*commit->author)
5017                 return FALSE;
5019         if (opt_date && draw_date(view, &commit->time))
5020                 return TRUE;
5022         if (opt_author &&
5023             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5024                 return TRUE;
5026         if (opt_rev_graph && commit->graph_size &&
5027             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5028                 return TRUE;
5030         if (opt_show_refs && commit->refs) {
5031                 size_t i = 0;
5033                 do {
5034                         enum line_type type;
5036                         if (commit->refs[i]->head)
5037                                 type = LINE_MAIN_HEAD;
5038                         else if (commit->refs[i]->ltag)
5039                                 type = LINE_MAIN_LOCAL_TAG;
5040                         else if (commit->refs[i]->tag)
5041                                 type = LINE_MAIN_TAG;
5042                         else if (commit->refs[i]->tracked)
5043                                 type = LINE_MAIN_TRACKED;
5044                         else if (commit->refs[i]->remote)
5045                                 type = LINE_MAIN_REMOTE;
5046                         else
5047                                 type = LINE_MAIN_REF;
5049                         if (draw_text(view, type, "[", TRUE) ||
5050                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5051                             draw_text(view, type, "]", TRUE))
5052                                 return TRUE;
5054                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5055                                 return TRUE;
5056                 } while (commit->refs[i++]->next);
5057         }
5059         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5060         return TRUE;
5063 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5064 static bool
5065 main_read(struct view *view, char *line)
5067         static struct rev_graph *graph = graph_stacks;
5068         enum line_type type;
5069         struct commit *commit;
5071         if (!line) {
5072                 int i;
5074                 if (!view->lines && !view->parent)
5075                         die("No revisions match the given arguments.");
5076                 if (view->lines > 0) {
5077                         commit = view->line[view->lines - 1].data;
5078                         if (!*commit->author) {
5079                                 view->lines--;
5080                                 free(commit);
5081                                 graph->commit = NULL;
5082                         }
5083                 }
5084                 update_rev_graph(graph);
5086                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5087                         clear_rev_graph(&graph_stacks[i]);
5088                 return TRUE;
5089         }
5091         type = get_line_type(line);
5092         if (type == LINE_COMMIT) {
5093                 commit = calloc(1, sizeof(struct commit));
5094                 if (!commit)
5095                         return FALSE;
5097                 line += STRING_SIZE("commit ");
5098                 if (*line == '-') {
5099                         graph->boundary = 1;
5100                         line++;
5101                 }
5103                 string_copy_rev(commit->id, line);
5104                 commit->refs = get_refs(commit->id);
5105                 graph->commit = commit;
5106                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5108                 while ((line = strchr(line, ' '))) {
5109                         line++;
5110                         push_rev_graph(graph->parents, line);
5111                         commit->has_parents = TRUE;
5112                 }
5113                 return TRUE;
5114         }
5116         if (!view->lines)
5117                 return TRUE;
5118         commit = view->line[view->lines - 1].data;
5120         switch (type) {
5121         case LINE_PARENT:
5122                 if (commit->has_parents)
5123                         break;
5124                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5125                 break;
5127         case LINE_AUTHOR:
5128         {
5129                 /* Parse author lines where the name may be empty:
5130                  *      author  <email@address.tld> 1138474660 +0100
5131                  */
5132                 char *ident = line + STRING_SIZE("author ");
5133                 char *nameend = strchr(ident, '<');
5134                 char *emailend = strchr(ident, '>');
5136                 if (!nameend || !emailend)
5137                         break;
5139                 update_rev_graph(graph);
5140                 graph = graph->next;
5142                 *nameend = *emailend = 0;
5143                 ident = chomp_string(ident);
5144                 if (!*ident) {
5145                         ident = chomp_string(nameend + 1);
5146                         if (!*ident)
5147                                 ident = "Unknown";
5148                 }
5150                 string_ncopy(commit->author, ident, strlen(ident));
5152                 /* Parse epoch and timezone */
5153                 if (emailend[1] == ' ') {
5154                         char *secs = emailend + 2;
5155                         char *zone = strchr(secs, ' ');
5156                         time_t time = (time_t) atol(secs);
5158                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5159                                 long tz;
5161                                 zone++;
5162                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5163                                 tz += ('0' - zone[2]) * 60 * 60;
5164                                 tz += ('0' - zone[3]) * 60;
5165                                 tz += ('0' - zone[4]) * 60;
5167                                 if (zone[0] == '-')
5168                                         tz = -tz;
5170                                 time -= tz;
5171                         }
5173                         gmtime_r(&time, &commit->time);
5174                 }
5175                 break;
5176         }
5177         default:
5178                 /* Fill in the commit title if it has not already been set. */
5179                 if (commit->title[0])
5180                         break;
5182                 /* Require titles to start with a non-space character at the
5183                  * offset used by git log. */
5184                 if (strncmp(line, "    ", 4))
5185                         break;
5186                 line += 4;
5187                 /* Well, if the title starts with a whitespace character,
5188                  * try to be forgiving.  Otherwise we end up with no title. */
5189                 while (isspace(*line))
5190                         line++;
5191                 if (*line == '\0')
5192                         break;
5193                 /* FIXME: More graceful handling of titles; append "..." to
5194                  * shortened titles, etc. */
5196                 string_ncopy(commit->title, line, strlen(line));
5197         }
5199         return TRUE;
5202 static enum request
5203 main_request(struct view *view, enum request request, struct line *line)
5205         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5207         switch (request) {
5208         case REQ_ENTER:
5209                 open_view(view, REQ_VIEW_DIFF, flags);
5210                 break;
5211         case REQ_REFRESH:
5212                 load_refs();
5213                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5214                 break;
5215         default:
5216                 return request;
5217         }
5219         return REQ_NONE;
5222 static bool
5223 grep_refs(struct ref **refs, regex_t *regex)
5225         regmatch_t pmatch;
5226         size_t i = 0;
5228         if (!refs)
5229                 return FALSE;
5230         do {
5231                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5232                         return TRUE;
5233         } while (refs[i++]->next);
5235         return FALSE;
5238 static bool
5239 main_grep(struct view *view, struct line *line)
5241         struct commit *commit = line->data;
5242         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5243         char buf[DATE_COLS + 1];
5244         regmatch_t pmatch;
5246         for (state = S_TITLE; state < S_END; state++) {
5247                 char *text;
5249                 switch (state) {
5250                 case S_TITLE:   text = commit->title;   break;
5251                 case S_AUTHOR:
5252                         if (!opt_author)
5253                                 continue;
5254                         text = commit->author;
5255                         break;
5256                 case S_DATE:
5257                         if (!opt_date)
5258                                 continue;
5259                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5260                                 continue;
5261                         text = buf;
5262                         break;
5263                 case S_REFS:
5264                         if (!opt_show_refs)
5265                                 continue;
5266                         if (grep_refs(commit->refs, view->regex) == TRUE)
5267                                 return TRUE;
5268                         continue;
5269                 default:
5270                         return FALSE;
5271                 }
5273                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5274                         return TRUE;
5275         }
5277         return FALSE;
5280 static void
5281 main_select(struct view *view, struct line *line)
5283         struct commit *commit = line->data;
5285         string_copy_rev(view->ref, commit->id);
5286         string_copy_rev(ref_commit, view->ref);
5289 static struct view_ops main_ops = {
5290         "commit",
5291         NULL,
5292         main_read,
5293         main_draw,
5294         main_request,
5295         main_grep,
5296         main_select,
5297 };
5300 /*
5301  * Unicode / UTF-8 handling
5302  *
5303  * NOTE: Much of the following code for dealing with unicode is derived from
5304  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5305  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5306  */
5308 /* I've (over)annotated a lot of code snippets because I am not entirely
5309  * confident that the approach taken by this small UTF-8 interface is correct.
5310  * --jonas */
5312 static inline int
5313 unicode_width(unsigned long c)
5315         if (c >= 0x1100 &&
5316            (c <= 0x115f                         /* Hangul Jamo */
5317             || c == 0x2329
5318             || c == 0x232a
5319             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5320                                                 /* CJK ... Yi */
5321             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5322             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5323             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5324             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5325             || (c >= 0xffe0  && c <= 0xffe6)
5326             || (c >= 0x20000 && c <= 0x2fffd)
5327             || (c >= 0x30000 && c <= 0x3fffd)))
5328                 return 2;
5330         if (c == '\t')
5331                 return opt_tab_size;
5333         return 1;
5336 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5337  * Illegal bytes are set one. */
5338 static const unsigned char utf8_bytes[256] = {
5339         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,
5340         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,
5341         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5342         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5343         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5344         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5345         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,
5346         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,
5347 };
5349 /* Decode UTF-8 multi-byte representation into a unicode character. */
5350 static inline unsigned long
5351 utf8_to_unicode(const char *string, size_t length)
5353         unsigned long unicode;
5355         switch (length) {
5356         case 1:
5357                 unicode  =   string[0];
5358                 break;
5359         case 2:
5360                 unicode  =  (string[0] & 0x1f) << 6;
5361                 unicode +=  (string[1] & 0x3f);
5362                 break;
5363         case 3:
5364                 unicode  =  (string[0] & 0x0f) << 12;
5365                 unicode += ((string[1] & 0x3f) << 6);
5366                 unicode +=  (string[2] & 0x3f);
5367                 break;
5368         case 4:
5369                 unicode  =  (string[0] & 0x0f) << 18;
5370                 unicode += ((string[1] & 0x3f) << 12);
5371                 unicode += ((string[2] & 0x3f) << 6);
5372                 unicode +=  (string[3] & 0x3f);
5373                 break;
5374         case 5:
5375                 unicode  =  (string[0] & 0x0f) << 24;
5376                 unicode += ((string[1] & 0x3f) << 18);
5377                 unicode += ((string[2] & 0x3f) << 12);
5378                 unicode += ((string[3] & 0x3f) << 6);
5379                 unicode +=  (string[4] & 0x3f);
5380                 break;
5381         case 6:
5382                 unicode  =  (string[0] & 0x01) << 30;
5383                 unicode += ((string[1] & 0x3f) << 24);
5384                 unicode += ((string[2] & 0x3f) << 18);
5385                 unicode += ((string[3] & 0x3f) << 12);
5386                 unicode += ((string[4] & 0x3f) << 6);
5387                 unicode +=  (string[5] & 0x3f);
5388                 break;
5389         default:
5390                 die("Invalid unicode length");
5391         }
5393         /* Invalid characters could return the special 0xfffd value but NUL
5394          * should be just as good. */
5395         return unicode > 0xffff ? 0 : unicode;
5398 /* Calculates how much of string can be shown within the given maximum width
5399  * and sets trimmed parameter to non-zero value if all of string could not be
5400  * shown. If the reserve flag is TRUE, it will reserve at least one
5401  * trailing character, which can be useful when drawing a delimiter.
5402  *
5403  * Returns the number of bytes to output from string to satisfy max_width. */
5404 static size_t
5405 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5407         const char *start = string;
5408         const char *end = strchr(string, '\0');
5409         unsigned char last_bytes = 0;
5410         size_t last_ucwidth = 0;
5412         *width = 0;
5413         *trimmed = 0;
5415         while (string < end) {
5416                 int c = *(unsigned char *) string;
5417                 unsigned char bytes = utf8_bytes[c];
5418                 size_t ucwidth;
5419                 unsigned long unicode;
5421                 if (string + bytes > end)
5422                         break;
5424                 /* Change representation to figure out whether
5425                  * it is a single- or double-width character. */
5427                 unicode = utf8_to_unicode(string, bytes);
5428                 /* FIXME: Graceful handling of invalid unicode character. */
5429                 if (!unicode)
5430                         break;
5432                 ucwidth = unicode_width(unicode);
5433                 *width  += ucwidth;
5434                 if (*width > max_width) {
5435                         *trimmed = 1;
5436                         *width -= ucwidth;
5437                         if (reserve && *width == max_width) {
5438                                 string -= last_bytes;
5439                                 *width -= last_ucwidth;
5440                         }
5441                         break;
5442                 }
5444                 string  += bytes;
5445                 last_bytes = bytes;
5446                 last_ucwidth = ucwidth;
5447         }
5449         return string - start;
5453 /*
5454  * Status management
5455  */
5457 /* Whether or not the curses interface has been initialized. */
5458 static bool cursed = FALSE;
5460 /* The status window is used for polling keystrokes. */
5461 static WINDOW *status_win;
5463 static bool status_empty = TRUE;
5465 /* Update status and title window. */
5466 static void
5467 report(const char *msg, ...)
5469         struct view *view = display[current_view];
5471         if (input_mode)
5472                 return;
5474         if (!view) {
5475                 char buf[SIZEOF_STR];
5476                 va_list args;
5478                 va_start(args, msg);
5479                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5480                         buf[sizeof(buf) - 1] = 0;
5481                         buf[sizeof(buf) - 2] = '.';
5482                         buf[sizeof(buf) - 3] = '.';
5483                         buf[sizeof(buf) - 4] = '.';
5484                 }
5485                 va_end(args);
5486                 die("%s", buf);
5487         }
5489         if (!status_empty || *msg) {
5490                 va_list args;
5492                 va_start(args, msg);
5494                 wmove(status_win, 0, 0);
5495                 if (*msg) {
5496                         vwprintw(status_win, msg, args);
5497                         status_empty = FALSE;
5498                 } else {
5499                         status_empty = TRUE;
5500                 }
5501                 wclrtoeol(status_win);
5502                 wrefresh(status_win);
5504                 va_end(args);
5505         }
5507         update_view_title(view);
5508         update_display_cursor(view);
5511 /* Controls when nodelay should be in effect when polling user input. */
5512 static void
5513 set_nonblocking_input(bool loading)
5515         static unsigned int loading_views;
5517         if ((loading == FALSE && loading_views-- == 1) ||
5518             (loading == TRUE  && loading_views++ == 0))
5519                 nodelay(status_win, loading);
5522 static void
5523 init_display(void)
5525         int x, y;
5527         /* Initialize the curses library */
5528         if (isatty(STDIN_FILENO)) {
5529                 cursed = !!initscr();
5530         } else {
5531                 /* Leave stdin and stdout alone when acting as a pager. */
5532                 FILE *io = fopen("/dev/tty", "r+");
5534                 if (!io)
5535                         die("Failed to open /dev/tty");
5536                 cursed = !!newterm(NULL, io, io);
5537         }
5539         if (!cursed)
5540                 die("Failed to initialize curses");
5542         nonl();         /* Tell curses not to do NL->CR/NL on output */
5543         cbreak();       /* Take input chars one at a time, no wait for \n */
5544         noecho();       /* Don't echo input */
5545         leaveok(stdscr, TRUE);
5547         if (has_colors())
5548                 init_colors();
5550         getmaxyx(stdscr, y, x);
5551         status_win = newwin(1, 0, y - 1, 0);
5552         if (!status_win)
5553                 die("Failed to create status window");
5555         /* Enable keyboard mapping */
5556         keypad(status_win, TRUE);
5557         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5559         TABSIZE = opt_tab_size;
5560         if (opt_line_graphics) {
5561                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5562         }
5565 static bool
5566 prompt_yesno(const char *prompt)
5568         enum { WAIT, STOP, CANCEL  } status = WAIT;
5569         bool answer = FALSE;
5571         while (status == WAIT) {
5572                 struct view *view;
5573                 int i, key;
5575                 input_mode = TRUE;
5577                 foreach_view (view, i)
5578                         update_view(view);
5580                 input_mode = FALSE;
5582                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5583                 wclrtoeol(status_win);
5585                 /* Refresh, accept single keystroke of input */
5586                 key = wgetch(status_win);
5587                 switch (key) {
5588                 case ERR:
5589                         break;
5591                 case 'y':
5592                 case 'Y':
5593                         answer = TRUE;
5594                         status = STOP;
5595                         break;
5597                 case KEY_ESC:
5598                 case KEY_RETURN:
5599                 case KEY_ENTER:
5600                 case KEY_BACKSPACE:
5601                 case 'n':
5602                 case 'N':
5603                 case '\n':
5604                 default:
5605                         answer = FALSE;
5606                         status = CANCEL;
5607                 }
5608         }
5610         /* Clear the status window */
5611         status_empty = FALSE;
5612         report("");
5614         return answer;
5617 static char *
5618 read_prompt(const char *prompt)
5620         enum { READING, STOP, CANCEL } status = READING;
5621         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5622         int pos = 0;
5624         while (status == READING) {
5625                 struct view *view;
5626                 int i, key;
5628                 input_mode = TRUE;
5630                 foreach_view (view, i)
5631                         update_view(view);
5633                 input_mode = FALSE;
5635                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5636                 wclrtoeol(status_win);
5638                 /* Refresh, accept single keystroke of input */
5639                 key = wgetch(status_win);
5640                 switch (key) {
5641                 case KEY_RETURN:
5642                 case KEY_ENTER:
5643                 case '\n':
5644                         status = pos ? STOP : CANCEL;
5645                         break;
5647                 case KEY_BACKSPACE:
5648                         if (pos > 0)
5649                                 pos--;
5650                         else
5651                                 status = CANCEL;
5652                         break;
5654                 case KEY_ESC:
5655                         status = CANCEL;
5656                         break;
5658                 case ERR:
5659                         break;
5661                 default:
5662                         if (pos >= sizeof(buf)) {
5663                                 report("Input string too long");
5664                                 return NULL;
5665                         }
5667                         if (isprint(key))
5668                                 buf[pos++] = (char) key;
5669                 }
5670         }
5672         /* Clear the status window */
5673         status_empty = FALSE;
5674         report("");
5676         if (status == CANCEL)
5677                 return NULL;
5679         buf[pos++] = 0;
5681         return buf;
5684 /*
5685  * Repository references
5686  */
5688 static struct ref *refs = NULL;
5689 static size_t refs_alloc = 0;
5690 static size_t refs_size = 0;
5692 /* Id <-> ref store */
5693 static struct ref ***id_refs = NULL;
5694 static size_t id_refs_alloc = 0;
5695 static size_t id_refs_size = 0;
5697 static int
5698 compare_refs(const void *ref1_, const void *ref2_)
5700         const struct ref *ref1 = *(const struct ref **)ref1_;
5701         const struct ref *ref2 = *(const struct ref **)ref2_;
5703         if (ref1->tag != ref2->tag)
5704                 return ref2->tag - ref1->tag;
5705         if (ref1->ltag != ref2->ltag)
5706                 return ref2->ltag - ref2->ltag;
5707         if (ref1->head != ref2->head)
5708                 return ref2->head - ref1->head;
5709         if (ref1->tracked != ref2->tracked)
5710                 return ref2->tracked - ref1->tracked;
5711         if (ref1->remote != ref2->remote)
5712                 return ref2->remote - ref1->remote;
5713         return strcmp(ref1->name, ref2->name);
5716 static struct ref **
5717 get_refs(const char *id)
5719         struct ref ***tmp_id_refs;
5720         struct ref **ref_list = NULL;
5721         size_t ref_list_alloc = 0;
5722         size_t ref_list_size = 0;
5723         size_t i;
5725         for (i = 0; i < id_refs_size; i++)
5726                 if (!strcmp(id, id_refs[i][0]->id))
5727                         return id_refs[i];
5729         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5730                                     sizeof(*id_refs));
5731         if (!tmp_id_refs)
5732                 return NULL;
5734         id_refs = tmp_id_refs;
5736         for (i = 0; i < refs_size; i++) {
5737                 struct ref **tmp;
5739                 if (strcmp(id, refs[i].id))
5740                         continue;
5742                 tmp = realloc_items(ref_list, &ref_list_alloc,
5743                                     ref_list_size + 1, sizeof(*ref_list));
5744                 if (!tmp) {
5745                         if (ref_list)
5746                                 free(ref_list);
5747                         return NULL;
5748                 }
5750                 ref_list = tmp;
5751                 ref_list[ref_list_size] = &refs[i];
5752                 /* XXX: The properties of the commit chains ensures that we can
5753                  * safely modify the shared ref. The repo references will
5754                  * always be similar for the same id. */
5755                 ref_list[ref_list_size]->next = 1;
5757                 ref_list_size++;
5758         }
5760         if (ref_list) {
5761                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5762                 ref_list[ref_list_size - 1]->next = 0;
5763                 id_refs[id_refs_size++] = ref_list;
5764         }
5766         return ref_list;
5769 static int
5770 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5772         struct ref *ref;
5773         bool tag = FALSE;
5774         bool ltag = FALSE;
5775         bool remote = FALSE;
5776         bool tracked = FALSE;
5777         bool check_replace = FALSE;
5778         bool head = FALSE;
5780         if (!prefixcmp(name, "refs/tags/")) {
5781                 if (!strcmp(name + namelen - 3, "^{}")) {
5782                         namelen -= 3;
5783                         name[namelen] = 0;
5784                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5785                                 check_replace = TRUE;
5786                 } else {
5787                         ltag = TRUE;
5788                 }
5790                 tag = TRUE;
5791                 namelen -= STRING_SIZE("refs/tags/");
5792                 name    += STRING_SIZE("refs/tags/");
5794         } else if (!prefixcmp(name, "refs/remotes/")) {
5795                 remote = TRUE;
5796                 namelen -= STRING_SIZE("refs/remotes/");
5797                 name    += STRING_SIZE("refs/remotes/");
5798                 tracked  = !strcmp(opt_remote, name);
5800         } else if (!prefixcmp(name, "refs/heads/")) {
5801                 namelen -= STRING_SIZE("refs/heads/");
5802                 name    += STRING_SIZE("refs/heads/");
5803                 head     = !strncmp(opt_head, name, namelen);
5805         } else if (!strcmp(name, "HEAD")) {
5806                 opt_no_head = FALSE;
5807                 return OK;
5808         }
5810         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5811                 /* it's an annotated tag, replace the previous sha1 with the
5812                  * resolved commit id; relies on the fact git-ls-remote lists
5813                  * the commit id of an annotated tag right before the commit id
5814                  * it points to. */
5815                 refs[refs_size - 1].ltag = ltag;
5816                 string_copy_rev(refs[refs_size - 1].id, id);
5818                 return OK;
5819         }
5820         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5821         if (!refs)
5822                 return ERR;
5824         ref = &refs[refs_size++];
5825         ref->name = malloc(namelen + 1);
5826         if (!ref->name)
5827                 return ERR;
5829         strncpy(ref->name, name, namelen);
5830         ref->name[namelen] = 0;
5831         ref->head = head;
5832         ref->tag = tag;
5833         ref->ltag = ltag;
5834         ref->remote = remote;
5835         ref->tracked = tracked;
5836         string_copy_rev(ref->id, id);
5838         return OK;
5841 static int
5842 load_refs(void)
5844         const char *cmd_env = getenv("TIG_LS_REMOTE");
5845         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5847         if (!*opt_git_dir)
5848                 return OK;
5850         while (refs_size > 0)
5851                 free(refs[--refs_size].name);
5852         while (id_refs_size > 0)
5853                 free(id_refs[--id_refs_size]);
5855         return read_properties(popen(cmd, "r"), "\t", read_ref);
5858 static int
5859 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5861         if (!strcmp(name, "i18n.commitencoding"))
5862                 string_ncopy(opt_encoding, value, valuelen);
5864         if (!strcmp(name, "core.editor"))
5865                 string_ncopy(opt_editor, value, valuelen);
5867         /* branch.<head>.remote */
5868         if (*opt_head &&
5869             !strncmp(name, "branch.", 7) &&
5870             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5871             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5872                 string_ncopy(opt_remote, value, valuelen);
5874         if (*opt_head && *opt_remote &&
5875             !strncmp(name, "branch.", 7) &&
5876             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5877             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5878                 size_t from = strlen(opt_remote);
5880                 if (!prefixcmp(value, "refs/heads/")) {
5881                         value += STRING_SIZE("refs/heads/");
5882                         valuelen -= STRING_SIZE("refs/heads/");
5883                 }
5885                 if (!string_format_from(opt_remote, &from, "/%s", value))
5886                         opt_remote[0] = 0;
5887         }
5889         return OK;
5892 static int
5893 load_git_config(void)
5895         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5896                                "=", read_repo_config_option);
5899 static int
5900 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5902         if (!opt_git_dir[0]) {
5903                 string_ncopy(opt_git_dir, name, namelen);
5905         } else if (opt_is_inside_work_tree == -1) {
5906                 /* This can be 3 different values depending on the
5907                  * version of git being used. If git-rev-parse does not
5908                  * understand --is-inside-work-tree it will simply echo
5909                  * the option else either "true" or "false" is printed.
5910                  * Default to true for the unknown case. */
5911                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5913         } else if (opt_cdup[0] == ' ') {
5914                 string_ncopy(opt_cdup, name, namelen);
5915         } else {
5916                 if (!prefixcmp(name, "refs/heads/")) {
5917                         namelen -= STRING_SIZE("refs/heads/");
5918                         name    += STRING_SIZE("refs/heads/");
5919                         string_ncopy(opt_head, name, namelen);
5920                 }
5921         }
5923         return OK;
5926 static int
5927 load_repo_info(void)
5929         int result;
5930         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5931                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5933         /* XXX: The line outputted by "--show-cdup" can be empty so
5934          * initialize it to something invalid to make it possible to
5935          * detect whether it has been set or not. */
5936         opt_cdup[0] = ' ';
5938         result = read_properties(pipe, "=", read_repo_info);
5939         if (opt_cdup[0] == ' ')
5940                 opt_cdup[0] = 0;
5942         return result;
5945 static int
5946 read_properties(FILE *pipe, const char *separators,
5947                 int (*read_property)(char *, size_t, char *, size_t))
5949         char buffer[BUFSIZ];
5950         char *name;
5951         int state = OK;
5953         if (!pipe)
5954                 return ERR;
5956         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5957                 char *value;
5958                 size_t namelen;
5959                 size_t valuelen;
5961                 name = chomp_string(name);
5962                 namelen = strcspn(name, separators);
5964                 if (name[namelen]) {
5965                         name[namelen] = 0;
5966                         value = chomp_string(name + namelen + 1);
5967                         valuelen = strlen(value);
5969                 } else {
5970                         value = "";
5971                         valuelen = 0;
5972                 }
5974                 state = read_property(name, namelen, value, valuelen);
5975         }
5977         if (state != ERR && ferror(pipe))
5978                 state = ERR;
5980         pclose(pipe);
5982         return state;
5986 /*
5987  * Main
5988  */
5990 static void __NORETURN
5991 quit(int sig)
5993         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5994         if (cursed)
5995                 endwin();
5996         exit(0);
5999 static void __NORETURN
6000 die(const char *err, ...)
6002         va_list args;
6004         endwin();
6006         va_start(args, err);
6007         fputs("tig: ", stderr);
6008         vfprintf(stderr, err, args);
6009         fputs("\n", stderr);
6010         va_end(args);
6012         exit(1);
6015 static void
6016 warn(const char *msg, ...)
6018         va_list args;
6020         va_start(args, msg);
6021         fputs("tig warning: ", stderr);
6022         vfprintf(stderr, msg, args);
6023         fputs("\n", stderr);
6024         va_end(args);
6027 int
6028 main(int argc, const char *argv[])
6030         struct view *view;
6031         enum request request;
6032         size_t i;
6034         signal(SIGINT, quit);
6036         if (setlocale(LC_ALL, "")) {
6037                 char *codeset = nl_langinfo(CODESET);
6039                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6040         }
6042         if (load_repo_info() == ERR)
6043                 die("Failed to load repo info.");
6045         if (load_options() == ERR)
6046                 die("Failed to load user config.");
6048         if (load_git_config() == ERR)
6049                 die("Failed to load repo config.");
6051         request = parse_options(argc, argv);
6052         if (request == REQ_NONE)
6053                 return 0;
6055         /* Require a git repository unless when running in pager mode. */
6056         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6057                 die("Not a git repository");
6059         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6060                 opt_utf8 = FALSE;
6062         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6063                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6064                 if (opt_iconv == ICONV_NONE)
6065                         die("Failed to initialize character set conversion");
6066         }
6068         if (load_refs() == ERR)
6069                 die("Failed to load refs.");
6071         foreach_view (view, i)
6072                 view->cmd_env = getenv(view->cmd_env);
6074         init_display();
6076         while (view_driver(display[current_view], request)) {
6077                 int key;
6078                 int i;
6080                 foreach_view (view, i)
6081                         update_view(view);
6082                 view = display[current_view];
6084                 /* Refresh, accept single keystroke of input */
6085                 key = wgetch(status_win);
6087                 /* wgetch() with nodelay() enabled returns ERR when there's no
6088                  * input. */
6089                 if (key == ERR) {
6090                         request = REQ_NONE;
6091                         continue;
6092                 }
6094                 request = get_keybinding(view->keymap, key);
6096                 /* Some low-level request handling. This keeps access to
6097                  * status_win restricted. */
6098                 switch (request) {
6099                 case REQ_PROMPT:
6100                 {
6101                         char *cmd = read_prompt(":");
6103                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6104                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6105                                         request = REQ_VIEW_DIFF;
6106                                 } else {
6107                                         request = REQ_VIEW_PAGER;
6108                                 }
6110                                 /* Always reload^Wrerun commands from the prompt. */
6111                                 open_view(view, request, OPEN_RELOAD);
6112                         }
6114                         request = REQ_NONE;
6115                         break;
6116                 }
6117                 case REQ_SEARCH:
6118                 case REQ_SEARCH_BACK:
6119                 {
6120                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6121                         char *search = read_prompt(prompt);
6123                         if (search)
6124                                 string_ncopy(opt_search, search, strlen(search));
6125                         else
6126                                 request = REQ_NONE;
6127                         break;
6128                 }
6129                 case REQ_SCREEN_RESIZE:
6130                 {
6131                         int height, width;
6133                         getmaxyx(stdscr, height, width);
6135                         /* Resize the status view and let the view driver take
6136                          * care of resizing the displayed views. */
6137                         wresize(status_win, 1, width);
6138                         mvwin(status_win, height - 1, 0);
6139                         wrefresh(status_win);
6140                         break;
6141                 }
6142                 default:
6143                         break;
6144                 }
6145         }
6147         quit(0);
6149         return 0;