Code

Fix waiting for input after executing a run request in pager mode
[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]      = "";
486 static FILE *opt_tty                    = NULL;
488 static enum request
489 parse_options(int argc, const char *argv[])
491         enum request request = REQ_VIEW_MAIN;
492         size_t buf_size;
493         const char *subcommand;
494         bool seen_dashdash = FALSE;
495         int i;
497         if (!isatty(STDIN_FILENO)) {
498                 opt_pipe = stdin;
499                 return REQ_VIEW_PAGER;
500         }
502         if (argc <= 1)
503                 return REQ_VIEW_MAIN;
505         subcommand = argv[1];
506         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
507                 if (!strcmp(subcommand, "-S"))
508                         warn("`-S' has been deprecated; use `tig status' instead");
509                 if (argc > 2)
510                         warn("ignoring arguments after `%s'", subcommand);
511                 return REQ_VIEW_STATUS;
513         } else if (!strcmp(subcommand, "blame")) {
514                 if (argc <= 2 || argc > 4)
515                         die("invalid number of options to blame\n\n%s", usage);
517                 i = 2;
518                 if (argc == 4) {
519                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
520                         i++;
521                 }
523                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
524                 return REQ_VIEW_BLAME;
526         } else if (!strcmp(subcommand, "show")) {
527                 request = REQ_VIEW_DIFF;
529         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
530                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
531                 warn("`tig %s' has been deprecated", subcommand);
533         } else {
534                 subcommand = NULL;
535         }
537         if (!subcommand)
538                 /* XXX: This is vulnerable to the user overriding
539                  * options required for the main view parser. */
540                 string_copy(opt_cmd, TIG_MAIN_BASE);
541         else
542                 string_format(opt_cmd, "git %s", subcommand);
544         buf_size = strlen(opt_cmd);
546         for (i = 1 + !!subcommand; i < argc; i++) {
547                 const char *opt = argv[i];
549                 if (seen_dashdash || !strcmp(opt, "--")) {
550                         seen_dashdash = TRUE;
552                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
553                         printf("tig version %s\n", TIG_VERSION);
554                         return REQ_NONE;
556                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
557                         printf("%s\n", usage);
558                         return REQ_NONE;
559                 }
561                 opt_cmd[buf_size++] = ' ';
562                 buf_size = sq_quote(opt_cmd, buf_size, opt);
563                 if (buf_size >= sizeof(opt_cmd))
564                         die("command too long");
565         }
567         opt_cmd[buf_size] = 0;
569         return request;
573 /*
574  * Line-oriented content detection.
575  */
577 #define LINE_INFO \
578 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
579 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
580 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
581 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
582 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
583 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
584 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
585 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
586 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
587 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
588 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
589 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
590 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
591 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
592 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
593 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
594 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
595 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
596 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
597 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
599 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
600 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
601 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
602 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
603 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
604 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
606 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
607 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
608 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
609 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
611 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
612 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
613 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
614 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
615 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
616 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
617 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
618 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
619 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
620 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
621 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
622 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
623 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
624 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
625 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
626 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
627 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
628 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
629 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
630 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
631 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
633 enum line_type {
634 #define LINE(type, line, fg, bg, attr) \
635         LINE_##type
636         LINE_INFO,
637         LINE_NONE
638 #undef  LINE
639 };
641 struct line_info {
642         const char *name;       /* Option name. */
643         int namelen;            /* Size of option name. */
644         const char *line;       /* The start of line to match. */
645         int linelen;            /* Size of string to match. */
646         int fg, bg, attr;       /* Color and text attributes for the lines. */
647 };
649 static struct line_info line_info[] = {
650 #define LINE(type, line, fg, bg, attr) \
651         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
652         LINE_INFO
653 #undef  LINE
654 };
656 static enum line_type
657 get_line_type(const char *line)
659         int linelen = strlen(line);
660         enum line_type type;
662         for (type = 0; type < ARRAY_SIZE(line_info); type++)
663                 /* Case insensitive search matches Signed-off-by lines better. */
664                 if (linelen >= line_info[type].linelen &&
665                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
666                         return type;
668         return LINE_DEFAULT;
671 static inline int
672 get_line_attr(enum line_type type)
674         assert(type < ARRAY_SIZE(line_info));
675         return COLOR_PAIR(type) | line_info[type].attr;
678 static struct line_info *
679 get_line_info(const char *name)
681         size_t namelen = strlen(name);
682         enum line_type type;
684         for (type = 0; type < ARRAY_SIZE(line_info); type++)
685                 if (namelen == line_info[type].namelen &&
686                     !string_enum_compare(line_info[type].name, name, namelen))
687                         return &line_info[type];
689         return NULL;
692 static void
693 init_colors(void)
695         int default_bg = line_info[LINE_DEFAULT].bg;
696         int default_fg = line_info[LINE_DEFAULT].fg;
697         enum line_type type;
699         start_color();
701         if (assume_default_colors(default_fg, default_bg) == ERR) {
702                 default_bg = COLOR_BLACK;
703                 default_fg = COLOR_WHITE;
704         }
706         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
707                 struct line_info *info = &line_info[type];
708                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
709                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
711                 init_pair(type, fg, bg);
712         }
715 struct line {
716         enum line_type type;
718         /* State flags */
719         unsigned int selected:1;
720         unsigned int dirty:1;
722         void *data;             /* User data */
723 };
726 /*
727  * Keys
728  */
730 struct keybinding {
731         int alias;
732         enum request request;
733         struct keybinding *next;
734 };
736 static struct keybinding default_keybindings[] = {
737         /* View switching */
738         { 'm',          REQ_VIEW_MAIN },
739         { 'd',          REQ_VIEW_DIFF },
740         { 'l',          REQ_VIEW_LOG },
741         { 't',          REQ_VIEW_TREE },
742         { 'f',          REQ_VIEW_BLOB },
743         { 'B',          REQ_VIEW_BLAME },
744         { 'p',          REQ_VIEW_PAGER },
745         { 'h',          REQ_VIEW_HELP },
746         { 'S',          REQ_VIEW_STATUS },
747         { 'c',          REQ_VIEW_STAGE },
749         /* View manipulation */
750         { 'q',          REQ_VIEW_CLOSE },
751         { KEY_TAB,      REQ_VIEW_NEXT },
752         { KEY_RETURN,   REQ_ENTER },
753         { KEY_UP,       REQ_PREVIOUS },
754         { KEY_DOWN,     REQ_NEXT },
755         { 'R',          REQ_REFRESH },
756         { KEY_F(5),     REQ_REFRESH },
757         { 'O',          REQ_MAXIMIZE },
759         /* Cursor navigation */
760         { 'k',          REQ_MOVE_UP },
761         { 'j',          REQ_MOVE_DOWN },
762         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
763         { KEY_END,      REQ_MOVE_LAST_LINE },
764         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
765         { ' ',          REQ_MOVE_PAGE_DOWN },
766         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
767         { 'b',          REQ_MOVE_PAGE_UP },
768         { '-',          REQ_MOVE_PAGE_UP },
770         /* Scrolling */
771         { KEY_IC,       REQ_SCROLL_LINE_UP },
772         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
773         { 'w',          REQ_SCROLL_PAGE_UP },
774         { 's',          REQ_SCROLL_PAGE_DOWN },
776         /* Searching */
777         { '/',          REQ_SEARCH },
778         { '?',          REQ_SEARCH_BACK },
779         { 'n',          REQ_FIND_NEXT },
780         { 'N',          REQ_FIND_PREV },
782         /* Misc */
783         { 'Q',          REQ_QUIT },
784         { 'z',          REQ_STOP_LOADING },
785         { 'v',          REQ_SHOW_VERSION },
786         { 'r',          REQ_SCREEN_REDRAW },
787         { '.',          REQ_TOGGLE_LINENO },
788         { 'D',          REQ_TOGGLE_DATE },
789         { 'A',          REQ_TOGGLE_AUTHOR },
790         { 'g',          REQ_TOGGLE_REV_GRAPH },
791         { 'F',          REQ_TOGGLE_REFS },
792         { ':',          REQ_PROMPT },
793         { 'u',          REQ_STATUS_UPDATE },
794         { '!',          REQ_STATUS_REVERT },
795         { 'M',          REQ_STATUS_MERGE },
796         { '@',          REQ_STAGE_NEXT },
797         { ',',          REQ_TREE_PARENT },
798         { 'e',          REQ_EDIT },
800         /* Using the ncurses SIGWINCH handler. */
801         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
802 };
804 #define KEYMAP_INFO \
805         KEYMAP_(GENERIC), \
806         KEYMAP_(MAIN), \
807         KEYMAP_(DIFF), \
808         KEYMAP_(LOG), \
809         KEYMAP_(TREE), \
810         KEYMAP_(BLOB), \
811         KEYMAP_(BLAME), \
812         KEYMAP_(PAGER), \
813         KEYMAP_(HELP), \
814         KEYMAP_(STATUS), \
815         KEYMAP_(STAGE)
817 enum keymap {
818 #define KEYMAP_(name) KEYMAP_##name
819         KEYMAP_INFO
820 #undef  KEYMAP_
821 };
823 static struct int_map keymap_table[] = {
824 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
825         KEYMAP_INFO
826 #undef  KEYMAP_
827 };
829 #define set_keymap(map, name) \
830         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
832 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
834 static void
835 add_keybinding(enum keymap keymap, enum request request, int key)
837         struct keybinding *keybinding;
839         keybinding = calloc(1, sizeof(*keybinding));
840         if (!keybinding)
841                 die("Failed to allocate keybinding");
843         keybinding->alias = key;
844         keybinding->request = request;
845         keybinding->next = keybindings[keymap];
846         keybindings[keymap] = keybinding;
849 /* Looks for a key binding first in the given map, then in the generic map, and
850  * lastly in the default keybindings. */
851 static enum request
852 get_keybinding(enum keymap keymap, int key)
854         struct keybinding *kbd;
855         int i;
857         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
858                 if (kbd->alias == key)
859                         return kbd->request;
861         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
862                 if (kbd->alias == key)
863                         return kbd->request;
865         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
866                 if (default_keybindings[i].alias == key)
867                         return default_keybindings[i].request;
869         return (enum request) key;
873 struct key {
874         const char *name;
875         int value;
876 };
878 static struct key key_table[] = {
879         { "Enter",      KEY_RETURN },
880         { "Space",      ' ' },
881         { "Backspace",  KEY_BACKSPACE },
882         { "Tab",        KEY_TAB },
883         { "Escape",     KEY_ESC },
884         { "Left",       KEY_LEFT },
885         { "Right",      KEY_RIGHT },
886         { "Up",         KEY_UP },
887         { "Down",       KEY_DOWN },
888         { "Insert",     KEY_IC },
889         { "Delete",     KEY_DC },
890         { "Hash",       '#' },
891         { "Home",       KEY_HOME },
892         { "End",        KEY_END },
893         { "PageUp",     KEY_PPAGE },
894         { "PageDown",   KEY_NPAGE },
895         { "F1",         KEY_F(1) },
896         { "F2",         KEY_F(2) },
897         { "F3",         KEY_F(3) },
898         { "F4",         KEY_F(4) },
899         { "F5",         KEY_F(5) },
900         { "F6",         KEY_F(6) },
901         { "F7",         KEY_F(7) },
902         { "F8",         KEY_F(8) },
903         { "F9",         KEY_F(9) },
904         { "F10",        KEY_F(10) },
905         { "F11",        KEY_F(11) },
906         { "F12",        KEY_F(12) },
907 };
909 static int
910 get_key_value(const char *name)
912         int i;
914         for (i = 0; i < ARRAY_SIZE(key_table); i++)
915                 if (!strcasecmp(key_table[i].name, name))
916                         return key_table[i].value;
918         if (strlen(name) == 1 && isprint(*name))
919                 return (int) *name;
921         return ERR;
924 static const char *
925 get_key_name(int key_value)
927         static char key_char[] = "'X'";
928         const char *seq = NULL;
929         int key;
931         for (key = 0; key < ARRAY_SIZE(key_table); key++)
932                 if (key_table[key].value == key_value)
933                         seq = key_table[key].name;
935         if (seq == NULL &&
936             key_value < 127 &&
937             isprint(key_value)) {
938                 key_char[1] = (char) key_value;
939                 seq = key_char;
940         }
942         return seq ? seq : "(no key)";
945 static const char *
946 get_key(enum request request)
948         static char buf[BUFSIZ];
949         size_t pos = 0;
950         char *sep = "";
951         int i;
953         buf[pos] = 0;
955         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
956                 struct keybinding *keybinding = &default_keybindings[i];
958                 if (keybinding->request != request)
959                         continue;
961                 if (!string_format_from(buf, &pos, "%s%s", sep,
962                                         get_key_name(keybinding->alias)))
963                         return "Too many keybindings!";
964                 sep = ", ";
965         }
967         return buf;
970 struct run_request {
971         enum keymap keymap;
972         int key;
973         char cmd[SIZEOF_STR];
974 };
976 static struct run_request *run_request;
977 static size_t run_requests;
979 static enum request
980 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
982         struct run_request *req;
983         char cmd[SIZEOF_STR];
984         size_t bufpos;
986         for (bufpos = 0; argc > 0; argc--, argv++)
987                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
988                         return REQ_NONE;
990         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
991         if (!req)
992                 return REQ_NONE;
994         run_request = req;
995         req = &run_request[run_requests++];
996         string_copy(req->cmd, cmd);
997         req->keymap = keymap;
998         req->key = key;
1000         return REQ_NONE + run_requests;
1003 static struct run_request *
1004 get_run_request(enum request request)
1006         if (request <= REQ_NONE)
1007                 return NULL;
1008         return &run_request[request - REQ_NONE - 1];
1011 static void
1012 add_builtin_run_requests(void)
1014         struct {
1015                 enum keymap keymap;
1016                 int key;
1017                 const char *argv[1];
1018         } reqs[] = {
1019                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1020                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1021         };
1022         int i;
1024         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1025                 enum request req;
1027                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1028                 if (req != REQ_NONE)
1029                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1030         }
1033 /*
1034  * User config file handling.
1035  */
1037 static struct int_map color_map[] = {
1038 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1039         COLOR_MAP(DEFAULT),
1040         COLOR_MAP(BLACK),
1041         COLOR_MAP(BLUE),
1042         COLOR_MAP(CYAN),
1043         COLOR_MAP(GREEN),
1044         COLOR_MAP(MAGENTA),
1045         COLOR_MAP(RED),
1046         COLOR_MAP(WHITE),
1047         COLOR_MAP(YELLOW),
1048 };
1050 #define set_color(color, name) \
1051         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1053 static struct int_map attr_map[] = {
1054 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1055         ATTR_MAP(NORMAL),
1056         ATTR_MAP(BLINK),
1057         ATTR_MAP(BOLD),
1058         ATTR_MAP(DIM),
1059         ATTR_MAP(REVERSE),
1060         ATTR_MAP(STANDOUT),
1061         ATTR_MAP(UNDERLINE),
1062 };
1064 #define set_attribute(attr, name) \
1065         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1067 static int   config_lineno;
1068 static bool  config_errors;
1069 static const char *config_msg;
1071 /* Wants: object fgcolor bgcolor [attr] */
1072 static int
1073 option_color_command(int argc, const char *argv[])
1075         struct line_info *info;
1077         if (argc != 3 && argc != 4) {
1078                 config_msg = "Wrong number of arguments given to color command";
1079                 return ERR;
1080         }
1082         info = get_line_info(argv[0]);
1083         if (!info) {
1084                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1085                         info = get_line_info("delimiter");
1087                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1088                         info = get_line_info("date");
1090                 } else {
1091                         config_msg = "Unknown color name";
1092                         return ERR;
1093                 }
1094         }
1096         if (set_color(&info->fg, argv[1]) == ERR ||
1097             set_color(&info->bg, argv[2]) == ERR) {
1098                 config_msg = "Unknown color";
1099                 return ERR;
1100         }
1102         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1103                 config_msg = "Unknown attribute";
1104                 return ERR;
1105         }
1107         return OK;
1110 static bool parse_bool(const char *s)
1112         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1113                 !strcmp(s, "yes")) ? TRUE : FALSE;
1116 static int
1117 parse_int(const char *s, int default_value, int min, int max)
1119         int value = atoi(s);
1121         return (value < min || value > max) ? default_value : value;
1124 /* Wants: name = value */
1125 static int
1126 option_set_command(int argc, const char *argv[])
1128         if (argc != 3) {
1129                 config_msg = "Wrong number of arguments given to set command";
1130                 return ERR;
1131         }
1133         if (strcmp(argv[1], "=")) {
1134                 config_msg = "No value assigned";
1135                 return ERR;
1136         }
1138         if (!strcmp(argv[0], "show-author")) {
1139                 opt_author = parse_bool(argv[2]);
1140                 return OK;
1141         }
1143         if (!strcmp(argv[0], "show-date")) {
1144                 opt_date = parse_bool(argv[2]);
1145                 return OK;
1146         }
1148         if (!strcmp(argv[0], "show-rev-graph")) {
1149                 opt_rev_graph = parse_bool(argv[2]);
1150                 return OK;
1151         }
1153         if (!strcmp(argv[0], "show-refs")) {
1154                 opt_show_refs = parse_bool(argv[2]);
1155                 return OK;
1156         }
1158         if (!strcmp(argv[0], "show-line-numbers")) {
1159                 opt_line_number = parse_bool(argv[2]);
1160                 return OK;
1161         }
1163         if (!strcmp(argv[0], "line-graphics")) {
1164                 opt_line_graphics = parse_bool(argv[2]);
1165                 return OK;
1166         }
1168         if (!strcmp(argv[0], "line-number-interval")) {
1169                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1170                 return OK;
1171         }
1173         if (!strcmp(argv[0], "author-width")) {
1174                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1175                 return OK;
1176         }
1178         if (!strcmp(argv[0], "tab-size")) {
1179                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1180                 return OK;
1181         }
1183         if (!strcmp(argv[0], "commit-encoding")) {
1184                 const char *arg = argv[2];
1185                 int arglen = strlen(arg);
1187                 switch (arg[0]) {
1188                 case '"':
1189                 case '\'':
1190                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1191                                 config_msg = "Unmatched quotation";
1192                                 return ERR;
1193                         }
1194                         arg += 1; arglen -= 2;
1195                 default:
1196                         string_ncopy(opt_encoding, arg, strlen(arg));
1197                         return OK;
1198                 }
1199         }
1201         config_msg = "Unknown variable name";
1202         return ERR;
1205 /* Wants: mode request key */
1206 static int
1207 option_bind_command(int argc, const char *argv[])
1209         enum request request;
1210         int keymap;
1211         int key;
1213         if (argc < 3) {
1214                 config_msg = "Wrong number of arguments given to bind command";
1215                 return ERR;
1216         }
1218         if (set_keymap(&keymap, argv[0]) == ERR) {
1219                 config_msg = "Unknown key map";
1220                 return ERR;
1221         }
1223         key = get_key_value(argv[1]);
1224         if (key == ERR) {
1225                 config_msg = "Unknown key";
1226                 return ERR;
1227         }
1229         request = get_request(argv[2]);
1230         if (request == REQ_NONE) {
1231                 const char *obsolete[] = { "cherry-pick" };
1232                 size_t namelen = strlen(argv[2]);
1233                 int i;
1235                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1236                         if (namelen == strlen(obsolete[i]) &&
1237                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1238                                 config_msg = "Obsolete request name";
1239                                 return ERR;
1240                         }
1241                 }
1242         }
1243         if (request == REQ_NONE && *argv[2]++ == '!')
1244                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1245         if (request == REQ_NONE) {
1246                 config_msg = "Unknown request name";
1247                 return ERR;
1248         }
1250         add_keybinding(keymap, request, key);
1252         return OK;
1255 static int
1256 set_option(const char *opt, char *value)
1258         const char *argv[SIZEOF_ARG];
1259         int valuelen;
1260         int argc = 0;
1262         /* Tokenize */
1263         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1264                 argv[argc++] = value;
1265                 value += valuelen;
1267                 /* Nothing more to tokenize or last available token. */
1268                 if (!*value || argc >= ARRAY_SIZE(argv))
1269                         break;
1271                 *value++ = 0;
1272                 while (isspace(*value))
1273                         value++;
1274         }
1276         if (!strcmp(opt, "color"))
1277                 return option_color_command(argc, argv);
1279         if (!strcmp(opt, "set"))
1280                 return option_set_command(argc, argv);
1282         if (!strcmp(opt, "bind"))
1283                 return option_bind_command(argc, argv);
1285         config_msg = "Unknown option command";
1286         return ERR;
1289 static int
1290 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1292         int status = OK;
1294         config_lineno++;
1295         config_msg = "Internal error";
1297         /* Check for comment markers, since read_properties() will
1298          * only ensure opt and value are split at first " \t". */
1299         optlen = strcspn(opt, "#");
1300         if (optlen == 0)
1301                 return OK;
1303         if (opt[optlen] != 0) {
1304                 config_msg = "No option value";
1305                 status = ERR;
1307         }  else {
1308                 /* Look for comment endings in the value. */
1309                 size_t len = strcspn(value, "#");
1311                 if (len < valuelen) {
1312                         valuelen = len;
1313                         value[valuelen] = 0;
1314                 }
1316                 status = set_option(opt, value);
1317         }
1319         if (status == ERR) {
1320                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1321                         config_lineno, (int) optlen, opt, config_msg);
1322                 config_errors = TRUE;
1323         }
1325         /* Always keep going if errors are encountered. */
1326         return OK;
1329 static void
1330 load_option_file(const char *path)
1332         FILE *file;
1334         /* It's ok that the file doesn't exist. */
1335         file = fopen(path, "r");
1336         if (!file)
1337                 return;
1339         config_lineno = 0;
1340         config_errors = FALSE;
1342         if (read_properties(file, " \t", read_option) == ERR ||
1343             config_errors == TRUE)
1344                 fprintf(stderr, "Errors while loading %s.\n", path);
1347 static int
1348 load_options(void)
1350         const char *home = getenv("HOME");
1351         const char *tigrc_user = getenv("TIGRC_USER");
1352         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1353         char buf[SIZEOF_STR];
1355         add_builtin_run_requests();
1357         if (!tigrc_system) {
1358                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1359                         return ERR;
1360                 tigrc_system = buf;
1361         }
1362         load_option_file(tigrc_system);
1364         if (!tigrc_user) {
1365                 if (!home || !string_format(buf, "%s/.tigrc", home))
1366                         return ERR;
1367                 tigrc_user = buf;
1368         }
1369         load_option_file(tigrc_user);
1371         return OK;
1375 /*
1376  * The viewer
1377  */
1379 struct view;
1380 struct view_ops;
1382 /* The display array of active views and the index of the current view. */
1383 static struct view *display[2];
1384 static unsigned int current_view;
1386 /* Reading from the prompt? */
1387 static bool input_mode = FALSE;
1389 #define foreach_displayed_view(view, i) \
1390         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1392 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1394 /* Current head and commit ID */
1395 static char ref_blob[SIZEOF_REF]        = "";
1396 static char ref_commit[SIZEOF_REF]      = "HEAD";
1397 static char ref_head[SIZEOF_REF]        = "HEAD";
1399 struct view {
1400         const char *name;       /* View name */
1401         const char *cmd_fmt;    /* Default command line format */
1402         const char *cmd_env;    /* Command line set via environment */
1403         const char *id;         /* Points to either of ref_{head,commit,blob} */
1405         struct view_ops *ops;   /* View operations */
1407         enum keymap keymap;     /* What keymap does this view have */
1408         bool git_dir;           /* Whether the view requires a git directory. */
1410         char cmd[SIZEOF_STR];   /* Command buffer */
1411         char ref[SIZEOF_REF];   /* Hovered commit reference */
1412         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1414         int height, width;      /* The width and height of the main window */
1415         WINDOW *win;            /* The main window */
1416         WINDOW *title;          /* The title window living below the main window */
1418         /* Navigation */
1419         unsigned long offset;   /* Offset of the window top */
1420         unsigned long lineno;   /* Current line number */
1422         /* Searching */
1423         char grep[SIZEOF_STR];  /* Search string */
1424         regex_t *regex;         /* Pre-compiled regex */
1426         /* If non-NULL, points to the view that opened this view. If this view
1427          * is closed tig will switch back to the parent view. */
1428         struct view *parent;
1430         /* Buffering */
1431         size_t lines;           /* Total number of lines */
1432         struct line *line;      /* Line index */
1433         size_t line_alloc;      /* Total number of allocated lines */
1434         size_t line_size;       /* Total number of used lines */
1435         unsigned int digits;    /* Number of digits in the lines member. */
1437         /* Drawing */
1438         struct line *curline;   /* Line currently being drawn. */
1439         enum line_type curtype; /* Attribute currently used for drawing. */
1440         unsigned long col;      /* Column when drawing. */
1442         /* Loading */
1443         FILE *pipe;
1444         time_t start_time;
1445 };
1447 struct view_ops {
1448         /* What type of content being displayed. Used in the title bar. */
1449         const char *type;
1450         /* Open and reads in all view content. */
1451         bool (*open)(struct view *view);
1452         /* Read one line; updates view->line. */
1453         bool (*read)(struct view *view, char *data);
1454         /* Draw one line; @lineno must be < view->height. */
1455         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1456         /* Depending on view handle a special requests. */
1457         enum request (*request)(struct view *view, enum request request, struct line *line);
1458         /* Search for regex in a line. */
1459         bool (*grep)(struct view *view, struct line *line);
1460         /* Select line */
1461         void (*select)(struct view *view, struct line *line);
1462 };
1464 static struct view_ops blame_ops;
1465 static struct view_ops blob_ops;
1466 static struct view_ops help_ops;
1467 static struct view_ops log_ops;
1468 static struct view_ops main_ops;
1469 static struct view_ops pager_ops;
1470 static struct view_ops stage_ops;
1471 static struct view_ops status_ops;
1472 static struct view_ops tree_ops;
1474 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1475         { name, cmd, #env, ref, ops, map, git }
1477 #define VIEW_(id, name, ops, git, ref) \
1478         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1481 static struct view views[] = {
1482         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1483         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1484         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1485         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1486         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1487         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1488         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1489         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1490         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1491         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1492 };
1494 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1495 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1497 #define foreach_view(view, i) \
1498         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1500 #define view_is_displayed(view) \
1501         (view == display[0] || view == display[1])
1504 enum line_graphic {
1505         LINE_GRAPHIC_VLINE
1506 };
1508 static int line_graphics[] = {
1509         /* LINE_GRAPHIC_VLINE: */ '|'
1510 };
1512 static inline void
1513 set_view_attr(struct view *view, enum line_type type)
1515         if (!view->curline->selected && view->curtype != type) {
1516                 wattrset(view->win, get_line_attr(type));
1517                 wchgat(view->win, -1, 0, type, NULL);
1518                 view->curtype = type;
1519         }
1522 static int
1523 draw_chars(struct view *view, enum line_type type, const char *string,
1524            int max_len, bool use_tilde)
1526         int len = 0;
1527         int col = 0;
1528         int trimmed = FALSE;
1530         if (max_len <= 0)
1531                 return 0;
1533         if (opt_utf8) {
1534                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1535         } else {
1536                 col = len = strlen(string);
1537                 if (len > max_len) {
1538                         if (use_tilde) {
1539                                 max_len -= 1;
1540                         }
1541                         col = len = max_len;
1542                         trimmed = TRUE;
1543                 }
1544         }
1546         set_view_attr(view, type);
1547         waddnstr(view->win, string, len);
1548         if (trimmed && use_tilde) {
1549                 set_view_attr(view, LINE_DELIMITER);
1550                 waddch(view->win, '~');
1551                 col++;
1552         }
1554         return col;
1557 static int
1558 draw_space(struct view *view, enum line_type type, int max, int spaces)
1560         static char space[] = "                    ";
1561         int col = 0;
1563         spaces = MIN(max, spaces);
1565         while (spaces > 0) {
1566                 int len = MIN(spaces, sizeof(space) - 1);
1568                 col += draw_chars(view, type, space, spaces, FALSE);
1569                 spaces -= len;
1570         }
1572         return col;
1575 static bool
1576 draw_lineno(struct view *view, unsigned int lineno)
1578         char number[10];
1579         int digits3 = view->digits < 3 ? 3 : view->digits;
1580         int max_number = MIN(digits3, STRING_SIZE(number));
1581         int max = view->width - view->col;
1582         int col;
1584         if (max < max_number)
1585                 max_number = max;
1587         lineno += view->offset + 1;
1588         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1589                 static char fmt[] = "%1ld";
1591                 if (view->digits <= 9)
1592                         fmt[1] = '0' + digits3;
1594                 if (!string_format(number, fmt, lineno))
1595                         number[0] = 0;
1596                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1597         } else {
1598                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1599         }
1601         if (col < max) {
1602                 set_view_attr(view, LINE_DEFAULT);
1603                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1604                 col++;
1605         }
1607         if (col < max)
1608                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1609         view->col += col;
1611         return view->width - view->col <= 0;
1614 static bool
1615 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1617         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1618         return view->width - view->col <= 0;
1621 static bool
1622 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1624         int max = view->width - view->col;
1625         int i;
1627         if (max < size)
1628                 size = max;
1630         set_view_attr(view, type);
1631         /* Using waddch() instead of waddnstr() ensures that
1632          * they'll be rendered correctly for the cursor line. */
1633         for (i = 0; i < size; i++)
1634                 waddch(view->win, graphic[i]);
1636         view->col += size;
1637         if (size < max) {
1638                 waddch(view->win, ' ');
1639                 view->col++;
1640         }
1642         return view->width - view->col <= 0;
1645 static bool
1646 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1648         int max = MIN(view->width - view->col, len);
1649         int col;
1651         if (text)
1652                 col = draw_chars(view, type, text, max - 1, trim);
1653         else
1654                 col = draw_space(view, type, max - 1, max - 1);
1656         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1657         return view->width - view->col <= 0;
1660 static bool
1661 draw_date(struct view *view, struct tm *time)
1663         char buf[DATE_COLS];
1664         char *date;
1665         int timelen = 0;
1667         if (time)
1668                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1669         date = timelen ? buf : NULL;
1671         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1674 static bool
1675 draw_view_line(struct view *view, unsigned int lineno)
1677         struct line *line;
1678         bool selected = (view->offset + lineno == view->lineno);
1679         bool draw_ok;
1681         assert(view_is_displayed(view));
1683         if (view->offset + lineno >= view->lines)
1684                 return FALSE;
1686         line = &view->line[view->offset + lineno];
1688         wmove(view->win, lineno, 0);
1689         view->col = 0;
1690         view->curline = line;
1691         view->curtype = LINE_NONE;
1692         line->selected = FALSE;
1694         if (selected) {
1695                 set_view_attr(view, LINE_CURSOR);
1696                 line->selected = TRUE;
1697                 view->ops->select(view, line);
1698         } else if (line->selected) {
1699                 wclrtoeol(view->win);
1700         }
1702         scrollok(view->win, FALSE);
1703         draw_ok = view->ops->draw(view, line, lineno);
1704         scrollok(view->win, TRUE);
1706         return draw_ok;
1709 static void
1710 redraw_view_dirty(struct view *view)
1712         bool dirty = FALSE;
1713         int lineno;
1715         for (lineno = 0; lineno < view->height; lineno++) {
1716                 struct line *line = &view->line[view->offset + lineno];
1718                 if (!line->dirty)
1719                         continue;
1720                 line->dirty = 0;
1721                 dirty = TRUE;
1722                 if (!draw_view_line(view, lineno))
1723                         break;
1724         }
1726         if (!dirty)
1727                 return;
1728         redrawwin(view->win);
1729         if (input_mode)
1730                 wnoutrefresh(view->win);
1731         else
1732                 wrefresh(view->win);
1735 static void
1736 redraw_view_from(struct view *view, int lineno)
1738         assert(0 <= lineno && lineno < view->height);
1740         for (; lineno < view->height; lineno++) {
1741                 if (!draw_view_line(view, lineno))
1742                         break;
1743         }
1745         redrawwin(view->win);
1746         if (input_mode)
1747                 wnoutrefresh(view->win);
1748         else
1749                 wrefresh(view->win);
1752 static void
1753 redraw_view(struct view *view)
1755         wclear(view->win);
1756         redraw_view_from(view, 0);
1760 static void
1761 update_view_title(struct view *view)
1763         char buf[SIZEOF_STR];
1764         char state[SIZEOF_STR];
1765         size_t bufpos = 0, statelen = 0;
1767         assert(view_is_displayed(view));
1769         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1770                 unsigned int view_lines = view->offset + view->height;
1771                 unsigned int lines = view->lines
1772                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1773                                    : 0;
1775                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1776                                    view->ops->type,
1777                                    view->lineno + 1,
1778                                    view->lines,
1779                                    lines);
1781                 if (view->pipe) {
1782                         time_t secs = time(NULL) - view->start_time;
1784                         /* Three git seconds are a long time ... */
1785                         if (secs > 2)
1786                                 string_format_from(state, &statelen, " %lds", secs);
1787                 }
1788         }
1790         string_format_from(buf, &bufpos, "[%s]", view->name);
1791         if (*view->ref && bufpos < view->width) {
1792                 size_t refsize = strlen(view->ref);
1793                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1795                 if (minsize < view->width)
1796                         refsize = view->width - minsize + 7;
1797                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1798         }
1800         if (statelen && bufpos < view->width) {
1801                 string_format_from(buf, &bufpos, " %s", state);
1802         }
1804         if (view == display[current_view])
1805                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1806         else
1807                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1809         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1810         wclrtoeol(view->title);
1811         wmove(view->title, 0, view->width - 1);
1813         if (input_mode)
1814                 wnoutrefresh(view->title);
1815         else
1816                 wrefresh(view->title);
1819 static void
1820 resize_display(void)
1822         int offset, i;
1823         struct view *base = display[0];
1824         struct view *view = display[1] ? display[1] : display[0];
1826         /* Setup window dimensions */
1828         getmaxyx(stdscr, base->height, base->width);
1830         /* Make room for the status window. */
1831         base->height -= 1;
1833         if (view != base) {
1834                 /* Horizontal split. */
1835                 view->width   = base->width;
1836                 view->height  = SCALE_SPLIT_VIEW(base->height);
1837                 base->height -= view->height;
1839                 /* Make room for the title bar. */
1840                 view->height -= 1;
1841         }
1843         /* Make room for the title bar. */
1844         base->height -= 1;
1846         offset = 0;
1848         foreach_displayed_view (view, i) {
1849                 if (!view->win) {
1850                         view->win = newwin(view->height, 0, offset, 0);
1851                         if (!view->win)
1852                                 die("Failed to create %s view", view->name);
1854                         scrollok(view->win, TRUE);
1856                         view->title = newwin(1, 0, offset + view->height, 0);
1857                         if (!view->title)
1858                                 die("Failed to create title window");
1860                 } else {
1861                         wresize(view->win, view->height, view->width);
1862                         mvwin(view->win,   offset, 0);
1863                         mvwin(view->title, offset + view->height, 0);
1864                 }
1866                 offset += view->height + 1;
1867         }
1870 static void
1871 redraw_display(void)
1873         struct view *view;
1874         int i;
1876         foreach_displayed_view (view, i) {
1877                 redraw_view(view);
1878                 update_view_title(view);
1879         }
1882 static void
1883 update_display_cursor(struct view *view)
1885         /* Move the cursor to the right-most column of the cursor line.
1886          *
1887          * XXX: This could turn out to be a bit expensive, but it ensures that
1888          * the cursor does not jump around. */
1889         if (view->lines) {
1890                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1891                 wrefresh(view->win);
1892         }
1895 /*
1896  * Navigation
1897  */
1899 /* Scrolling backend */
1900 static void
1901 do_scroll_view(struct view *view, int lines)
1903         bool redraw_current_line = FALSE;
1905         /* The rendering expects the new offset. */
1906         view->offset += lines;
1908         assert(0 <= view->offset && view->offset < view->lines);
1909         assert(lines);
1911         /* Move current line into the view. */
1912         if (view->lineno < view->offset) {
1913                 view->lineno = view->offset;
1914                 redraw_current_line = TRUE;
1915         } else if (view->lineno >= view->offset + view->height) {
1916                 view->lineno = view->offset + view->height - 1;
1917                 redraw_current_line = TRUE;
1918         }
1920         assert(view->offset <= view->lineno && view->lineno < view->lines);
1922         /* Redraw the whole screen if scrolling is pointless. */
1923         if (view->height < ABS(lines)) {
1924                 redraw_view(view);
1926         } else {
1927                 int line = lines > 0 ? view->height - lines : 0;
1928                 int end = line + ABS(lines);
1930                 wscrl(view->win, lines);
1932                 for (; line < end; line++) {
1933                         if (!draw_view_line(view, line))
1934                                 break;
1935                 }
1937                 if (redraw_current_line)
1938                         draw_view_line(view, view->lineno - view->offset);
1939         }
1941         redrawwin(view->win);
1942         wrefresh(view->win);
1943         report("");
1946 /* Scroll frontend */
1947 static void
1948 scroll_view(struct view *view, enum request request)
1950         int lines = 1;
1952         assert(view_is_displayed(view));
1954         switch (request) {
1955         case REQ_SCROLL_PAGE_DOWN:
1956                 lines = view->height;
1957         case REQ_SCROLL_LINE_DOWN:
1958                 if (view->offset + lines > view->lines)
1959                         lines = view->lines - view->offset;
1961                 if (lines == 0 || view->offset + view->height >= view->lines) {
1962                         report("Cannot scroll beyond the last line");
1963                         return;
1964                 }
1965                 break;
1967         case REQ_SCROLL_PAGE_UP:
1968                 lines = view->height;
1969         case REQ_SCROLL_LINE_UP:
1970                 if (lines > view->offset)
1971                         lines = view->offset;
1973                 if (lines == 0) {
1974                         report("Cannot scroll beyond the first line");
1975                         return;
1976                 }
1978                 lines = -lines;
1979                 break;
1981         default:
1982                 die("request %d not handled in switch", request);
1983         }
1985         do_scroll_view(view, lines);
1988 /* Cursor moving */
1989 static void
1990 move_view(struct view *view, enum request request)
1992         int scroll_steps = 0;
1993         int steps;
1995         switch (request) {
1996         case REQ_MOVE_FIRST_LINE:
1997                 steps = -view->lineno;
1998                 break;
2000         case REQ_MOVE_LAST_LINE:
2001                 steps = view->lines - view->lineno - 1;
2002                 break;
2004         case REQ_MOVE_PAGE_UP:
2005                 steps = view->height > view->lineno
2006                       ? -view->lineno : -view->height;
2007                 break;
2009         case REQ_MOVE_PAGE_DOWN:
2010                 steps = view->lineno + view->height >= view->lines
2011                       ? view->lines - view->lineno - 1 : view->height;
2012                 break;
2014         case REQ_MOVE_UP:
2015                 steps = -1;
2016                 break;
2018         case REQ_MOVE_DOWN:
2019                 steps = 1;
2020                 break;
2022         default:
2023                 die("request %d not handled in switch", request);
2024         }
2026         if (steps <= 0 && view->lineno == 0) {
2027                 report("Cannot move beyond the first line");
2028                 return;
2030         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2031                 report("Cannot move beyond the last line");
2032                 return;
2033         }
2035         /* Move the current line */
2036         view->lineno += steps;
2037         assert(0 <= view->lineno && view->lineno < view->lines);
2039         /* Check whether the view needs to be scrolled */
2040         if (view->lineno < view->offset ||
2041             view->lineno >= view->offset + view->height) {
2042                 scroll_steps = steps;
2043                 if (steps < 0 && -steps > view->offset) {
2044                         scroll_steps = -view->offset;
2046                 } else if (steps > 0) {
2047                         if (view->lineno == view->lines - 1 &&
2048                             view->lines > view->height) {
2049                                 scroll_steps = view->lines - view->offset - 1;
2050                                 if (scroll_steps >= view->height)
2051                                         scroll_steps -= view->height - 1;
2052                         }
2053                 }
2054         }
2056         if (!view_is_displayed(view)) {
2057                 view->offset += scroll_steps;
2058                 assert(0 <= view->offset && view->offset < view->lines);
2059                 view->ops->select(view, &view->line[view->lineno]);
2060                 return;
2061         }
2063         /* Repaint the old "current" line if we be scrolling */
2064         if (ABS(steps) < view->height)
2065                 draw_view_line(view, view->lineno - steps - view->offset);
2067         if (scroll_steps) {
2068                 do_scroll_view(view, scroll_steps);
2069                 return;
2070         }
2072         /* Draw the current line */
2073         draw_view_line(view, view->lineno - view->offset);
2075         redrawwin(view->win);
2076         wrefresh(view->win);
2077         report("");
2081 /*
2082  * Searching
2083  */
2085 static void search_view(struct view *view, enum request request);
2087 static bool
2088 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2090         assert(view_is_displayed(view));
2092         if (!view->ops->grep(view, line))
2093                 return FALSE;
2095         if (lineno - view->offset >= view->height) {
2096                 view->offset = lineno;
2097                 view->lineno = lineno;
2098                 redraw_view(view);
2100         } else {
2101                 unsigned long old_lineno = view->lineno - view->offset;
2103                 view->lineno = lineno;
2104                 draw_view_line(view, old_lineno);
2106                 draw_view_line(view, view->lineno - view->offset);
2107                 redrawwin(view->win);
2108                 wrefresh(view->win);
2109         }
2111         report("Line %ld matches '%s'", lineno + 1, view->grep);
2112         return TRUE;
2115 static void
2116 find_next(struct view *view, enum request request)
2118         unsigned long lineno = view->lineno;
2119         int direction;
2121         if (!*view->grep) {
2122                 if (!*opt_search)
2123                         report("No previous search");
2124                 else
2125                         search_view(view, request);
2126                 return;
2127         }
2129         switch (request) {
2130         case REQ_SEARCH:
2131         case REQ_FIND_NEXT:
2132                 direction = 1;
2133                 break;
2135         case REQ_SEARCH_BACK:
2136         case REQ_FIND_PREV:
2137                 direction = -1;
2138                 break;
2140         default:
2141                 return;
2142         }
2144         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2145                 lineno += direction;
2147         /* Note, lineno is unsigned long so will wrap around in which case it
2148          * will become bigger than view->lines. */
2149         for (; lineno < view->lines; lineno += direction) {
2150                 struct line *line = &view->line[lineno];
2152                 if (find_next_line(view, lineno, line))
2153                         return;
2154         }
2156         report("No match found for '%s'", view->grep);
2159 static void
2160 search_view(struct view *view, enum request request)
2162         int regex_err;
2164         if (view->regex) {
2165                 regfree(view->regex);
2166                 *view->grep = 0;
2167         } else {
2168                 view->regex = calloc(1, sizeof(*view->regex));
2169                 if (!view->regex)
2170                         return;
2171         }
2173         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2174         if (regex_err != 0) {
2175                 char buf[SIZEOF_STR] = "unknown error";
2177                 regerror(regex_err, view->regex, buf, sizeof(buf));
2178                 report("Search failed: %s", buf);
2179                 return;
2180         }
2182         string_copy(view->grep, opt_search);
2184         find_next(view, request);
2187 /*
2188  * Incremental updating
2189  */
2191 static void
2192 reset_view(struct view *view)
2194         int i;
2196         for (i = 0; i < view->lines; i++)
2197                 free(view->line[i].data);
2198         free(view->line);
2200         view->line = NULL;
2201         view->offset = 0;
2202         view->lines  = 0;
2203         view->lineno = 0;
2204         view->line_size = 0;
2205         view->line_alloc = 0;
2206         view->vid[0] = 0;
2209 static void
2210 end_update(struct view *view, bool force)
2212         if (!view->pipe)
2213                 return;
2214         while (!view->ops->read(view, NULL))
2215                 if (!force)
2216                         return;
2217         set_nonblocking_input(FALSE);
2218         if (view->pipe == stdin)
2219                 fclose(view->pipe);
2220         else
2221                 pclose(view->pipe);
2222         view->pipe = NULL;
2225 static bool
2226 begin_update(struct view *view, bool refresh)
2228         if (opt_cmd[0]) {
2229                 string_copy(view->cmd, opt_cmd);
2230                 opt_cmd[0] = 0;
2231                 /* When running random commands, initially show the
2232                  * command in the title. However, it maybe later be
2233                  * overwritten if a commit line is selected. */
2234                 if (view == VIEW(REQ_VIEW_PAGER))
2235                         string_copy(view->ref, view->cmd);
2236                 else
2237                         view->ref[0] = 0;
2239         } else if (view == VIEW(REQ_VIEW_TREE)) {
2240                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2241                 char path[SIZEOF_STR];
2243                 if (strcmp(view->vid, view->id))
2244                         opt_path[0] = path[0] = 0;
2245                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2246                         return FALSE;
2248                 if (!string_format(view->cmd, format, view->id, path))
2249                         return FALSE;
2251         } else if (!refresh) {
2252                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2253                 const char *id = view->id;
2255                 if (!string_format(view->cmd, format, id, id, id, id, id))
2256                         return FALSE;
2258                 /* Put the current ref_* value to the view title ref
2259                  * member. This is needed by the blob view. Most other
2260                  * views sets it automatically after loading because the
2261                  * first line is a commit line. */
2262                 string_copy_rev(view->ref, view->id);
2263         }
2265         /* Special case for the pager view. */
2266         if (opt_pipe) {
2267                 view->pipe = opt_pipe;
2268                 opt_pipe = NULL;
2269         } else {
2270                 view->pipe = popen(view->cmd, "r");
2271         }
2273         if (!view->pipe)
2274                 return FALSE;
2276         set_nonblocking_input(TRUE);
2277         reset_view(view);
2278         string_copy_rev(view->vid, view->id);
2280         view->start_time = time(NULL);
2282         return TRUE;
2285 #define ITEM_CHUNK_SIZE 256
2286 static void *
2287 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2289         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2290         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2292         if (mem == NULL || num_chunks != num_chunks_new) {
2293                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2294                 mem = realloc(mem, *size * item_size);
2295         }
2297         return mem;
2300 static struct line *
2301 realloc_lines(struct view *view, size_t line_size)
2303         size_t alloc = view->line_alloc;
2304         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2305                                          sizeof(*view->line));
2307         if (!tmp)
2308                 return NULL;
2310         view->line = tmp;
2311         view->line_alloc = alloc;
2312         view->line_size = line_size;
2313         return view->line;
2316 static bool
2317 update_view(struct view *view)
2319         char in_buffer[BUFSIZ];
2320         char out_buffer[BUFSIZ * 2];
2321         char *line;
2322         /* The number of lines to read. If too low it will cause too much
2323          * redrawing (and possible flickering), if too high responsiveness
2324          * will suffer. */
2325         unsigned long lines = view->height;
2326         int redraw_from = -1;
2328         if (!view->pipe)
2329                 return TRUE;
2331         /* Only redraw if lines are visible. */
2332         if (view->offset + view->height >= view->lines)
2333                 redraw_from = view->lines - view->offset;
2335         /* FIXME: This is probably not perfect for backgrounded views. */
2336         if (!realloc_lines(view, view->lines + lines))
2337                 goto alloc_error;
2339         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2340                 size_t linelen = strlen(line);
2342                 if (linelen)
2343                         line[linelen - 1] = 0;
2345                 if (opt_iconv != ICONV_NONE) {
2346                         ICONV_CONST char *inbuf = line;
2347                         size_t inlen = linelen;
2349                         char *outbuf = out_buffer;
2350                         size_t outlen = sizeof(out_buffer);
2352                         size_t ret;
2354                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2355                         if (ret != (size_t) -1) {
2356                                 line = out_buffer;
2357                                 linelen = strlen(out_buffer);
2358                         }
2359                 }
2361                 if (!view->ops->read(view, line))
2362                         goto alloc_error;
2364                 if (lines-- == 1)
2365                         break;
2366         }
2368         {
2369                 int digits;
2371                 lines = view->lines;
2372                 for (digits = 0; lines; digits++)
2373                         lines /= 10;
2375                 /* Keep the displayed view in sync with line number scaling. */
2376                 if (digits != view->digits) {
2377                         view->digits = digits;
2378                         redraw_from = 0;
2379                 }
2380         }
2382         if (ferror(view->pipe) && errno != 0) {
2383                 report("Failed to read: %s", strerror(errno));
2384                 end_update(view, TRUE);
2386         } else if (feof(view->pipe)) {
2387                 report("");
2388                 end_update(view, FALSE);
2389         }
2391         if (view == VIEW(REQ_VIEW_TREE)) {
2392                 /* Clear the view and redraw everything since the tree sorting
2393                  * might have rearranged things. */
2394                 redraw_view(view);
2396         } else if (redraw_from >= 0) {
2397                 /* If this is an incremental update, redraw the previous line
2398                  * since for commits some members could have changed when
2399                  * loading the main view. */
2400                 if (redraw_from > 0)
2401                         redraw_from--;
2403                 /* Since revision graph visualization requires knowledge
2404                  * about the parent commit, it causes a further one-off
2405                  * needed to be redrawn for incremental updates. */
2406                 if (redraw_from > 0 && opt_rev_graph)
2407                         redraw_from--;
2409                 /* Incrementally draw avoids flickering. */
2410                 redraw_view_from(view, redraw_from);
2411         }
2413         if (view == VIEW(REQ_VIEW_BLAME))
2414                 redraw_view_dirty(view);
2416         /* Update the title _after_ the redraw so that if the redraw picks up a
2417          * commit reference in view->ref it'll be available here. */
2418         update_view_title(view);
2419         return TRUE;
2421 alloc_error:
2422         report("Allocation failure");
2423         end_update(view, TRUE);
2424         return FALSE;
2427 static struct line *
2428 add_line_data(struct view *view, void *data, enum line_type type)
2430         struct line *line = &view->line[view->lines++];
2432         memset(line, 0, sizeof(*line));
2433         line->type = type;
2434         line->data = data;
2436         return line;
2439 static struct line *
2440 add_line_text(struct view *view, const char *text, enum line_type type)
2442         char *data = text ? strdup(text) : NULL;
2444         return data ? add_line_data(view, data, type) : NULL;
2448 /*
2449  * View opening
2450  */
2452 enum open_flags {
2453         OPEN_DEFAULT = 0,       /* Use default view switching. */
2454         OPEN_SPLIT = 1,         /* Split current view. */
2455         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2456         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2457         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2458         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2459 };
2461 static void
2462 open_view(struct view *prev, enum request request, enum open_flags flags)
2464         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2465         bool split = !!(flags & OPEN_SPLIT);
2466         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2467         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2468         struct view *view = VIEW(request);
2469         int nviews = displayed_views();
2470         struct view *base_view = display[0];
2472         if (view == prev && nviews == 1 && !reload) {
2473                 report("Already in %s view", view->name);
2474                 return;
2475         }
2477         if (view->git_dir && !opt_git_dir[0]) {
2478                 report("The %s view is disabled in pager view", view->name);
2479                 return;
2480         }
2482         if (split) {
2483                 display[1] = view;
2484                 if (!backgrounded)
2485                         current_view = 1;
2486         } else if (!nomaximize) {
2487                 /* Maximize the current view. */
2488                 memset(display, 0, sizeof(display));
2489                 current_view = 0;
2490                 display[current_view] = view;
2491         }
2493         /* Resize the view when switching between split- and full-screen,
2494          * or when switching between two different full-screen views. */
2495         if (nviews != displayed_views() ||
2496             (nviews == 1 && base_view != display[0]))
2497                 resize_display();
2499         if (view->pipe)
2500                 end_update(view, TRUE);
2502         if (view->ops->open) {
2503                 if (!view->ops->open(view)) {
2504                         report("Failed to load %s view", view->name);
2505                         return;
2506                 }
2508         } else if ((reload || strcmp(view->vid, view->id)) &&
2509                    !begin_update(view, flags & OPEN_REFRESH)) {
2510                 report("Failed to load %s view", view->name);
2511                 return;
2512         }
2514         if (split && prev->lineno - prev->offset >= prev->height) {
2515                 /* Take the title line into account. */
2516                 int lines = prev->lineno - prev->offset - prev->height + 1;
2518                 /* Scroll the view that was split if the current line is
2519                  * outside the new limited view. */
2520                 do_scroll_view(prev, lines);
2521         }
2523         if (prev && view != prev) {
2524                 if (split && !backgrounded) {
2525                         /* "Blur" the previous view. */
2526                         update_view_title(prev);
2527                 }
2529                 view->parent = prev;
2530         }
2532         if (view->pipe && view->lines == 0) {
2533                 /* Clear the old view and let the incremental updating refill
2534                  * the screen. */
2535                 werase(view->win);
2536                 report("");
2537         } else if (view_is_displayed(view)) {
2538                 redraw_view(view);
2539                 report("");
2540         }
2542         /* If the view is backgrounded the above calls to report()
2543          * won't redraw the view title. */
2544         if (backgrounded)
2545                 update_view_title(view);
2548 static bool
2549 run_confirm(const char *cmd, const char *prompt)
2551         bool confirmation = prompt_yesno(prompt);
2553         if (confirmation)
2554                 system(cmd);
2556         return confirmation;
2559 static void
2560 open_external_viewer(const char *cmd)
2562         def_prog_mode();           /* save current tty modes */
2563         endwin();                  /* restore original tty modes */
2564         system(cmd);
2565         fprintf(stderr, "Press Enter to continue");
2566         getc(opt_tty);
2567         reset_prog_mode();
2568         redraw_display();
2571 static void
2572 open_mergetool(const char *file)
2574         char cmd[SIZEOF_STR];
2575         char file_sq[SIZEOF_STR];
2577         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2578             string_format(cmd, "git mergetool %s", file_sq)) {
2579                 open_external_viewer(cmd);
2580         }
2583 static void
2584 open_editor(bool from_root, const char *file)
2586         char cmd[SIZEOF_STR];
2587         char file_sq[SIZEOF_STR];
2588         const char *editor;
2589         char *prefix = from_root ? opt_cdup : "";
2591         editor = getenv("GIT_EDITOR");
2592         if (!editor && *opt_editor)
2593                 editor = opt_editor;
2594         if (!editor)
2595                 editor = getenv("VISUAL");
2596         if (!editor)
2597                 editor = getenv("EDITOR");
2598         if (!editor)
2599                 editor = "vi";
2601         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2602             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2603                 open_external_viewer(cmd);
2604         }
2607 static void
2608 open_run_request(enum request request)
2610         struct run_request *req = get_run_request(request);
2611         char buf[SIZEOF_STR * 2];
2612         size_t bufpos;
2613         char *cmd;
2615         if (!req) {
2616                 report("Unknown run request");
2617                 return;
2618         }
2620         bufpos = 0;
2621         cmd = req->cmd;
2623         while (cmd) {
2624                 char *next = strstr(cmd, "%(");
2625                 int len = next - cmd;
2626                 char *value;
2628                 if (!next) {
2629                         len = strlen(cmd);
2630                         value = "";
2632                 } else if (!strncmp(next, "%(head)", 7)) {
2633                         value = ref_head;
2635                 } else if (!strncmp(next, "%(commit)", 9)) {
2636                         value = ref_commit;
2638                 } else if (!strncmp(next, "%(blob)", 7)) {
2639                         value = ref_blob;
2641                 } else {
2642                         report("Unknown replacement in run request: `%s`", req->cmd);
2643                         return;
2644                 }
2646                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2647                         return;
2649                 if (next)
2650                         next = strchr(next, ')') + 1;
2651                 cmd = next;
2652         }
2654         open_external_viewer(buf);
2657 /*
2658  * User request switch noodle
2659  */
2661 static int
2662 view_driver(struct view *view, enum request request)
2664         int i;
2666         if (request == REQ_NONE) {
2667                 doupdate();
2668                 return TRUE;
2669         }
2671         if (request > REQ_NONE) {
2672                 open_run_request(request);
2673                 /* FIXME: When all views can refresh always do this. */
2674                 if (view == VIEW(REQ_VIEW_STATUS) ||
2675                     view == VIEW(REQ_VIEW_MAIN) ||
2676                     view == VIEW(REQ_VIEW_LOG) ||
2677                     view == VIEW(REQ_VIEW_STAGE))
2678                         request = REQ_REFRESH;
2679                 else
2680                         return TRUE;
2681         }
2683         if (view && view->lines) {
2684                 request = view->ops->request(view, request, &view->line[view->lineno]);
2685                 if (request == REQ_NONE)
2686                         return TRUE;
2687         }
2689         switch (request) {
2690         case REQ_MOVE_UP:
2691         case REQ_MOVE_DOWN:
2692         case REQ_MOVE_PAGE_UP:
2693         case REQ_MOVE_PAGE_DOWN:
2694         case REQ_MOVE_FIRST_LINE:
2695         case REQ_MOVE_LAST_LINE:
2696                 move_view(view, request);
2697                 break;
2699         case REQ_SCROLL_LINE_DOWN:
2700         case REQ_SCROLL_LINE_UP:
2701         case REQ_SCROLL_PAGE_DOWN:
2702         case REQ_SCROLL_PAGE_UP:
2703                 scroll_view(view, request);
2704                 break;
2706         case REQ_VIEW_BLAME:
2707                 if (!opt_file[0]) {
2708                         report("No file chosen, press %s to open tree view",
2709                                get_key(REQ_VIEW_TREE));
2710                         break;
2711                 }
2712                 open_view(view, request, OPEN_DEFAULT);
2713                 break;
2715         case REQ_VIEW_BLOB:
2716                 if (!ref_blob[0]) {
2717                         report("No file chosen, press %s to open tree view",
2718                                get_key(REQ_VIEW_TREE));
2719                         break;
2720                 }
2721                 open_view(view, request, OPEN_DEFAULT);
2722                 break;
2724         case REQ_VIEW_PAGER:
2725                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2726                         report("No pager content, press %s to run command from prompt",
2727                                get_key(REQ_PROMPT));
2728                         break;
2729                 }
2730                 open_view(view, request, OPEN_DEFAULT);
2731                 break;
2733         case REQ_VIEW_STAGE:
2734                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2735                         report("No stage content, press %s to open the status view and choose file",
2736                                get_key(REQ_VIEW_STATUS));
2737                         break;
2738                 }
2739                 open_view(view, request, OPEN_DEFAULT);
2740                 break;
2742         case REQ_VIEW_STATUS:
2743                 if (opt_is_inside_work_tree == FALSE) {
2744                         report("The status view requires a working tree");
2745                         break;
2746                 }
2747                 open_view(view, request, OPEN_DEFAULT);
2748                 break;
2750         case REQ_VIEW_MAIN:
2751         case REQ_VIEW_DIFF:
2752         case REQ_VIEW_LOG:
2753         case REQ_VIEW_TREE:
2754         case REQ_VIEW_HELP:
2755                 open_view(view, request, OPEN_DEFAULT);
2756                 break;
2758         case REQ_NEXT:
2759         case REQ_PREVIOUS:
2760                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2762                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2763                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2764                    (view == VIEW(REQ_VIEW_DIFF) &&
2765                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2766                    (view == VIEW(REQ_VIEW_STAGE) &&
2767                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2768                    (view == VIEW(REQ_VIEW_BLOB) &&
2769                      view->parent == VIEW(REQ_VIEW_TREE))) {
2770                         int line;
2772                         view = view->parent;
2773                         line = view->lineno;
2774                         move_view(view, request);
2775                         if (view_is_displayed(view))
2776                                 update_view_title(view);
2777                         if (line != view->lineno)
2778                                 view->ops->request(view, REQ_ENTER,
2779                                                    &view->line[view->lineno]);
2781                 } else {
2782                         move_view(view, request);
2783                 }
2784                 break;
2786         case REQ_VIEW_NEXT:
2787         {
2788                 int nviews = displayed_views();
2789                 int next_view = (current_view + 1) % nviews;
2791                 if (next_view == current_view) {
2792                         report("Only one view is displayed");
2793                         break;
2794                 }
2796                 current_view = next_view;
2797                 /* Blur out the title of the previous view. */
2798                 update_view_title(view);
2799                 report("");
2800                 break;
2801         }
2802         case REQ_REFRESH:
2803                 report("Refreshing is not yet supported for the %s view", view->name);
2804                 break;
2806         case REQ_MAXIMIZE:
2807                 if (displayed_views() == 2)
2808                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2809                 break;
2811         case REQ_TOGGLE_LINENO:
2812                 opt_line_number = !opt_line_number;
2813                 redraw_display();
2814                 break;
2816         case REQ_TOGGLE_DATE:
2817                 opt_date = !opt_date;
2818                 redraw_display();
2819                 break;
2821         case REQ_TOGGLE_AUTHOR:
2822                 opt_author = !opt_author;
2823                 redraw_display();
2824                 break;
2826         case REQ_TOGGLE_REV_GRAPH:
2827                 opt_rev_graph = !opt_rev_graph;
2828                 redraw_display();
2829                 break;
2831         case REQ_TOGGLE_REFS:
2832                 opt_show_refs = !opt_show_refs;
2833                 redraw_display();
2834                 break;
2836         case REQ_SEARCH:
2837         case REQ_SEARCH_BACK:
2838                 search_view(view, request);
2839                 break;
2841         case REQ_FIND_NEXT:
2842         case REQ_FIND_PREV:
2843                 find_next(view, request);
2844                 break;
2846         case REQ_STOP_LOADING:
2847                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2848                         view = &views[i];
2849                         if (view->pipe)
2850                                 report("Stopped loading the %s view", view->name),
2851                         end_update(view, TRUE);
2852                 }
2853                 break;
2855         case REQ_SHOW_VERSION:
2856                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2857                 return TRUE;
2859         case REQ_SCREEN_RESIZE:
2860                 resize_display();
2861                 /* Fall-through */
2862         case REQ_SCREEN_REDRAW:
2863                 redraw_display();
2864                 break;
2866         case REQ_EDIT:
2867                 report("Nothing to edit");
2868                 break;
2870         case REQ_ENTER:
2871                 report("Nothing to enter");
2872                 break;
2874         case REQ_VIEW_CLOSE:
2875                 /* XXX: Mark closed views by letting view->parent point to the
2876                  * view itself. Parents to closed view should never be
2877                  * followed. */
2878                 if (view->parent &&
2879                     view->parent->parent != view->parent) {
2880                         memset(display, 0, sizeof(display));
2881                         current_view = 0;
2882                         display[current_view] = view->parent;
2883                         view->parent = view;
2884                         resize_display();
2885                         redraw_display();
2886                         report("");
2887                         break;
2888                 }
2889                 /* Fall-through */
2890         case REQ_QUIT:
2891                 return FALSE;
2893         default:
2894                 report("Unknown key, press 'h' for help");
2895                 return TRUE;
2896         }
2898         return TRUE;
2902 /*
2903  * Pager backend
2904  */
2906 static bool
2907 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2909         char *text = line->data;
2911         if (opt_line_number && draw_lineno(view, lineno))
2912                 return TRUE;
2914         draw_text(view, line->type, text, TRUE);
2915         return TRUE;
2918 static bool
2919 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2921         char refbuf[SIZEOF_STR];
2922         char *ref = NULL;
2923         FILE *pipe;
2925         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2926                 return TRUE;
2928         pipe = popen(refbuf, "r");
2929         if (!pipe)
2930                 return TRUE;
2932         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2933                 ref = chomp_string(ref);
2934         pclose(pipe);
2936         if (!ref || !*ref)
2937                 return TRUE;
2939         /* This is the only fatal call, since it can "corrupt" the buffer. */
2940         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2941                 return FALSE;
2943         return TRUE;
2946 static void
2947 add_pager_refs(struct view *view, struct line *line)
2949         char buf[SIZEOF_STR];
2950         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2951         struct ref **refs;
2952         size_t bufpos = 0, refpos = 0;
2953         const char *sep = "Refs: ";
2954         bool is_tag = FALSE;
2956         assert(line->type == LINE_COMMIT);
2958         refs = get_refs(commit_id);
2959         if (!refs) {
2960                 if (view == VIEW(REQ_VIEW_DIFF))
2961                         goto try_add_describe_ref;
2962                 return;
2963         }
2965         do {
2966                 struct ref *ref = refs[refpos];
2967                 const char *fmt = ref->tag    ? "%s[%s]" :
2968                                   ref->remote ? "%s<%s>" : "%s%s";
2970                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2971                         return;
2972                 sep = ", ";
2973                 if (ref->tag)
2974                         is_tag = TRUE;
2975         } while (refs[refpos++]->next);
2977         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2978 try_add_describe_ref:
2979                 /* Add <tag>-g<commit_id> "fake" reference. */
2980                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2981                         return;
2982         }
2984         if (bufpos == 0)
2985                 return;
2987         if (!realloc_lines(view, view->line_size + 1))
2988                 return;
2990         add_line_text(view, buf, LINE_PP_REFS);
2993 static bool
2994 pager_read(struct view *view, char *data)
2996         struct line *line;
2998         if (!data)
2999                 return TRUE;
3001         line = add_line_text(view, data, get_line_type(data));
3002         if (!line)
3003                 return FALSE;
3005         if (line->type == LINE_COMMIT &&
3006             (view == VIEW(REQ_VIEW_DIFF) ||
3007              view == VIEW(REQ_VIEW_LOG)))
3008                 add_pager_refs(view, line);
3010         return TRUE;
3013 static enum request
3014 pager_request(struct view *view, enum request request, struct line *line)
3016         int split = 0;
3018         if (request != REQ_ENTER)
3019                 return request;
3021         if (line->type == LINE_COMMIT &&
3022            (view == VIEW(REQ_VIEW_LOG) ||
3023             view == VIEW(REQ_VIEW_PAGER))) {
3024                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3025                 split = 1;
3026         }
3028         /* Always scroll the view even if it was split. That way
3029          * you can use Enter to scroll through the log view and
3030          * split open each commit diff. */
3031         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3033         /* FIXME: A minor workaround. Scrolling the view will call report("")
3034          * but if we are scrolling a non-current view this won't properly
3035          * update the view title. */
3036         if (split)
3037                 update_view_title(view);
3039         return REQ_NONE;
3042 static bool
3043 pager_grep(struct view *view, struct line *line)
3045         regmatch_t pmatch;
3046         char *text = line->data;
3048         if (!*text)
3049                 return FALSE;
3051         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3052                 return FALSE;
3054         return TRUE;
3057 static void
3058 pager_select(struct view *view, struct line *line)
3060         if (line->type == LINE_COMMIT) {
3061                 char *text = (char *)line->data + STRING_SIZE("commit ");
3063                 if (view != VIEW(REQ_VIEW_PAGER))
3064                         string_copy_rev(view->ref, text);
3065                 string_copy_rev(ref_commit, text);
3066         }
3069 static struct view_ops pager_ops = {
3070         "line",
3071         NULL,
3072         pager_read,
3073         pager_draw,
3074         pager_request,
3075         pager_grep,
3076         pager_select,
3077 };
3079 static enum request
3080 log_request(struct view *view, enum request request, struct line *line)
3082         switch (request) {
3083         case REQ_REFRESH:
3084                 load_refs();
3085                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3086                 return REQ_NONE;
3087         default:
3088                 return pager_request(view, request, line);
3089         }
3092 static struct view_ops log_ops = {
3093         "line",
3094         NULL,
3095         pager_read,
3096         pager_draw,
3097         log_request,
3098         pager_grep,
3099         pager_select,
3100 };
3103 /*
3104  * Help backend
3105  */
3107 static bool
3108 help_open(struct view *view)
3110         char buf[BUFSIZ];
3111         int lines = ARRAY_SIZE(req_info) + 2;
3112         int i;
3114         if (view->lines > 0)
3115                 return TRUE;
3117         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3118                 if (!req_info[i].request)
3119                         lines++;
3121         lines += run_requests + 1;
3123         view->line = calloc(lines, sizeof(*view->line));
3124         if (!view->line)
3125                 return FALSE;
3127         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3129         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3130                 const char *key;
3132                 if (req_info[i].request == REQ_NONE)
3133                         continue;
3135                 if (!req_info[i].request) {
3136                         add_line_text(view, "", LINE_DEFAULT);
3137                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3138                         continue;
3139                 }
3141                 key = get_key(req_info[i].request);
3142                 if (!*key)
3143                         key = "(no key defined)";
3145                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3146                         continue;
3148                 add_line_text(view, buf, LINE_DEFAULT);
3149         }
3151         if (run_requests) {
3152                 add_line_text(view, "", LINE_DEFAULT);
3153                 add_line_text(view, "External commands:", LINE_DEFAULT);
3154         }
3156         for (i = 0; i < run_requests; i++) {
3157                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3158                 const char *key;
3160                 if (!req)
3161                         continue;
3163                 key = get_key_name(req->key);
3164                 if (!*key)
3165                         key = "(no key defined)";
3167                 if (!string_format(buf, "    %-10s %-14s `%s`",
3168                                    keymap_table[req->keymap].name,
3169                                    key, req->cmd))
3170                         continue;
3172                 add_line_text(view, buf, LINE_DEFAULT);
3173         }
3175         return TRUE;
3178 static struct view_ops help_ops = {
3179         "line",
3180         help_open,
3181         NULL,
3182         pager_draw,
3183         pager_request,
3184         pager_grep,
3185         pager_select,
3186 };
3189 /*
3190  * Tree backend
3191  */
3193 struct tree_stack_entry {
3194         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3195         unsigned long lineno;           /* Line number to restore */
3196         char *name;                     /* Position of name in opt_path */
3197 };
3199 /* The top of the path stack. */
3200 static struct tree_stack_entry *tree_stack = NULL;
3201 unsigned long tree_lineno = 0;
3203 static void
3204 pop_tree_stack_entry(void)
3206         struct tree_stack_entry *entry = tree_stack;
3208         tree_lineno = entry->lineno;
3209         entry->name[0] = 0;
3210         tree_stack = entry->prev;
3211         free(entry);
3214 static void
3215 push_tree_stack_entry(const char *name, unsigned long lineno)
3217         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3218         size_t pathlen = strlen(opt_path);
3220         if (!entry)
3221                 return;
3223         entry->prev = tree_stack;
3224         entry->name = opt_path + pathlen;
3225         tree_stack = entry;
3227         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3228                 pop_tree_stack_entry();
3229                 return;
3230         }
3232         /* Move the current line to the first tree entry. */
3233         tree_lineno = 1;
3234         entry->lineno = lineno;
3237 /* Parse output from git-ls-tree(1):
3238  *
3239  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3240  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3241  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3242  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3243  */
3245 #define SIZEOF_TREE_ATTR \
3246         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3248 #define TREE_UP_FORMAT "040000 tree %s\t.."
3250 static int
3251 tree_compare_entry(enum line_type type1, const char *name1,
3252                    enum line_type type2, const char *name2)
3254         if (type1 != type2) {
3255                 if (type1 == LINE_TREE_DIR)
3256                         return -1;
3257                 return 1;
3258         }
3260         return strcmp(name1, name2);
3263 static const char *
3264 tree_path(struct line *line)
3266         const char *path = line->data;
3268         return path + SIZEOF_TREE_ATTR;
3271 static bool
3272 tree_read(struct view *view, char *text)
3274         size_t textlen = text ? strlen(text) : 0;
3275         char buf[SIZEOF_STR];
3276         unsigned long pos;
3277         enum line_type type;
3278         bool first_read = view->lines == 0;
3280         if (!text)
3281                 return TRUE;
3282         if (textlen <= SIZEOF_TREE_ATTR)
3283                 return FALSE;
3285         type = text[STRING_SIZE("100644 ")] == 't'
3286              ? LINE_TREE_DIR : LINE_TREE_FILE;
3288         if (first_read) {
3289                 /* Add path info line */
3290                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3291                     !realloc_lines(view, view->line_size + 1) ||
3292                     !add_line_text(view, buf, LINE_DEFAULT))
3293                         return FALSE;
3295                 /* Insert "link" to parent directory. */
3296                 if (*opt_path) {
3297                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3298                             !realloc_lines(view, view->line_size + 1) ||
3299                             !add_line_text(view, buf, LINE_TREE_DIR))
3300                                 return FALSE;
3301                 }
3302         }
3304         /* Strip the path part ... */
3305         if (*opt_path) {
3306                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3307                 size_t striplen = strlen(opt_path);
3308                 char *path = text + SIZEOF_TREE_ATTR;
3310                 if (pathlen > striplen)
3311                         memmove(path, path + striplen,
3312                                 pathlen - striplen + 1);
3313         }
3315         /* Skip "Directory ..." and ".." line. */
3316         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3317                 struct line *line = &view->line[pos];
3318                 const char *path1 = tree_path(line);
3319                 char *path2 = text + SIZEOF_TREE_ATTR;
3320                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3322                 if (cmp <= 0)
3323                         continue;
3325                 text = strdup(text);
3326                 if (!text)
3327                         return FALSE;
3329                 if (view->lines > pos)
3330                         memmove(&view->line[pos + 1], &view->line[pos],
3331                                 (view->lines - pos) * sizeof(*line));
3333                 line = &view->line[pos];
3334                 line->data = text;
3335                 line->type = type;
3336                 view->lines++;
3337                 return TRUE;
3338         }
3340         if (!add_line_text(view, text, type))
3341                 return FALSE;
3343         if (tree_lineno > view->lineno) {
3344                 view->lineno = tree_lineno;
3345                 tree_lineno = 0;
3346         }
3348         return TRUE;
3351 static enum request
3352 tree_request(struct view *view, enum request request, struct line *line)
3354         enum open_flags flags;
3356         if (request == REQ_VIEW_BLAME) {
3357                 const char *filename = tree_path(line);
3359                 if (line->type == LINE_TREE_DIR) {
3360                         report("Cannot show blame for directory %s", opt_path);
3361                         return REQ_NONE;
3362                 }
3364                 string_copy(opt_ref, view->vid);
3365                 string_format(opt_file, "%s%s", opt_path, filename);
3366                 return request;
3367         }
3368         if (request == REQ_TREE_PARENT) {
3369                 if (*opt_path) {
3370                         /* fake 'cd  ..' */
3371                         request = REQ_ENTER;
3372                         line = &view->line[1];
3373                 } else {
3374                         /* quit view if at top of tree */
3375                         return REQ_VIEW_CLOSE;
3376                 }
3377         }
3378         if (request != REQ_ENTER)
3379                 return request;
3381         /* Cleanup the stack if the tree view is at a different tree. */
3382         while (!*opt_path && tree_stack)
3383                 pop_tree_stack_entry();
3385         switch (line->type) {
3386         case LINE_TREE_DIR:
3387                 /* Depending on whether it is a subdir or parent (updir?) link
3388                  * mangle the path buffer. */
3389                 if (line == &view->line[1] && *opt_path) {
3390                         pop_tree_stack_entry();
3392                 } else {
3393                         const char *basename = tree_path(line);
3395                         push_tree_stack_entry(basename, view->lineno);
3396                 }
3398                 /* Trees and subtrees share the same ID, so they are not not
3399                  * unique like blobs. */
3400                 flags = OPEN_RELOAD;
3401                 request = REQ_VIEW_TREE;
3402                 break;
3404         case LINE_TREE_FILE:
3405                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3406                 request = REQ_VIEW_BLOB;
3407                 break;
3409         default:
3410                 return TRUE;
3411         }
3413         open_view(view, request, flags);
3414         if (request == REQ_VIEW_TREE) {
3415                 view->lineno = tree_lineno;
3416         }
3418         return REQ_NONE;
3421 static void
3422 tree_select(struct view *view, struct line *line)
3424         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3426         if (line->type == LINE_TREE_FILE) {
3427                 string_copy_rev(ref_blob, text);
3429         } else if (line->type != LINE_TREE_DIR) {
3430                 return;
3431         }
3433         string_copy_rev(view->ref, text);
3436 static struct view_ops tree_ops = {
3437         "file",
3438         NULL,
3439         tree_read,
3440         pager_draw,
3441         tree_request,
3442         pager_grep,
3443         tree_select,
3444 };
3446 static bool
3447 blob_read(struct view *view, char *line)
3449         if (!line)
3450                 return TRUE;
3451         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3454 static struct view_ops blob_ops = {
3455         "line",
3456         NULL,
3457         blob_read,
3458         pager_draw,
3459         pager_request,
3460         pager_grep,
3461         pager_select,
3462 };
3464 /*
3465  * Blame backend
3466  *
3467  * Loading the blame view is a two phase job:
3468  *
3469  *  1. File content is read either using opt_file from the
3470  *     filesystem or using git-cat-file.
3471  *  2. Then blame information is incrementally added by
3472  *     reading output from git-blame.
3473  */
3475 struct blame_commit {
3476         char id[SIZEOF_REV];            /* SHA1 ID. */
3477         char title[128];                /* First line of the commit message. */
3478         char author[75];                /* Author of the commit. */
3479         struct tm time;                 /* Date from the author ident. */
3480         char filename[128];             /* Name of file. */
3481 };
3483 struct blame {
3484         struct blame_commit *commit;
3485         unsigned int header:1;
3486         char text[1];
3487 };
3489 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3490 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3492 static bool
3493 blame_open(struct view *view)
3495         char path[SIZEOF_STR];
3496         char ref[SIZEOF_STR] = "";
3498         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3499                 return FALSE;
3501         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3502                 return FALSE;
3504         if (*opt_ref) {
3505                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3506                         return FALSE;
3507         } else {
3508                 view->pipe = fopen(opt_file, "r");
3509                 if (!view->pipe &&
3510                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3511                         return FALSE;
3512         }
3514         if (!view->pipe)
3515                 view->pipe = popen(view->cmd, "r");
3516         if (!view->pipe)
3517                 return FALSE;
3519         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3520                 return FALSE;
3522         reset_view(view);
3523         string_format(view->ref, "%s ...", opt_file);
3524         string_copy_rev(view->vid, opt_file);
3525         set_nonblocking_input(TRUE);
3526         view->start_time = time(NULL);
3528         return TRUE;
3531 static struct blame_commit *
3532 get_blame_commit(struct view *view, const char *id)
3534         size_t i;
3536         for (i = 0; i < view->lines; i++) {
3537                 struct blame *blame = view->line[i].data;
3539                 if (!blame->commit)
3540                         continue;
3542                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3543                         return blame->commit;
3544         }
3546         {
3547                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3549                 if (commit)
3550                         string_ncopy(commit->id, id, SIZEOF_REV);
3551                 return commit;
3552         }
3555 static bool
3556 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3558         const char *pos = *posref;
3560         *posref = NULL;
3561         pos = strchr(pos + 1, ' ');
3562         if (!pos || !isdigit(pos[1]))
3563                 return FALSE;
3564         *number = atoi(pos + 1);
3565         if (*number < min || *number > max)
3566                 return FALSE;
3568         *posref = pos;
3569         return TRUE;
3572 static struct blame_commit *
3573 parse_blame_commit(struct view *view, const char *text, int *blamed)
3575         struct blame_commit *commit;
3576         struct blame *blame;
3577         const char *pos = text + SIZEOF_REV - 1;
3578         size_t lineno;
3579         size_t group;
3581         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3582                 return NULL;
3584         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3585             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3586                 return NULL;
3588         commit = get_blame_commit(view, text);
3589         if (!commit)
3590                 return NULL;
3592         *blamed += group;
3593         while (group--) {
3594                 struct line *line = &view->line[lineno + group - 1];
3596                 blame = line->data;
3597                 blame->commit = commit;
3598                 blame->header = !group;
3599                 line->dirty = 1;
3600         }
3602         return commit;
3605 static bool
3606 blame_read_file(struct view *view, const char *line)
3608         if (!line) {
3609                 FILE *pipe = NULL;
3611                 if (view->lines > 0)
3612                         pipe = popen(view->cmd, "r");
3613                 else if (!view->parent)
3614                         die("No blame exist for %s", view->vid);
3615                 view->cmd[0] = 0;
3616                 if (!pipe) {
3617                         report("Failed to load blame data");
3618                         return TRUE;
3619                 }
3621                 fclose(view->pipe);
3622                 view->pipe = pipe;
3623                 return FALSE;
3625         } else {
3626                 size_t linelen = strlen(line);
3627                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3629                 blame->commit = NULL;
3630                 strncpy(blame->text, line, linelen);
3631                 blame->text[linelen] = 0;
3632                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3633         }
3636 static bool
3637 match_blame_header(const char *name, char **line)
3639         size_t namelen = strlen(name);
3640         bool matched = !strncmp(name, *line, namelen);
3642         if (matched)
3643                 *line += namelen;
3645         return matched;
3648 static bool
3649 blame_read(struct view *view, char *line)
3651         static struct blame_commit *commit = NULL;
3652         static int blamed = 0;
3653         static time_t author_time;
3655         if (*view->cmd)
3656                 return blame_read_file(view, line);
3658         if (!line) {
3659                 /* Reset all! */
3660                 commit = NULL;
3661                 blamed = 0;
3662                 string_format(view->ref, "%s", view->vid);
3663                 if (view_is_displayed(view)) {
3664                         update_view_title(view);
3665                         redraw_view_from(view, 0);
3666                 }
3667                 return TRUE;
3668         }
3670         if (!commit) {
3671                 commit = parse_blame_commit(view, line, &blamed);
3672                 string_format(view->ref, "%s %2d%%", view->vid,
3673                               blamed * 100 / view->lines);
3675         } else if (match_blame_header("author ", &line)) {
3676                 string_ncopy(commit->author, line, strlen(line));
3678         } else if (match_blame_header("author-time ", &line)) {
3679                 author_time = (time_t) atol(line);
3681         } else if (match_blame_header("author-tz ", &line)) {
3682                 long tz;
3684                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3685                 tz += ('0' - line[2]) * 60 * 60;
3686                 tz += ('0' - line[3]) * 60;
3687                 tz += ('0' - line[4]) * 60;
3689                 if (line[0] == '-')
3690                         tz = -tz;
3692                 author_time -= tz;
3693                 gmtime_r(&author_time, &commit->time);
3695         } else if (match_blame_header("summary ", &line)) {
3696                 string_ncopy(commit->title, line, strlen(line));
3698         } else if (match_blame_header("filename ", &line)) {
3699                 string_ncopy(commit->filename, line, strlen(line));
3700                 commit = NULL;
3701         }
3703         return TRUE;
3706 static bool
3707 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3709         struct blame *blame = line->data;
3710         struct tm *time = NULL;
3711         const char *id = NULL, *author = NULL;
3713         if (blame->commit && *blame->commit->filename) {
3714                 id = blame->commit->id;
3715                 author = blame->commit->author;
3716                 time = &blame->commit->time;
3717         }
3719         if (opt_date && draw_date(view, time))
3720                 return TRUE;
3722         if (opt_author &&
3723             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3724                 return TRUE;
3726         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3727                 return TRUE;
3729         if (draw_lineno(view, lineno))
3730                 return TRUE;
3732         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3733         return TRUE;
3736 static enum request
3737 blame_request(struct view *view, enum request request, struct line *line)
3739         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3740         struct blame *blame = line->data;
3742         switch (request) {
3743         case REQ_ENTER:
3744                 if (!blame->commit) {
3745                         report("No commit loaded yet");
3746                         break;
3747                 }
3749                 if (!strcmp(blame->commit->id, NULL_ID)) {
3750                         char path[SIZEOF_STR];
3752                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3753                                 break;
3754                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3755                 }
3757                 open_view(view, REQ_VIEW_DIFF, flags);
3758                 break;
3760         default:
3761                 return request;
3762         }
3764         return REQ_NONE;
3767 static bool
3768 blame_grep(struct view *view, struct line *line)
3770         struct blame *blame = line->data;
3771         struct blame_commit *commit = blame->commit;
3772         regmatch_t pmatch;
3774 #define MATCH(text, on)                                                 \
3775         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3777         if (commit) {
3778                 char buf[DATE_COLS + 1];
3780                 if (MATCH(commit->title, 1) ||
3781                     MATCH(commit->author, opt_author) ||
3782                     MATCH(commit->id, opt_date))
3783                         return TRUE;
3785                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3786                     MATCH(buf, 1))
3787                         return TRUE;
3788         }
3790         return MATCH(blame->text, 1);
3792 #undef MATCH
3795 static void
3796 blame_select(struct view *view, struct line *line)
3798         struct blame *blame = line->data;
3799         struct blame_commit *commit = blame->commit;
3801         if (!commit)
3802                 return;
3804         if (!strcmp(commit->id, NULL_ID))
3805                 string_ncopy(ref_commit, "HEAD", 4);
3806         else
3807                 string_copy_rev(ref_commit, commit->id);
3810 static struct view_ops blame_ops = {
3811         "line",
3812         blame_open,
3813         blame_read,
3814         blame_draw,
3815         blame_request,
3816         blame_grep,
3817         blame_select,
3818 };
3820 /*
3821  * Status backend
3822  */
3824 struct status {
3825         char status;
3826         struct {
3827                 mode_t mode;
3828                 char rev[SIZEOF_REV];
3829                 char name[SIZEOF_STR];
3830         } old;
3831         struct {
3832                 mode_t mode;
3833                 char rev[SIZEOF_REV];
3834                 char name[SIZEOF_STR];
3835         } new;
3836 };
3838 static char status_onbranch[SIZEOF_STR];
3839 static struct status stage_status;
3840 static enum line_type stage_line_type;
3841 static size_t stage_chunks;
3842 static int *stage_chunk;
3844 /* This should work even for the "On branch" line. */
3845 static inline bool
3846 status_has_none(struct view *view, struct line *line)
3848         return line < view->line + view->lines && !line[1].data;
3851 /* Get fields from the diff line:
3852  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3853  */
3854 static inline bool
3855 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3857         const char *old_mode = buf +  1;
3858         const char *new_mode = buf +  8;
3859         const char *old_rev  = buf + 15;
3860         const char *new_rev  = buf + 56;
3861         const char *status   = buf + 97;
3863         if (bufsize < 99 ||
3864             old_mode[-1] != ':' ||
3865             new_mode[-1] != ' ' ||
3866             old_rev[-1]  != ' ' ||
3867             new_rev[-1]  != ' ' ||
3868             status[-1]   != ' ')
3869                 return FALSE;
3871         file->status = *status;
3873         string_copy_rev(file->old.rev, old_rev);
3874         string_copy_rev(file->new.rev, new_rev);
3876         file->old.mode = strtoul(old_mode, NULL, 8);
3877         file->new.mode = strtoul(new_mode, NULL, 8);
3879         file->old.name[0] = file->new.name[0] = 0;
3881         return TRUE;
3884 static bool
3885 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3887         struct status *file = NULL;
3888         struct status *unmerged = NULL;
3889         char buf[SIZEOF_STR * 4];
3890         size_t bufsize = 0;
3891         FILE *pipe;
3893         pipe = popen(cmd, "r");
3894         if (!pipe)
3895                 return FALSE;
3897         add_line_data(view, NULL, type);
3899         while (!feof(pipe) && !ferror(pipe)) {
3900                 char *sep;
3901                 size_t readsize;
3903                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3904                 if (!readsize)
3905                         break;
3906                 bufsize += readsize;
3908                 /* Process while we have NUL chars. */
3909                 while ((sep = memchr(buf, 0, bufsize))) {
3910                         size_t sepsize = sep - buf + 1;
3912                         if (!file) {
3913                                 if (!realloc_lines(view, view->line_size + 1))
3914                                         goto error_out;
3916                                 file = calloc(1, sizeof(*file));
3917                                 if (!file)
3918                                         goto error_out;
3920                                 add_line_data(view, file, type);
3921                         }
3923                         /* Parse diff info part. */
3924                         if (status) {
3925                                 file->status = status;
3926                                 if (status == 'A')
3927                                         string_copy(file->old.rev, NULL_ID);
3929                         } else if (!file->status) {
3930                                 if (!status_get_diff(file, buf, sepsize))
3931                                         goto error_out;
3933                                 bufsize -= sepsize;
3934                                 memmove(buf, sep + 1, bufsize);
3936                                 sep = memchr(buf, 0, bufsize);
3937                                 if (!sep)
3938                                         break;
3939                                 sepsize = sep - buf + 1;
3941                                 /* Collapse all 'M'odified entries that
3942                                  * follow a associated 'U'nmerged entry.
3943                                  */
3944                                 if (file->status == 'U') {
3945                                         unmerged = file;
3947                                 } else if (unmerged) {
3948                                         int collapse = !strcmp(buf, unmerged->new.name);
3950                                         unmerged = NULL;
3951                                         if (collapse) {
3952                                                 free(file);
3953                                                 view->lines--;
3954                                                 continue;
3955                                         }
3956                                 }
3957                         }
3959                         /* Grab the old name for rename/copy. */
3960                         if (!*file->old.name &&
3961                             (file->status == 'R' || file->status == 'C')) {
3962                                 sepsize = sep - buf + 1;
3963                                 string_ncopy(file->old.name, buf, sepsize);
3964                                 bufsize -= sepsize;
3965                                 memmove(buf, sep + 1, bufsize);
3967                                 sep = memchr(buf, 0, bufsize);
3968                                 if (!sep)
3969                                         break;
3970                                 sepsize = sep - buf + 1;
3971                         }
3973                         /* git-ls-files just delivers a NUL separated
3974                          * list of file names similar to the second half
3975                          * of the git-diff-* output. */
3976                         string_ncopy(file->new.name, buf, sepsize);
3977                         if (!*file->old.name)
3978                                 string_copy(file->old.name, file->new.name);
3979                         bufsize -= sepsize;
3980                         memmove(buf, sep + 1, bufsize);
3981                         file = NULL;
3982                 }
3983         }
3985         if (ferror(pipe)) {
3986 error_out:
3987                 pclose(pipe);
3988                 return FALSE;
3989         }
3991         if (!view->line[view->lines - 1].data)
3992                 add_line_data(view, NULL, LINE_STAT_NONE);
3994         pclose(pipe);
3995         return TRUE;
3998 /* Don't show unmerged entries in the staged section. */
3999 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4000 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4001 #define STATUS_LIST_OTHER_CMD \
4002         "git ls-files -z --others --exclude-standard"
4003 #define STATUS_LIST_NO_HEAD_CMD \
4004         "git ls-files -z --cached --exclude-standard"
4006 #define STATUS_DIFF_INDEX_SHOW_CMD \
4007         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4009 #define STATUS_DIFF_FILES_SHOW_CMD \
4010         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4012 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4013         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4015 /* First parse staged info using git-diff-index(1), then parse unstaged
4016  * info using git-diff-files(1), and finally untracked files using
4017  * git-ls-files(1). */
4018 static bool
4019 status_open(struct view *view)
4021         unsigned long prev_lineno = view->lineno;
4023         reset_view(view);
4025         if (!realloc_lines(view, view->line_size + 7))
4026                 return FALSE;
4028         add_line_data(view, NULL, LINE_STAT_HEAD);
4029         if (opt_no_head)
4030                 string_copy(status_onbranch, "Initial commit");
4031         else if (!*opt_head)
4032                 string_copy(status_onbranch, "Not currently on any branch");
4033         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4034                 return FALSE;
4036         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4038         if (opt_no_head) {
4039                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4040                         return FALSE;
4041         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4042                 return FALSE;
4043         }
4045         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4046             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4047                 return FALSE;
4049         /* If all went well restore the previous line number to stay in
4050          * the context or select a line with something that can be
4051          * updated. */
4052         if (prev_lineno >= view->lines)
4053                 prev_lineno = view->lines - 1;
4054         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4055                 prev_lineno++;
4056         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4057                 prev_lineno--;
4059         /* If the above fails, always skip the "On branch" line. */
4060         if (prev_lineno < view->lines)
4061                 view->lineno = prev_lineno;
4062         else
4063                 view->lineno = 1;
4065         if (view->lineno < view->offset)
4066                 view->offset = view->lineno;
4067         else if (view->offset + view->height <= view->lineno)
4068                 view->offset = view->lineno - view->height + 1;
4070         return TRUE;
4073 static bool
4074 status_draw(struct view *view, struct line *line, unsigned int lineno)
4076         struct status *status = line->data;
4077         enum line_type type;
4078         const char *text;
4080         if (!status) {
4081                 switch (line->type) {
4082                 case LINE_STAT_STAGED:
4083                         type = LINE_STAT_SECTION;
4084                         text = "Changes to be committed:";
4085                         break;
4087                 case LINE_STAT_UNSTAGED:
4088                         type = LINE_STAT_SECTION;
4089                         text = "Changed but not updated:";
4090                         break;
4092                 case LINE_STAT_UNTRACKED:
4093                         type = LINE_STAT_SECTION;
4094                         text = "Untracked files:";
4095                         break;
4097                 case LINE_STAT_NONE:
4098                         type = LINE_DEFAULT;
4099                         text = "    (no files)";
4100                         break;
4102                 case LINE_STAT_HEAD:
4103                         type = LINE_STAT_HEAD;
4104                         text = status_onbranch;
4105                         break;
4107                 default:
4108                         return FALSE;
4109                 }
4110         } else {
4111                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4113                 buf[0] = status->status;
4114                 if (draw_text(view, line->type, buf, TRUE))
4115                         return TRUE;
4116                 type = LINE_DEFAULT;
4117                 text = status->new.name;
4118         }
4120         draw_text(view, type, text, TRUE);
4121         return TRUE;
4124 static enum request
4125 status_enter(struct view *view, struct line *line)
4127         struct status *status = line->data;
4128         char oldpath[SIZEOF_STR] = "";
4129         char newpath[SIZEOF_STR] = "";
4130         const char *info;
4131         size_t cmdsize = 0;
4132         enum open_flags split;
4134         if (line->type == LINE_STAT_NONE ||
4135             (!status && line[1].type == LINE_STAT_NONE)) {
4136                 report("No file to diff");
4137                 return REQ_NONE;
4138         }
4140         if (status) {
4141                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4142                         return REQ_QUIT;
4143                 /* Diffs for unmerged entries are empty when pasing the
4144                  * new path, so leave it empty. */
4145                 if (status->status != 'U' &&
4146                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4147                         return REQ_QUIT;
4148         }
4150         if (opt_cdup[0] &&
4151             line->type != LINE_STAT_UNTRACKED &&
4152             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4153                 return REQ_QUIT;
4155         switch (line->type) {
4156         case LINE_STAT_STAGED:
4157                 if (opt_no_head) {
4158                         if (!string_format_from(opt_cmd, &cmdsize,
4159                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4160                                                 newpath))
4161                                 return REQ_QUIT;
4162                 } else {
4163                         if (!string_format_from(opt_cmd, &cmdsize,
4164                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4165                                                 oldpath, newpath))
4166                                 return REQ_QUIT;
4167                 }
4169                 if (status)
4170                         info = "Staged changes to %s";
4171                 else
4172                         info = "Staged changes";
4173                 break;
4175         case LINE_STAT_UNSTAGED:
4176                 if (!string_format_from(opt_cmd, &cmdsize,
4177                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4178                         return REQ_QUIT;
4179                 if (status)
4180                         info = "Unstaged changes to %s";
4181                 else
4182                         info = "Unstaged changes";
4183                 break;
4185         case LINE_STAT_UNTRACKED:
4186                 if (opt_pipe)
4187                         return REQ_QUIT;
4189                 if (!status) {
4190                         report("No file to show");
4191                         return REQ_NONE;
4192                 }
4194                 opt_pipe = fopen(status->new.name, "r");
4195                 info = "Untracked file %s";
4196                 break;
4198         case LINE_STAT_HEAD:
4199                 return REQ_NONE;
4201         default:
4202                 die("line type %d not handled in switch", line->type);
4203         }
4205         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4206         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4207         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4208                 if (status) {
4209                         stage_status = *status;
4210                 } else {
4211                         memset(&stage_status, 0, sizeof(stage_status));
4212                 }
4214                 stage_line_type = line->type;
4215                 stage_chunks = 0;
4216                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4217         }
4219         return REQ_NONE;
4222 static bool
4223 status_exists(struct status *status, enum line_type type)
4225         struct view *view = VIEW(REQ_VIEW_STATUS);
4226         struct line *line;
4228         for (line = view->line; line < view->line + view->lines; line++) {
4229                 struct status *pos = line->data;
4231                 if (line->type == type && pos &&
4232                     !strcmp(status->new.name, pos->new.name))
4233                         return TRUE;
4234         }
4236         return FALSE;
4240 static FILE *
4241 status_update_prepare(enum line_type type)
4243         char cmd[SIZEOF_STR];
4244         size_t cmdsize = 0;
4246         if (opt_cdup[0] &&
4247             type != LINE_STAT_UNTRACKED &&
4248             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4249                 return NULL;
4251         switch (type) {
4252         case LINE_STAT_STAGED:
4253                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4254                 break;
4256         case LINE_STAT_UNSTAGED:
4257         case LINE_STAT_UNTRACKED:
4258                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4259                 break;
4261         default:
4262                 die("line type %d not handled in switch", type);
4263         }
4265         return popen(cmd, "w");
4268 static bool
4269 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4271         char buf[SIZEOF_STR];
4272         size_t bufsize = 0;
4273         size_t written = 0;
4275         switch (type) {
4276         case LINE_STAT_STAGED:
4277                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4278                                         status->old.mode,
4279                                         status->old.rev,
4280                                         status->old.name, 0))
4281                         return FALSE;
4282                 break;
4284         case LINE_STAT_UNSTAGED:
4285         case LINE_STAT_UNTRACKED:
4286                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4287                         return FALSE;
4288                 break;
4290         default:
4291                 die("line type %d not handled in switch", type);
4292         }
4294         while (!ferror(pipe) && written < bufsize) {
4295                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4296         }
4298         return written == bufsize;
4301 static bool
4302 status_update_file(struct status *status, enum line_type type)
4304         FILE *pipe = status_update_prepare(type);
4305         bool result;
4307         if (!pipe)
4308                 return FALSE;
4310         result = status_update_write(pipe, status, type);
4311         pclose(pipe);
4312         return result;
4315 static bool
4316 status_update_files(struct view *view, struct line *line)
4318         FILE *pipe = status_update_prepare(line->type);
4319         bool result = TRUE;
4320         struct line *pos = view->line + view->lines;
4321         int files = 0;
4322         int file, done;
4324         if (!pipe)
4325                 return FALSE;
4327         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4328                 files++;
4330         for (file = 0, done = 0; result && file < files; line++, file++) {
4331                 int almost_done = file * 100 / files;
4333                 if (almost_done > done) {
4334                         done = almost_done;
4335                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4336                                       file, files, done);
4337                         update_view_title(view);
4338                 }
4339                 result = status_update_write(pipe, line->data, line->type);
4340         }
4342         pclose(pipe);
4343         return result;
4346 static bool
4347 status_update(struct view *view)
4349         struct line *line = &view->line[view->lineno];
4351         assert(view->lines);
4353         if (!line->data) {
4354                 /* This should work even for the "On branch" line. */
4355                 if (line < view->line + view->lines && !line[1].data) {
4356                         report("Nothing to update");
4357                         return FALSE;
4358                 }
4360                 if (!status_update_files(view, line + 1)) {
4361                         report("Failed to update file status");
4362                         return FALSE;
4363                 }
4365         } else if (!status_update_file(line->data, line->type)) {
4366                 report("Failed to update file status");
4367                 return FALSE;
4368         }
4370         return TRUE;
4373 static bool
4374 status_revert(struct status *status, enum line_type type, bool has_none)
4376         if (!status || type != LINE_STAT_UNSTAGED) {
4377                 if (type == LINE_STAT_STAGED) {
4378                         report("Cannot revert changes to staged files");
4379                 } else if (type == LINE_STAT_UNTRACKED) {
4380                         report("Cannot revert changes to untracked files");
4381                 } else if (has_none) {
4382                         report("Nothing to revert");
4383                 } else {
4384                         report("Cannot revert changes to multiple files");
4385                 }
4386                 return FALSE;
4388         } else {
4389                 char cmd[SIZEOF_STR];
4390                 char file_sq[SIZEOF_STR];
4392                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4393                     !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4394                         return FALSE;
4396                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4397         }
4400 static enum request
4401 status_request(struct view *view, enum request request, struct line *line)
4403         struct status *status = line->data;
4405         switch (request) {
4406         case REQ_STATUS_UPDATE:
4407                 if (!status_update(view))
4408                         return REQ_NONE;
4409                 break;
4411         case REQ_STATUS_REVERT:
4412                 if (!status_revert(status, line->type, status_has_none(view, line)))
4413                         return REQ_NONE;
4414                 break;
4416         case REQ_STATUS_MERGE:
4417                 if (!status || status->status != 'U') {
4418                         report("Merging only possible for files with unmerged status ('U').");
4419                         return REQ_NONE;
4420                 }
4421                 open_mergetool(status->new.name);
4422                 break;
4424         case REQ_EDIT:
4425                 if (!status)
4426                         return request;
4428                 open_editor(status->status != '?', status->new.name);
4429                 break;
4431         case REQ_VIEW_BLAME:
4432                 if (status) {
4433                         string_copy(opt_file, status->new.name);
4434                         opt_ref[0] = 0;
4435                 }
4436                 return request;
4438         case REQ_ENTER:
4439                 /* After returning the status view has been split to
4440                  * show the stage view. No further reloading is
4441                  * necessary. */
4442                 status_enter(view, line);
4443                 return REQ_NONE;
4445         case REQ_REFRESH:
4446                 /* Simply reload the view. */
4447                 break;
4449         default:
4450                 return request;
4451         }
4453         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4455         return REQ_NONE;
4458 static void
4459 status_select(struct view *view, struct line *line)
4461         struct status *status = line->data;
4462         char file[SIZEOF_STR] = "all files";
4463         const char *text;
4464         const char *key;
4466         if (status && !string_format(file, "'%s'", status->new.name))
4467                 return;
4469         if (!status && line[1].type == LINE_STAT_NONE)
4470                 line++;
4472         switch (line->type) {
4473         case LINE_STAT_STAGED:
4474                 text = "Press %s to unstage %s for commit";
4475                 break;
4477         case LINE_STAT_UNSTAGED:
4478                 text = "Press %s to stage %s for commit";
4479                 break;
4481         case LINE_STAT_UNTRACKED:
4482                 text = "Press %s to stage %s for addition";
4483                 break;
4485         case LINE_STAT_HEAD:
4486         case LINE_STAT_NONE:
4487                 text = "Nothing to update";
4488                 break;
4490         default:
4491                 die("line type %d not handled in switch", line->type);
4492         }
4494         if (status && status->status == 'U') {
4495                 text = "Press %s to resolve conflict in %s";
4496                 key = get_key(REQ_STATUS_MERGE);
4498         } else {
4499                 key = get_key(REQ_STATUS_UPDATE);
4500         }
4502         string_format(view->ref, text, key, file);
4505 static bool
4506 status_grep(struct view *view, struct line *line)
4508         struct status *status = line->data;
4509         enum { S_STATUS, S_NAME, S_END } state;
4510         char buf[2] = "?";
4511         regmatch_t pmatch;
4513         if (!status)
4514                 return FALSE;
4516         for (state = S_STATUS; state < S_END; state++) {
4517                 const char *text;
4519                 switch (state) {
4520                 case S_NAME:    text = status->new.name;        break;
4521                 case S_STATUS:
4522                         buf[0] = status->status;
4523                         text = buf;
4524                         break;
4526                 default:
4527                         return FALSE;
4528                 }
4530                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4531                         return TRUE;
4532         }
4534         return FALSE;
4537 static struct view_ops status_ops = {
4538         "file",
4539         status_open,
4540         NULL,
4541         status_draw,
4542         status_request,
4543         status_grep,
4544         status_select,
4545 };
4548 static bool
4549 stage_diff_line(FILE *pipe, struct line *line)
4551         const char *buf = line->data;
4552         size_t bufsize = strlen(buf);
4553         size_t written = 0;
4555         while (!ferror(pipe) && written < bufsize) {
4556                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4557         }
4559         fputc('\n', pipe);
4561         return written == bufsize;
4564 static bool
4565 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4567         while (line < end) {
4568                 if (!stage_diff_line(pipe, line++))
4569                         return FALSE;
4570                 if (line->type == LINE_DIFF_CHUNK ||
4571                     line->type == LINE_DIFF_HEADER)
4572                         break;
4573         }
4575         return TRUE;
4578 static struct line *
4579 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4581         for (; view->line < line; line--)
4582                 if (line->type == type)
4583                         return line;
4585         return NULL;
4588 static bool
4589 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4591         char cmd[SIZEOF_STR];
4592         size_t cmdsize = 0;
4593         struct line *diff_hdr;
4594         FILE *pipe;
4596         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4597         if (!diff_hdr)
4598                 return FALSE;
4600         if (opt_cdup[0] &&
4601             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4602                 return FALSE;
4604         if (!string_format_from(cmd, &cmdsize,
4605                                 "git apply --whitespace=nowarn %s %s - && "
4606                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4607                                 revert ? "" : "--cached",
4608                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4609                 return FALSE;
4611         pipe = popen(cmd, "w");
4612         if (!pipe)
4613                 return FALSE;
4615         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4616             !stage_diff_write(pipe, chunk, view->line + view->lines))
4617                 chunk = NULL;
4619         pclose(pipe);
4621         return chunk ? TRUE : FALSE;
4624 static bool
4625 stage_update(struct view *view, struct line *line)
4627         struct line *chunk = NULL;
4629         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4630                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4632         if (chunk) {
4633                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4634                         report("Failed to apply chunk");
4635                         return FALSE;
4636                 }
4638         } else if (!stage_status.status) {
4639                 view = VIEW(REQ_VIEW_STATUS);
4641                 for (line = view->line; line < view->line + view->lines; line++)
4642                         if (line->type == stage_line_type)
4643                                 break;
4645                 if (!status_update_files(view, line + 1)) {
4646                         report("Failed to update files");
4647                         return FALSE;
4648                 }
4650         } else if (!status_update_file(&stage_status, stage_line_type)) {
4651                 report("Failed to update file");
4652                 return FALSE;
4653         }
4655         return TRUE;
4658 static bool
4659 stage_revert(struct view *view, struct line *line)
4661         struct line *chunk = NULL;
4663         if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4664                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4666         if (chunk) {
4667                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4668                         return FALSE;
4670                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4671                         report("Failed to revert chunk");
4672                         return FALSE;
4673                 }
4674                 return TRUE;
4676         } else {
4677                 return status_revert(stage_status.status ? &stage_status : NULL,
4678                                      stage_line_type, FALSE);
4679         }
4683 static void
4684 stage_next(struct view *view, struct line *line)
4686         int i;
4688         if (!stage_chunks) {
4689                 static size_t alloc = 0;
4690                 int *tmp;
4692                 for (line = view->line; line < view->line + view->lines; line++) {
4693                         if (line->type != LINE_DIFF_CHUNK)
4694                                 continue;
4696                         tmp = realloc_items(stage_chunk, &alloc,
4697                                             stage_chunks, sizeof(*tmp));
4698                         if (!tmp) {
4699                                 report("Allocation failure");
4700                                 return;
4701                         }
4703                         stage_chunk = tmp;
4704                         stage_chunk[stage_chunks++] = line - view->line;
4705                 }
4706         }
4708         for (i = 0; i < stage_chunks; i++) {
4709                 if (stage_chunk[i] > view->lineno) {
4710                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4711                         report("Chunk %d of %d", i + 1, stage_chunks);
4712                         return;
4713                 }
4714         }
4716         report("No next chunk found");
4719 static enum request
4720 stage_request(struct view *view, enum request request, struct line *line)
4722         switch (request) {
4723         case REQ_STATUS_UPDATE:
4724                 if (!stage_update(view, line))
4725                         return REQ_NONE;
4726                 break;
4728         case REQ_STATUS_REVERT:
4729                 if (!stage_revert(view, line))
4730                         return REQ_NONE;
4731                 break;
4733         case REQ_STAGE_NEXT:
4734                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4735                         report("File is untracked; press %s to add",
4736                                get_key(REQ_STATUS_UPDATE));
4737                         return REQ_NONE;
4738                 }
4739                 stage_next(view, line);
4740                 return REQ_NONE;
4742         case REQ_EDIT:
4743                 if (!stage_status.new.name[0])
4744                         return request;
4746                 open_editor(stage_status.status != '?', stage_status.new.name);
4747                 break;
4749         case REQ_REFRESH:
4750                 /* Reload everything ... */
4751                 break;
4753         case REQ_VIEW_BLAME:
4754                 if (stage_status.new.name[0]) {
4755                         string_copy(opt_file, stage_status.new.name);
4756                         opt_ref[0] = 0;
4757                 }
4758                 return request;
4760         case REQ_ENTER:
4761                 return pager_request(view, request, line);
4763         default:
4764                 return request;
4765         }
4767         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4769         /* Check whether the staged entry still exists, and close the
4770          * stage view if it doesn't. */
4771         if (!status_exists(&stage_status, stage_line_type))
4772                 return REQ_VIEW_CLOSE;
4774         if (stage_line_type == LINE_STAT_UNTRACKED)
4775                 opt_pipe = fopen(stage_status.new.name, "r");
4776         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4778         return REQ_NONE;
4781 static struct view_ops stage_ops = {
4782         "line",
4783         NULL,
4784         pager_read,
4785         pager_draw,
4786         stage_request,
4787         pager_grep,
4788         pager_select,
4789 };
4792 /*
4793  * Revision graph
4794  */
4796 struct commit {
4797         char id[SIZEOF_REV];            /* SHA1 ID. */
4798         char title[128];                /* First line of the commit message. */
4799         char author[75];                /* Author of the commit. */
4800         struct tm time;                 /* Date from the author ident. */
4801         struct ref **refs;              /* Repository references. */
4802         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4803         size_t graph_size;              /* The width of the graph array. */
4804         bool has_parents;               /* Rewritten --parents seen. */
4805 };
4807 /* Size of rev graph with no  "padding" columns */
4808 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4810 struct rev_graph {
4811         struct rev_graph *prev, *next, *parents;
4812         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4813         size_t size;
4814         struct commit *commit;
4815         size_t pos;
4816         unsigned int boundary:1;
4817 };
4819 /* Parents of the commit being visualized. */
4820 static struct rev_graph graph_parents[4];
4822 /* The current stack of revisions on the graph. */
4823 static struct rev_graph graph_stacks[4] = {
4824         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4825         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4826         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4827         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4828 };
4830 static inline bool
4831 graph_parent_is_merge(struct rev_graph *graph)
4833         return graph->parents->size > 1;
4836 static inline void
4837 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4839         struct commit *commit = graph->commit;
4841         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4842                 commit->graph[commit->graph_size++] = symbol;
4845 static void
4846 clear_rev_graph(struct rev_graph *graph)
4848         graph->boundary = 0;
4849         graph->size = graph->pos = 0;
4850         graph->commit = NULL;
4851         memset(graph->parents, 0, sizeof(*graph->parents));
4854 static void
4855 done_rev_graph(struct rev_graph *graph)
4857         if (graph_parent_is_merge(graph) &&
4858             graph->pos < graph->size - 1 &&
4859             graph->next->size == graph->size + graph->parents->size - 1) {
4860                 size_t i = graph->pos + graph->parents->size - 1;
4862                 graph->commit->graph_size = i * 2;
4863                 while (i < graph->next->size - 1) {
4864                         append_to_rev_graph(graph, ' ');
4865                         append_to_rev_graph(graph, '\\');
4866                         i++;
4867                 }
4868         }
4870         clear_rev_graph(graph);
4873 static void
4874 push_rev_graph(struct rev_graph *graph, const char *parent)
4876         int i;
4878         /* "Collapse" duplicate parents lines.
4879          *
4880          * FIXME: This needs to also update update the drawn graph but
4881          * for now it just serves as a method for pruning graph lines. */
4882         for (i = 0; i < graph->size; i++)
4883                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4884                         return;
4886         if (graph->size < SIZEOF_REVITEMS) {
4887                 string_copy_rev(graph->rev[graph->size++], parent);
4888         }
4891 static chtype
4892 get_rev_graph_symbol(struct rev_graph *graph)
4894         chtype symbol;
4896         if (graph->boundary)
4897                 symbol = REVGRAPH_BOUND;
4898         else if (graph->parents->size == 0)
4899                 symbol = REVGRAPH_INIT;
4900         else if (graph_parent_is_merge(graph))
4901                 symbol = REVGRAPH_MERGE;
4902         else if (graph->pos >= graph->size)
4903                 symbol = REVGRAPH_BRANCH;
4904         else
4905                 symbol = REVGRAPH_COMMIT;
4907         return symbol;
4910 static void
4911 draw_rev_graph(struct rev_graph *graph)
4913         struct rev_filler {
4914                 chtype separator, line;
4915         };
4916         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4917         static struct rev_filler fillers[] = {
4918                 { ' ',  '|' },
4919                 { '`',  '.' },
4920                 { '\'', ' ' },
4921                 { '/',  ' ' },
4922         };
4923         chtype symbol = get_rev_graph_symbol(graph);
4924         struct rev_filler *filler;
4925         size_t i;
4927         if (opt_line_graphics)
4928                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4930         filler = &fillers[DEFAULT];
4932         for (i = 0; i < graph->pos; i++) {
4933                 append_to_rev_graph(graph, filler->line);
4934                 if (graph_parent_is_merge(graph->prev) &&
4935                     graph->prev->pos == i)
4936                         filler = &fillers[RSHARP];
4938                 append_to_rev_graph(graph, filler->separator);
4939         }
4941         /* Place the symbol for this revision. */
4942         append_to_rev_graph(graph, symbol);
4944         if (graph->prev->size > graph->size)
4945                 filler = &fillers[RDIAG];
4946         else
4947                 filler = &fillers[DEFAULT];
4949         i++;
4951         for (; i < graph->size; i++) {
4952                 append_to_rev_graph(graph, filler->separator);
4953                 append_to_rev_graph(graph, filler->line);
4954                 if (graph_parent_is_merge(graph->prev) &&
4955                     i < graph->prev->pos + graph->parents->size)
4956                         filler = &fillers[RSHARP];
4957                 if (graph->prev->size > graph->size)
4958                         filler = &fillers[LDIAG];
4959         }
4961         if (graph->prev->size > graph->size) {
4962                 append_to_rev_graph(graph, filler->separator);
4963                 if (filler->line != ' ')
4964                         append_to_rev_graph(graph, filler->line);
4965         }
4968 /* Prepare the next rev graph */
4969 static void
4970 prepare_rev_graph(struct rev_graph *graph)
4972         size_t i;
4974         /* First, traverse all lines of revisions up to the active one. */
4975         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4976                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4977                         break;
4979                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4980         }
4982         /* Interleave the new revision parent(s). */
4983         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4984                 push_rev_graph(graph->next, graph->parents->rev[i]);
4986         /* Lastly, put any remaining revisions. */
4987         for (i = graph->pos + 1; i < graph->size; i++)
4988                 push_rev_graph(graph->next, graph->rev[i]);
4991 static void
4992 update_rev_graph(struct rev_graph *graph)
4994         /* If this is the finalizing update ... */
4995         if (graph->commit)
4996                 prepare_rev_graph(graph);
4998         /* Graph visualization needs a one rev look-ahead,
4999          * so the first update doesn't visualize anything. */
5000         if (!graph->prev->commit)
5001                 return;
5003         draw_rev_graph(graph->prev);
5004         done_rev_graph(graph->prev->prev);
5008 /*
5009  * Main view backend
5010  */
5012 static bool
5013 main_draw(struct view *view, struct line *line, unsigned int lineno)
5015         struct commit *commit = line->data;
5017         if (!*commit->author)
5018                 return FALSE;
5020         if (opt_date && draw_date(view, &commit->time))
5021                 return TRUE;
5023         if (opt_author &&
5024             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5025                 return TRUE;
5027         if (opt_rev_graph && commit->graph_size &&
5028             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5029                 return TRUE;
5031         if (opt_show_refs && commit->refs) {
5032                 size_t i = 0;
5034                 do {
5035                         enum line_type type;
5037                         if (commit->refs[i]->head)
5038                                 type = LINE_MAIN_HEAD;
5039                         else if (commit->refs[i]->ltag)
5040                                 type = LINE_MAIN_LOCAL_TAG;
5041                         else if (commit->refs[i]->tag)
5042                                 type = LINE_MAIN_TAG;
5043                         else if (commit->refs[i]->tracked)
5044                                 type = LINE_MAIN_TRACKED;
5045                         else if (commit->refs[i]->remote)
5046                                 type = LINE_MAIN_REMOTE;
5047                         else
5048                                 type = LINE_MAIN_REF;
5050                         if (draw_text(view, type, "[", TRUE) ||
5051                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5052                             draw_text(view, type, "]", TRUE))
5053                                 return TRUE;
5055                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5056                                 return TRUE;
5057                 } while (commit->refs[i++]->next);
5058         }
5060         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5061         return TRUE;
5064 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5065 static bool
5066 main_read(struct view *view, char *line)
5068         static struct rev_graph *graph = graph_stacks;
5069         enum line_type type;
5070         struct commit *commit;
5072         if (!line) {
5073                 int i;
5075                 if (!view->lines && !view->parent)
5076                         die("No revisions match the given arguments.");
5077                 if (view->lines > 0) {
5078                         commit = view->line[view->lines - 1].data;
5079                         if (!*commit->author) {
5080                                 view->lines--;
5081                                 free(commit);
5082                                 graph->commit = NULL;
5083                         }
5084                 }
5085                 update_rev_graph(graph);
5087                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5088                         clear_rev_graph(&graph_stacks[i]);
5089                 return TRUE;
5090         }
5092         type = get_line_type(line);
5093         if (type == LINE_COMMIT) {
5094                 commit = calloc(1, sizeof(struct commit));
5095                 if (!commit)
5096                         return FALSE;
5098                 line += STRING_SIZE("commit ");
5099                 if (*line == '-') {
5100                         graph->boundary = 1;
5101                         line++;
5102                 }
5104                 string_copy_rev(commit->id, line);
5105                 commit->refs = get_refs(commit->id);
5106                 graph->commit = commit;
5107                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5109                 while ((line = strchr(line, ' '))) {
5110                         line++;
5111                         push_rev_graph(graph->parents, line);
5112                         commit->has_parents = TRUE;
5113                 }
5114                 return TRUE;
5115         }
5117         if (!view->lines)
5118                 return TRUE;
5119         commit = view->line[view->lines - 1].data;
5121         switch (type) {
5122         case LINE_PARENT:
5123                 if (commit->has_parents)
5124                         break;
5125                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5126                 break;
5128         case LINE_AUTHOR:
5129         {
5130                 /* Parse author lines where the name may be empty:
5131                  *      author  <email@address.tld> 1138474660 +0100
5132                  */
5133                 char *ident = line + STRING_SIZE("author ");
5134                 char *nameend = strchr(ident, '<');
5135                 char *emailend = strchr(ident, '>');
5137                 if (!nameend || !emailend)
5138                         break;
5140                 update_rev_graph(graph);
5141                 graph = graph->next;
5143                 *nameend = *emailend = 0;
5144                 ident = chomp_string(ident);
5145                 if (!*ident) {
5146                         ident = chomp_string(nameend + 1);
5147                         if (!*ident)
5148                                 ident = "Unknown";
5149                 }
5151                 string_ncopy(commit->author, ident, strlen(ident));
5153                 /* Parse epoch and timezone */
5154                 if (emailend[1] == ' ') {
5155                         char *secs = emailend + 2;
5156                         char *zone = strchr(secs, ' ');
5157                         time_t time = (time_t) atol(secs);
5159                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5160                                 long tz;
5162                                 zone++;
5163                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5164                                 tz += ('0' - zone[2]) * 60 * 60;
5165                                 tz += ('0' - zone[3]) * 60;
5166                                 tz += ('0' - zone[4]) * 60;
5168                                 if (zone[0] == '-')
5169                                         tz = -tz;
5171                                 time -= tz;
5172                         }
5174                         gmtime_r(&time, &commit->time);
5175                 }
5176                 break;
5177         }
5178         default:
5179                 /* Fill in the commit title if it has not already been set. */
5180                 if (commit->title[0])
5181                         break;
5183                 /* Require titles to start with a non-space character at the
5184                  * offset used by git log. */
5185                 if (strncmp(line, "    ", 4))
5186                         break;
5187                 line += 4;
5188                 /* Well, if the title starts with a whitespace character,
5189                  * try to be forgiving.  Otherwise we end up with no title. */
5190                 while (isspace(*line))
5191                         line++;
5192                 if (*line == '\0')
5193                         break;
5194                 /* FIXME: More graceful handling of titles; append "..." to
5195                  * shortened titles, etc. */
5197                 string_ncopy(commit->title, line, strlen(line));
5198         }
5200         return TRUE;
5203 static enum request
5204 main_request(struct view *view, enum request request, struct line *line)
5206         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5208         switch (request) {
5209         case REQ_ENTER:
5210                 open_view(view, REQ_VIEW_DIFF, flags);
5211                 break;
5212         case REQ_REFRESH:
5213                 load_refs();
5214                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5215                 break;
5216         default:
5217                 return request;
5218         }
5220         return REQ_NONE;
5223 static bool
5224 grep_refs(struct ref **refs, regex_t *regex)
5226         regmatch_t pmatch;
5227         size_t i = 0;
5229         if (!refs)
5230                 return FALSE;
5231         do {
5232                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5233                         return TRUE;
5234         } while (refs[i++]->next);
5236         return FALSE;
5239 static bool
5240 main_grep(struct view *view, struct line *line)
5242         struct commit *commit = line->data;
5243         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5244         char buf[DATE_COLS + 1];
5245         regmatch_t pmatch;
5247         for (state = S_TITLE; state < S_END; state++) {
5248                 char *text;
5250                 switch (state) {
5251                 case S_TITLE:   text = commit->title;   break;
5252                 case S_AUTHOR:
5253                         if (!opt_author)
5254                                 continue;
5255                         text = commit->author;
5256                         break;
5257                 case S_DATE:
5258                         if (!opt_date)
5259                                 continue;
5260                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5261                                 continue;
5262                         text = buf;
5263                         break;
5264                 case S_REFS:
5265                         if (!opt_show_refs)
5266                                 continue;
5267                         if (grep_refs(commit->refs, view->regex) == TRUE)
5268                                 return TRUE;
5269                         continue;
5270                 default:
5271                         return FALSE;
5272                 }
5274                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5275                         return TRUE;
5276         }
5278         return FALSE;
5281 static void
5282 main_select(struct view *view, struct line *line)
5284         struct commit *commit = line->data;
5286         string_copy_rev(view->ref, commit->id);
5287         string_copy_rev(ref_commit, view->ref);
5290 static struct view_ops main_ops = {
5291         "commit",
5292         NULL,
5293         main_read,
5294         main_draw,
5295         main_request,
5296         main_grep,
5297         main_select,
5298 };
5301 /*
5302  * Unicode / UTF-8 handling
5303  *
5304  * NOTE: Much of the following code for dealing with unicode is derived from
5305  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5306  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5307  */
5309 /* I've (over)annotated a lot of code snippets because I am not entirely
5310  * confident that the approach taken by this small UTF-8 interface is correct.
5311  * --jonas */
5313 static inline int
5314 unicode_width(unsigned long c)
5316         if (c >= 0x1100 &&
5317            (c <= 0x115f                         /* Hangul Jamo */
5318             || c == 0x2329
5319             || c == 0x232a
5320             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5321                                                 /* CJK ... Yi */
5322             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5323             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5324             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5325             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5326             || (c >= 0xffe0  && c <= 0xffe6)
5327             || (c >= 0x20000 && c <= 0x2fffd)
5328             || (c >= 0x30000 && c <= 0x3fffd)))
5329                 return 2;
5331         if (c == '\t')
5332                 return opt_tab_size;
5334         return 1;
5337 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5338  * Illegal bytes are set one. */
5339 static const unsigned char utf8_bytes[256] = {
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         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5346         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,
5347         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,
5348 };
5350 /* Decode UTF-8 multi-byte representation into a unicode character. */
5351 static inline unsigned long
5352 utf8_to_unicode(const char *string, size_t length)
5354         unsigned long unicode;
5356         switch (length) {
5357         case 1:
5358                 unicode  =   string[0];
5359                 break;
5360         case 2:
5361                 unicode  =  (string[0] & 0x1f) << 6;
5362                 unicode +=  (string[1] & 0x3f);
5363                 break;
5364         case 3:
5365                 unicode  =  (string[0] & 0x0f) << 12;
5366                 unicode += ((string[1] & 0x3f) << 6);
5367                 unicode +=  (string[2] & 0x3f);
5368                 break;
5369         case 4:
5370                 unicode  =  (string[0] & 0x0f) << 18;
5371                 unicode += ((string[1] & 0x3f) << 12);
5372                 unicode += ((string[2] & 0x3f) << 6);
5373                 unicode +=  (string[3] & 0x3f);
5374                 break;
5375         case 5:
5376                 unicode  =  (string[0] & 0x0f) << 24;
5377                 unicode += ((string[1] & 0x3f) << 18);
5378                 unicode += ((string[2] & 0x3f) << 12);
5379                 unicode += ((string[3] & 0x3f) << 6);
5380                 unicode +=  (string[4] & 0x3f);
5381                 break;
5382         case 6:
5383                 unicode  =  (string[0] & 0x01) << 30;
5384                 unicode += ((string[1] & 0x3f) << 24);
5385                 unicode += ((string[2] & 0x3f) << 18);
5386                 unicode += ((string[3] & 0x3f) << 12);
5387                 unicode += ((string[4] & 0x3f) << 6);
5388                 unicode +=  (string[5] & 0x3f);
5389                 break;
5390         default:
5391                 die("Invalid unicode length");
5392         }
5394         /* Invalid characters could return the special 0xfffd value but NUL
5395          * should be just as good. */
5396         return unicode > 0xffff ? 0 : unicode;
5399 /* Calculates how much of string can be shown within the given maximum width
5400  * and sets trimmed parameter to non-zero value if all of string could not be
5401  * shown. If the reserve flag is TRUE, it will reserve at least one
5402  * trailing character, which can be useful when drawing a delimiter.
5403  *
5404  * Returns the number of bytes to output from string to satisfy max_width. */
5405 static size_t
5406 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5408         const char *start = string;
5409         const char *end = strchr(string, '\0');
5410         unsigned char last_bytes = 0;
5411         size_t last_ucwidth = 0;
5413         *width = 0;
5414         *trimmed = 0;
5416         while (string < end) {
5417                 int c = *(unsigned char *) string;
5418                 unsigned char bytes = utf8_bytes[c];
5419                 size_t ucwidth;
5420                 unsigned long unicode;
5422                 if (string + bytes > end)
5423                         break;
5425                 /* Change representation to figure out whether
5426                  * it is a single- or double-width character. */
5428                 unicode = utf8_to_unicode(string, bytes);
5429                 /* FIXME: Graceful handling of invalid unicode character. */
5430                 if (!unicode)
5431                         break;
5433                 ucwidth = unicode_width(unicode);
5434                 *width  += ucwidth;
5435                 if (*width > max_width) {
5436                         *trimmed = 1;
5437                         *width -= ucwidth;
5438                         if (reserve && *width == max_width) {
5439                                 string -= last_bytes;
5440                                 *width -= last_ucwidth;
5441                         }
5442                         break;
5443                 }
5445                 string  += bytes;
5446                 last_bytes = bytes;
5447                 last_ucwidth = ucwidth;
5448         }
5450         return string - start;
5454 /*
5455  * Status management
5456  */
5458 /* Whether or not the curses interface has been initialized. */
5459 static bool cursed = FALSE;
5461 /* The status window is used for polling keystrokes. */
5462 static WINDOW *status_win;
5464 static bool status_empty = TRUE;
5466 /* Update status and title window. */
5467 static void
5468 report(const char *msg, ...)
5470         struct view *view = display[current_view];
5472         if (input_mode)
5473                 return;
5475         if (!view) {
5476                 char buf[SIZEOF_STR];
5477                 va_list args;
5479                 va_start(args, msg);
5480                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5481                         buf[sizeof(buf) - 1] = 0;
5482                         buf[sizeof(buf) - 2] = '.';
5483                         buf[sizeof(buf) - 3] = '.';
5484                         buf[sizeof(buf) - 4] = '.';
5485                 }
5486                 va_end(args);
5487                 die("%s", buf);
5488         }
5490         if (!status_empty || *msg) {
5491                 va_list args;
5493                 va_start(args, msg);
5495                 wmove(status_win, 0, 0);
5496                 if (*msg) {
5497                         vwprintw(status_win, msg, args);
5498                         status_empty = FALSE;
5499                 } else {
5500                         status_empty = TRUE;
5501                 }
5502                 wclrtoeol(status_win);
5503                 wrefresh(status_win);
5505                 va_end(args);
5506         }
5508         update_view_title(view);
5509         update_display_cursor(view);
5512 /* Controls when nodelay should be in effect when polling user input. */
5513 static void
5514 set_nonblocking_input(bool loading)
5516         static unsigned int loading_views;
5518         if ((loading == FALSE && loading_views-- == 1) ||
5519             (loading == TRUE  && loading_views++ == 0))
5520                 nodelay(status_win, loading);
5523 static void
5524 init_display(void)
5526         int x, y;
5528         /* Initialize the curses library */
5529         if (isatty(STDIN_FILENO)) {
5530                 cursed = !!initscr();
5531                 opt_tty = stdin;
5532         } else {
5533                 /* Leave stdin and stdout alone when acting as a pager. */
5534                 opt_tty = fopen("/dev/tty", "r+");
5535                 if (!opt_tty)
5536                         die("Failed to open /dev/tty");
5537                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5538         }
5540         if (!cursed)
5541                 die("Failed to initialize curses");
5543         nonl();         /* Tell curses not to do NL->CR/NL on output */
5544         cbreak();       /* Take input chars one at a time, no wait for \n */
5545         noecho();       /* Don't echo input */
5546         leaveok(stdscr, TRUE);
5548         if (has_colors())
5549                 init_colors();
5551         getmaxyx(stdscr, y, x);
5552         status_win = newwin(1, 0, y - 1, 0);
5553         if (!status_win)
5554                 die("Failed to create status window");
5556         /* Enable keyboard mapping */
5557         keypad(status_win, TRUE);
5558         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5560         TABSIZE = opt_tab_size;
5561         if (opt_line_graphics) {
5562                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5563         }
5566 static bool
5567 prompt_yesno(const char *prompt)
5569         enum { WAIT, STOP, CANCEL  } status = WAIT;
5570         bool answer = FALSE;
5572         while (status == WAIT) {
5573                 struct view *view;
5574                 int i, key;
5576                 input_mode = TRUE;
5578                 foreach_view (view, i)
5579                         update_view(view);
5581                 input_mode = FALSE;
5583                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5584                 wclrtoeol(status_win);
5586                 /* Refresh, accept single keystroke of input */
5587                 key = wgetch(status_win);
5588                 switch (key) {
5589                 case ERR:
5590                         break;
5592                 case 'y':
5593                 case 'Y':
5594                         answer = TRUE;
5595                         status = STOP;
5596                         break;
5598                 case KEY_ESC:
5599                 case KEY_RETURN:
5600                 case KEY_ENTER:
5601                 case KEY_BACKSPACE:
5602                 case 'n':
5603                 case 'N':
5604                 case '\n':
5605                 default:
5606                         answer = FALSE;
5607                         status = CANCEL;
5608                 }
5609         }
5611         /* Clear the status window */
5612         status_empty = FALSE;
5613         report("");
5615         return answer;
5618 static char *
5619 read_prompt(const char *prompt)
5621         enum { READING, STOP, CANCEL } status = READING;
5622         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5623         int pos = 0;
5625         while (status == READING) {
5626                 struct view *view;
5627                 int i, key;
5629                 input_mode = TRUE;
5631                 foreach_view (view, i)
5632                         update_view(view);
5634                 input_mode = FALSE;
5636                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5637                 wclrtoeol(status_win);
5639                 /* Refresh, accept single keystroke of input */
5640                 key = wgetch(status_win);
5641                 switch (key) {
5642                 case KEY_RETURN:
5643                 case KEY_ENTER:
5644                 case '\n':
5645                         status = pos ? STOP : CANCEL;
5646                         break;
5648                 case KEY_BACKSPACE:
5649                         if (pos > 0)
5650                                 pos--;
5651                         else
5652                                 status = CANCEL;
5653                         break;
5655                 case KEY_ESC:
5656                         status = CANCEL;
5657                         break;
5659                 case ERR:
5660                         break;
5662                 default:
5663                         if (pos >= sizeof(buf)) {
5664                                 report("Input string too long");
5665                                 return NULL;
5666                         }
5668                         if (isprint(key))
5669                                 buf[pos++] = (char) key;
5670                 }
5671         }
5673         /* Clear the status window */
5674         status_empty = FALSE;
5675         report("");
5677         if (status == CANCEL)
5678                 return NULL;
5680         buf[pos++] = 0;
5682         return buf;
5685 /*
5686  * Repository references
5687  */
5689 static struct ref *refs = NULL;
5690 static size_t refs_alloc = 0;
5691 static size_t refs_size = 0;
5693 /* Id <-> ref store */
5694 static struct ref ***id_refs = NULL;
5695 static size_t id_refs_alloc = 0;
5696 static size_t id_refs_size = 0;
5698 static int
5699 compare_refs(const void *ref1_, const void *ref2_)
5701         const struct ref *ref1 = *(const struct ref **)ref1_;
5702         const struct ref *ref2 = *(const struct ref **)ref2_;
5704         if (ref1->tag != ref2->tag)
5705                 return ref2->tag - ref1->tag;
5706         if (ref1->ltag != ref2->ltag)
5707                 return ref2->ltag - ref2->ltag;
5708         if (ref1->head != ref2->head)
5709                 return ref2->head - ref1->head;
5710         if (ref1->tracked != ref2->tracked)
5711                 return ref2->tracked - ref1->tracked;
5712         if (ref1->remote != ref2->remote)
5713                 return ref2->remote - ref1->remote;
5714         return strcmp(ref1->name, ref2->name);
5717 static struct ref **
5718 get_refs(const char *id)
5720         struct ref ***tmp_id_refs;
5721         struct ref **ref_list = NULL;
5722         size_t ref_list_alloc = 0;
5723         size_t ref_list_size = 0;
5724         size_t i;
5726         for (i = 0; i < id_refs_size; i++)
5727                 if (!strcmp(id, id_refs[i][0]->id))
5728                         return id_refs[i];
5730         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5731                                     sizeof(*id_refs));
5732         if (!tmp_id_refs)
5733                 return NULL;
5735         id_refs = tmp_id_refs;
5737         for (i = 0; i < refs_size; i++) {
5738                 struct ref **tmp;
5740                 if (strcmp(id, refs[i].id))
5741                         continue;
5743                 tmp = realloc_items(ref_list, &ref_list_alloc,
5744                                     ref_list_size + 1, sizeof(*ref_list));
5745                 if (!tmp) {
5746                         if (ref_list)
5747                                 free(ref_list);
5748                         return NULL;
5749                 }
5751                 ref_list = tmp;
5752                 ref_list[ref_list_size] = &refs[i];
5753                 /* XXX: The properties of the commit chains ensures that we can
5754                  * safely modify the shared ref. The repo references will
5755                  * always be similar for the same id. */
5756                 ref_list[ref_list_size]->next = 1;
5758                 ref_list_size++;
5759         }
5761         if (ref_list) {
5762                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5763                 ref_list[ref_list_size - 1]->next = 0;
5764                 id_refs[id_refs_size++] = ref_list;
5765         }
5767         return ref_list;
5770 static int
5771 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5773         struct ref *ref;
5774         bool tag = FALSE;
5775         bool ltag = FALSE;
5776         bool remote = FALSE;
5777         bool tracked = FALSE;
5778         bool check_replace = FALSE;
5779         bool head = FALSE;
5781         if (!prefixcmp(name, "refs/tags/")) {
5782                 if (!strcmp(name + namelen - 3, "^{}")) {
5783                         namelen -= 3;
5784                         name[namelen] = 0;
5785                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5786                                 check_replace = TRUE;
5787                 } else {
5788                         ltag = TRUE;
5789                 }
5791                 tag = TRUE;
5792                 namelen -= STRING_SIZE("refs/tags/");
5793                 name    += STRING_SIZE("refs/tags/");
5795         } else if (!prefixcmp(name, "refs/remotes/")) {
5796                 remote = TRUE;
5797                 namelen -= STRING_SIZE("refs/remotes/");
5798                 name    += STRING_SIZE("refs/remotes/");
5799                 tracked  = !strcmp(opt_remote, name);
5801         } else if (!prefixcmp(name, "refs/heads/")) {
5802                 namelen -= STRING_SIZE("refs/heads/");
5803                 name    += STRING_SIZE("refs/heads/");
5804                 head     = !strncmp(opt_head, name, namelen);
5806         } else if (!strcmp(name, "HEAD")) {
5807                 opt_no_head = FALSE;
5808                 return OK;
5809         }
5811         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5812                 /* it's an annotated tag, replace the previous sha1 with the
5813                  * resolved commit id; relies on the fact git-ls-remote lists
5814                  * the commit id of an annotated tag right before the commit id
5815                  * it points to. */
5816                 refs[refs_size - 1].ltag = ltag;
5817                 string_copy_rev(refs[refs_size - 1].id, id);
5819                 return OK;
5820         }
5821         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5822         if (!refs)
5823                 return ERR;
5825         ref = &refs[refs_size++];
5826         ref->name = malloc(namelen + 1);
5827         if (!ref->name)
5828                 return ERR;
5830         strncpy(ref->name, name, namelen);
5831         ref->name[namelen] = 0;
5832         ref->head = head;
5833         ref->tag = tag;
5834         ref->ltag = ltag;
5835         ref->remote = remote;
5836         ref->tracked = tracked;
5837         string_copy_rev(ref->id, id);
5839         return OK;
5842 static int
5843 load_refs(void)
5845         const char *cmd_env = getenv("TIG_LS_REMOTE");
5846         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5848         if (!*opt_git_dir)
5849                 return OK;
5851         while (refs_size > 0)
5852                 free(refs[--refs_size].name);
5853         while (id_refs_size > 0)
5854                 free(id_refs[--id_refs_size]);
5856         return read_properties(popen(cmd, "r"), "\t", read_ref);
5859 static int
5860 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5862         if (!strcmp(name, "i18n.commitencoding"))
5863                 string_ncopy(opt_encoding, value, valuelen);
5865         if (!strcmp(name, "core.editor"))
5866                 string_ncopy(opt_editor, value, valuelen);
5868         /* branch.<head>.remote */
5869         if (*opt_head &&
5870             !strncmp(name, "branch.", 7) &&
5871             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5872             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5873                 string_ncopy(opt_remote, value, valuelen);
5875         if (*opt_head && *opt_remote &&
5876             !strncmp(name, "branch.", 7) &&
5877             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5878             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5879                 size_t from = strlen(opt_remote);
5881                 if (!prefixcmp(value, "refs/heads/")) {
5882                         value += STRING_SIZE("refs/heads/");
5883                         valuelen -= STRING_SIZE("refs/heads/");
5884                 }
5886                 if (!string_format_from(opt_remote, &from, "/%s", value))
5887                         opt_remote[0] = 0;
5888         }
5890         return OK;
5893 static int
5894 load_git_config(void)
5896         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5897                                "=", read_repo_config_option);
5900 static int
5901 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5903         if (!opt_git_dir[0]) {
5904                 string_ncopy(opt_git_dir, name, namelen);
5906         } else if (opt_is_inside_work_tree == -1) {
5907                 /* This can be 3 different values depending on the
5908                  * version of git being used. If git-rev-parse does not
5909                  * understand --is-inside-work-tree it will simply echo
5910                  * the option else either "true" or "false" is printed.
5911                  * Default to true for the unknown case. */
5912                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5914         } else if (opt_cdup[0] == ' ') {
5915                 string_ncopy(opt_cdup, name, namelen);
5916         } else {
5917                 if (!prefixcmp(name, "refs/heads/")) {
5918                         namelen -= STRING_SIZE("refs/heads/");
5919                         name    += STRING_SIZE("refs/heads/");
5920                         string_ncopy(opt_head, name, namelen);
5921                 }
5922         }
5924         return OK;
5927 static int
5928 load_repo_info(void)
5930         int result;
5931         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5932                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5934         /* XXX: The line outputted by "--show-cdup" can be empty so
5935          * initialize it to something invalid to make it possible to
5936          * detect whether it has been set or not. */
5937         opt_cdup[0] = ' ';
5939         result = read_properties(pipe, "=", read_repo_info);
5940         if (opt_cdup[0] == ' ')
5941                 opt_cdup[0] = 0;
5943         return result;
5946 static int
5947 read_properties(FILE *pipe, const char *separators,
5948                 int (*read_property)(char *, size_t, char *, size_t))
5950         char buffer[BUFSIZ];
5951         char *name;
5952         int state = OK;
5954         if (!pipe)
5955                 return ERR;
5957         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5958                 char *value;
5959                 size_t namelen;
5960                 size_t valuelen;
5962                 name = chomp_string(name);
5963                 namelen = strcspn(name, separators);
5965                 if (name[namelen]) {
5966                         name[namelen] = 0;
5967                         value = chomp_string(name + namelen + 1);
5968                         valuelen = strlen(value);
5970                 } else {
5971                         value = "";
5972                         valuelen = 0;
5973                 }
5975                 state = read_property(name, namelen, value, valuelen);
5976         }
5978         if (state != ERR && ferror(pipe))
5979                 state = ERR;
5981         pclose(pipe);
5983         return state;
5987 /*
5988  * Main
5989  */
5991 static void __NORETURN
5992 quit(int sig)
5994         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5995         if (cursed)
5996                 endwin();
5997         exit(0);
6000 static void __NORETURN
6001 die(const char *err, ...)
6003         va_list args;
6005         endwin();
6007         va_start(args, err);
6008         fputs("tig: ", stderr);
6009         vfprintf(stderr, err, args);
6010         fputs("\n", stderr);
6011         va_end(args);
6013         exit(1);
6016 static void
6017 warn(const char *msg, ...)
6019         va_list args;
6021         va_start(args, msg);
6022         fputs("tig warning: ", stderr);
6023         vfprintf(stderr, msg, args);
6024         fputs("\n", stderr);
6025         va_end(args);
6028 int
6029 main(int argc, const char *argv[])
6031         struct view *view;
6032         enum request request;
6033         size_t i;
6035         signal(SIGINT, quit);
6037         if (setlocale(LC_ALL, "")) {
6038                 char *codeset = nl_langinfo(CODESET);
6040                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6041         }
6043         if (load_repo_info() == ERR)
6044                 die("Failed to load repo info.");
6046         if (load_options() == ERR)
6047                 die("Failed to load user config.");
6049         if (load_git_config() == ERR)
6050                 die("Failed to load repo config.");
6052         request = parse_options(argc, argv);
6053         if (request == REQ_NONE)
6054                 return 0;
6056         /* Require a git repository unless when running in pager mode. */
6057         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6058                 die("Not a git repository");
6060         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6061                 opt_utf8 = FALSE;
6063         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6064                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6065                 if (opt_iconv == ICONV_NONE)
6066                         die("Failed to initialize character set conversion");
6067         }
6069         if (load_refs() == ERR)
6070                 die("Failed to load refs.");
6072         foreach_view (view, i)
6073                 view->cmd_env = getenv(view->cmd_env);
6075         init_display();
6077         while (view_driver(display[current_view], request)) {
6078                 int key;
6079                 int i;
6081                 foreach_view (view, i)
6082                         update_view(view);
6083                 view = display[current_view];
6085                 /* Refresh, accept single keystroke of input */
6086                 key = wgetch(status_win);
6088                 /* wgetch() with nodelay() enabled returns ERR when there's no
6089                  * input. */
6090                 if (key == ERR) {
6091                         request = REQ_NONE;
6092                         continue;
6093                 }
6095                 request = get_keybinding(view->keymap, key);
6097                 /* Some low-level request handling. This keeps access to
6098                  * status_win restricted. */
6099                 switch (request) {
6100                 case REQ_PROMPT:
6101                 {
6102                         char *cmd = read_prompt(":");
6104                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6105                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6106                                         request = REQ_VIEW_DIFF;
6107                                 } else {
6108                                         request = REQ_VIEW_PAGER;
6109                                 }
6111                                 /* Always reload^Wrerun commands from the prompt. */
6112                                 open_view(view, request, OPEN_RELOAD);
6113                         }
6115                         request = REQ_NONE;
6116                         break;
6117                 }
6118                 case REQ_SEARCH:
6119                 case REQ_SEARCH_BACK:
6120                 {
6121                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6122                         char *search = read_prompt(prompt);
6124                         if (search)
6125                                 string_ncopy(opt_search, search, strlen(search));
6126                         else
6127                                 request = REQ_NONE;
6128                         break;
6129                 }
6130                 case REQ_SCREEN_RESIZE:
6131                 {
6132                         int height, width;
6134                         getmaxyx(stdscr, height, width);
6136                         /* Resize the status view and let the view driver take
6137                          * care of resizing the displayed views. */
6138                         wresize(status_win, 1, width);
6139                         mvwin(status_win, height - 1, 0);
6140                         wrefresh(status_win);
6141                         break;
6142                 }
6143                 default:
6144                         break;
6145                 }
6146         }
6148         quit(0);
6150         return 0;