Code

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