Code

status: add support for checking out files with unstaged changes
[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);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
75 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x)  (sizeof(x) - 1)
78 #define SIZEOF_STR      1024    /* Default string size. */
79 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
80 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
82 /* Revision graph */
84 #define REVGRAPH_INIT   'I'
85 #define REVGRAPH_MERGE  'M'
86 #define REVGRAPH_BRANCH '+'
87 #define REVGRAPH_COMMIT '*'
88 #define REVGRAPH_BOUND  '^'
90 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
92 /* This color name can be used to refer to the default term colors. */
93 #define COLOR_DEFAULT   (-1)
95 #define ICONV_NONE      ((iconv_t) -1)
96 #ifndef ICONV_CONST
97 #define ICONV_CONST     /* nothing */
98 #endif
100 /* The format and size of the date column in the main view. */
101 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
102 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
104 #define AUTHOR_COLS     20
105 #define ID_COLS         8
107 /* The default interval between line numbers. */
108 #define NUMBER_INTERVAL 5
110 #define TAB_SIZE        8
112 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
114 #define NULL_ID         "0000000000000000000000000000000000000000"
116 #ifndef GIT_CONFIG
117 #define GIT_CONFIG "git config"
118 #endif
120 #define TIG_LS_REMOTE \
121         "git ls-remote . 2>/dev/null"
123 #define TIG_DIFF_CMD \
124         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
126 #define TIG_LOG_CMD     \
127         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
129 #define TIG_MAIN_CMD \
130         "git log --no-color --topo-order --parents --pretty=raw %s 2>/dev/null"
132 #define TIG_TREE_CMD    \
133         "git ls-tree %s %s"
135 #define TIG_BLOB_CMD    \
136         "git cat-file blob %s"
138 /* XXX: Needs to be defined to the empty string. */
139 #define TIG_HELP_CMD    ""
140 #define TIG_PAGER_CMD   ""
141 #define TIG_STATUS_CMD  ""
142 #define TIG_STAGE_CMD   ""
143 #define TIG_BLAME_CMD   ""
145 /* Some ascii-shorthands fitted into the ncurses namespace. */
146 #define KEY_TAB         '\t'
147 #define KEY_RETURN      '\r'
148 #define KEY_ESC         27
151 struct ref {
152         char *name;             /* Ref name; tag or head names are shortened. */
153         char id[SIZEOF_REV];    /* Commit SHA1 ID */
154         unsigned int head:1;    /* Is it the current HEAD? */
155         unsigned int tag:1;     /* Is it a tag? */
156         unsigned int ltag:1;    /* If so, is the tag local? */
157         unsigned int remote:1;  /* Is it a remote ref? */
158         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
159         unsigned int next:1;    /* For ref lists: are there more refs? */
160 };
162 static struct ref **get_refs(char *id);
164 struct int_map {
165         const char *name;
166         int namelen;
167         int value;
168 };
170 static int
171 set_from_int_map(struct int_map *map, size_t map_size,
172                  int *value, const char *name, int namelen)
175         int i;
177         for (i = 0; i < map_size; i++)
178                 if (namelen == map[i].namelen &&
179                     !strncasecmp(name, map[i].name, namelen)) {
180                         *value = map[i].value;
181                         return OK;
182                 }
184         return ERR;
188 /*
189  * String helpers
190  */
192 static inline void
193 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
195         if (srclen > dstlen - 1)
196                 srclen = dstlen - 1;
198         strncpy(dst, src, srclen);
199         dst[srclen] = 0;
202 /* Shorthands for safely copying into a fixed buffer. */
204 #define string_copy(dst, src) \
205         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
207 #define string_ncopy(dst, src, srclen) \
208         string_ncopy_do(dst, sizeof(dst), src, srclen)
210 #define string_copy_rev(dst, src) \
211         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
213 #define string_add(dst, from, src) \
214         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
216 static char *
217 chomp_string(char *name)
219         int namelen;
221         while (isspace(*name))
222                 name++;
224         namelen = strlen(name) - 1;
225         while (namelen > 0 && isspace(name[namelen]))
226                 name[namelen--] = 0;
228         return name;
231 static bool
232 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
234         va_list args;
235         size_t pos = bufpos ? *bufpos : 0;
237         va_start(args, fmt);
238         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
239         va_end(args);
241         if (bufpos)
242                 *bufpos = pos;
244         return pos >= bufsize ? FALSE : TRUE;
247 #define string_format(buf, fmt, args...) \
248         string_nformat(buf, sizeof(buf), NULL, fmt, args)
250 #define string_format_from(buf, from, fmt, args...) \
251         string_nformat(buf, sizeof(buf), from, fmt, args)
253 static int
254 string_enum_compare(const char *str1, const char *str2, int len)
256         size_t i;
258 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
260         /* Diff-Header == DIFF_HEADER */
261         for (i = 0; i < len; i++) {
262                 if (toupper(str1[i]) == toupper(str2[i]))
263                         continue;
265                 if (string_enum_sep(str1[i]) &&
266                     string_enum_sep(str2[i]))
267                         continue;
269                 return str1[i] - str2[i];
270         }
272         return 0;
275 /* Shell quoting
276  *
277  * NOTE: The following is a slightly modified copy of the git project's shell
278  * quoting routines found in the quote.c file.
279  *
280  * Help to copy the thing properly quoted for the shell safety.  any single
281  * quote is replaced with '\'', any exclamation point is replaced with '\!',
282  * and the whole thing is enclosed in a
283  *
284  * E.g.
285  *  original     sq_quote     result
286  *  name     ==> name      ==> 'name'
287  *  a b      ==> a b       ==> 'a b'
288  *  a'b      ==> a'\''b    ==> 'a'\''b'
289  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
290  */
292 static size_t
293 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
295         char c;
297 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
299         BUFPUT('\'');
300         while ((c = *src++)) {
301                 if (c == '\'' || c == '!') {
302                         BUFPUT('\'');
303                         BUFPUT('\\');
304                         BUFPUT(c);
305                         BUFPUT('\'');
306                 } else {
307                         BUFPUT(c);
308                 }
309         }
310         BUFPUT('\'');
312         if (bufsize < SIZEOF_STR)
313                 buf[bufsize] = 0;
315         return bufsize;
319 /*
320  * User requests
321  */
323 #define REQ_INFO \
324         /* XXX: Keep the view request first and in sync with views[]. */ \
325         REQ_GROUP("View switching") \
326         REQ_(VIEW_MAIN,         "Show main view"), \
327         REQ_(VIEW_DIFF,         "Show diff view"), \
328         REQ_(VIEW_LOG,          "Show log view"), \
329         REQ_(VIEW_TREE,         "Show tree view"), \
330         REQ_(VIEW_BLOB,         "Show blob view"), \
331         REQ_(VIEW_BLAME,        "Show blame view"), \
332         REQ_(VIEW_HELP,         "Show help page"), \
333         REQ_(VIEW_PAGER,        "Show pager view"), \
334         REQ_(VIEW_STATUS,       "Show status view"), \
335         REQ_(VIEW_STAGE,        "Show stage view"), \
336         \
337         REQ_GROUP("View manipulation") \
338         REQ_(ENTER,             "Enter current line and scroll"), \
339         REQ_(NEXT,              "Move to next"), \
340         REQ_(PREVIOUS,          "Move to previous"), \
341         REQ_(VIEW_NEXT,         "Move focus to next view"), \
342         REQ_(REFRESH,           "Reload and refresh"), \
343         REQ_(MAXIMIZE,          "Maximize the current view"), \
344         REQ_(VIEW_CLOSE,        "Close the current view"), \
345         REQ_(QUIT,              "Close all views and quit"), \
346         \
347         REQ_GROUP("Cursor navigation") \
348         REQ_(MOVE_UP,           "Move cursor one line up"), \
349         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
350         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
351         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
352         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
353         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
354         \
355         REQ_GROUP("Scrolling") \
356         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
357         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
358         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
359         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
360         \
361         REQ_GROUP("Searching") \
362         REQ_(SEARCH,            "Search the view"), \
363         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
364         REQ_(FIND_NEXT,         "Find next search match"), \
365         REQ_(FIND_PREV,         "Find previous search match"), \
366         \
367         REQ_GROUP("Misc") \
368         REQ_(PROMPT,            "Bring up the prompt"), \
369         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
370         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
371         REQ_(SHOW_VERSION,      "Show version information"), \
372         REQ_(STOP_LOADING,      "Stop all loading views"), \
373         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
374         REQ_(TOGGLE_DATE,       "Toggle date display"), \
375         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
376         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
377         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
378         REQ_(STATUS_UPDATE,     "Update file status"), \
379         REQ_(STATUS_CHECKOUT,   "Checkout file"), \
380         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
381         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
382         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
383         REQ_(EDIT,              "Open in editor"), \
384         REQ_(NONE,              "Do nothing")
387 /* User action requests. */
388 enum request {
389 #define REQ_GROUP(help)
390 #define REQ_(req, help) REQ_##req
392         /* Offset all requests to avoid conflicts with ncurses getch values. */
393         REQ_OFFSET = KEY_MAX + 1,
394         REQ_INFO
396 #undef  REQ_GROUP
397 #undef  REQ_
398 };
400 struct request_info {
401         enum request request;
402         char *name;
403         int namelen;
404         char *help;
405 };
407 static struct request_info req_info[] = {
408 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
409 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
410         REQ_INFO
411 #undef  REQ_GROUP
412 #undef  REQ_
413 };
415 static enum request
416 get_request(const char *name)
418         int namelen = strlen(name);
419         int i;
421         for (i = 0; i < ARRAY_SIZE(req_info); i++)
422                 if (req_info[i].namelen == namelen &&
423                     !string_enum_compare(req_info[i].name, name, namelen))
424                         return req_info[i].request;
426         return REQ_NONE;
430 /*
431  * Options
432  */
434 static const char usage[] =
435 "tig " TIG_VERSION " (" __DATE__ ")\n"
436 "\n"
437 "Usage: tig        [options] [revs] [--] [paths]\n"
438 "   or: tig show   [options] [revs] [--] [paths]\n"
439 "   or: tig blame  [rev] path\n"
440 "   or: tig status\n"
441 "   or: tig <      [git command output]\n"
442 "\n"
443 "Options:\n"
444 "  -v, --version   Show version and exit\n"
445 "  -h, --help      Show help message and exit";
447 /* Option and state variables. */
448 static bool opt_date                    = TRUE;
449 static bool opt_author                  = TRUE;
450 static bool opt_line_number             = FALSE;
451 static bool opt_line_graphics           = TRUE;
452 static bool opt_rev_graph               = FALSE;
453 static bool opt_show_refs               = TRUE;
454 static int opt_num_interval             = NUMBER_INTERVAL;
455 static int opt_tab_size                 = TAB_SIZE;
456 static int opt_author_cols              = AUTHOR_COLS-1;
457 static char opt_cmd[SIZEOF_STR]         = "";
458 static char opt_path[SIZEOF_STR]        = "";
459 static char opt_file[SIZEOF_STR]        = "";
460 static char opt_ref[SIZEOF_REF]         = "";
461 static char opt_head[SIZEOF_REF]        = "";
462 static char opt_remote[SIZEOF_REF]      = "";
463 static bool opt_no_head                 = TRUE;
464 static FILE *opt_pipe                   = NULL;
465 static char opt_encoding[20]            = "UTF-8";
466 static bool opt_utf8                    = TRUE;
467 static char opt_codeset[20]             = "UTF-8";
468 static iconv_t opt_iconv                = ICONV_NONE;
469 static char opt_search[SIZEOF_STR]      = "";
470 static char opt_cdup[SIZEOF_STR]        = "";
471 static char opt_git_dir[SIZEOF_STR]     = "";
472 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
473 static char opt_editor[SIZEOF_STR]      = "";
475 static enum request
476 parse_options(int argc, char *argv[])
478         enum request request = REQ_VIEW_MAIN;
479         size_t buf_size;
480         char *subcommand;
481         bool seen_dashdash = FALSE;
482         int i;
484         if (!isatty(STDIN_FILENO)) {
485                 opt_pipe = stdin;
486                 return REQ_VIEW_PAGER;
487         }
489         if (argc <= 1)
490                 return REQ_VIEW_MAIN;
492         subcommand = argv[1];
493         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
494                 if (!strcmp(subcommand, "-S"))
495                         warn("`-S' has been deprecated; use `tig status' instead");
496                 if (argc > 2)
497                         warn("ignoring arguments after `%s'", subcommand);
498                 return REQ_VIEW_STATUS;
500         } else if (!strcmp(subcommand, "blame")) {
501                 if (argc <= 2 || argc > 4)
502                         die("invalid number of options to blame\n\n%s", usage);
504                 i = 2;
505                 if (argc == 4) {
506                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
507                         i++;
508                 }
510                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
511                 return REQ_VIEW_BLAME;
513         } else if (!strcmp(subcommand, "show")) {
514                 request = REQ_VIEW_DIFF;
516         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
517                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
518                 warn("`tig %s' has been deprecated", subcommand);
520         } else {
521                 subcommand = NULL;
522         }
524         if (!subcommand)
525                 /* XXX: This is vulnerable to the user overriding
526                  * options required for the main view parser. */
527                 string_copy(opt_cmd, "git log --no-color --pretty=raw --parents");
528         else
529                 string_format(opt_cmd, "git %s", subcommand);
531         buf_size = strlen(opt_cmd);
533         for (i = 1 + !!subcommand; i < argc; i++) {
534                 char *opt = argv[i];
536                 if (seen_dashdash || !strcmp(opt, "--")) {
537                         seen_dashdash = TRUE;
539                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
540                         printf("tig version %s\n", TIG_VERSION);
541                         return REQ_NONE;
543                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
544                         printf("%s\n", usage);
545                         return REQ_NONE;
546                 }
548                 opt_cmd[buf_size++] = ' ';
549                 buf_size = sq_quote(opt_cmd, buf_size, opt);
550                 if (buf_size >= sizeof(opt_cmd))
551                         die("command too long");
552         }
554         opt_cmd[buf_size] = 0;
556         return request;
560 /*
561  * Line-oriented content detection.
562  */
564 #define LINE_INFO \
565 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
566 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
568 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
569 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
570 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
571 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
572 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
573 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
574 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
575 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
579 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
580 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
581 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
582 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
583 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
586 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
587 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
588 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
593 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
594 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
595 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
596 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
598 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
599 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
600 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
601 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
602 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
603 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
604 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
606 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
607 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
608 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
609 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
611 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
612 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
613 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
615 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
616 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
617 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
618 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
620 enum line_type {
621 #define LINE(type, line, fg, bg, attr) \
622         LINE_##type
623         LINE_INFO,
624         LINE_NONE
625 #undef  LINE
626 };
628 struct line_info {
629         const char *name;       /* Option name. */
630         int namelen;            /* Size of option name. */
631         const char *line;       /* The start of line to match. */
632         int linelen;            /* Size of string to match. */
633         int fg, bg, attr;       /* Color and text attributes for the lines. */
634 };
636 static struct line_info line_info[] = {
637 #define LINE(type, line, fg, bg, attr) \
638         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
639         LINE_INFO
640 #undef  LINE
641 };
643 static enum line_type
644 get_line_type(char *line)
646         int linelen = strlen(line);
647         enum line_type type;
649         for (type = 0; type < ARRAY_SIZE(line_info); type++)
650                 /* Case insensitive search matches Signed-off-by lines better. */
651                 if (linelen >= line_info[type].linelen &&
652                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
653                         return type;
655         return LINE_DEFAULT;
658 static inline int
659 get_line_attr(enum line_type type)
661         assert(type < ARRAY_SIZE(line_info));
662         return COLOR_PAIR(type) | line_info[type].attr;
665 static struct line_info *
666 get_line_info(char *name)
668         size_t namelen = strlen(name);
669         enum line_type type;
671         for (type = 0; type < ARRAY_SIZE(line_info); type++)
672                 if (namelen == line_info[type].namelen &&
673                     !string_enum_compare(line_info[type].name, name, namelen))
674                         return &line_info[type];
676         return NULL;
679 static void
680 init_colors(void)
682         int default_bg = line_info[LINE_DEFAULT].bg;
683         int default_fg = line_info[LINE_DEFAULT].fg;
684         enum line_type type;
686         start_color();
688         if (assume_default_colors(default_fg, default_bg) == ERR) {
689                 default_bg = COLOR_BLACK;
690                 default_fg = COLOR_WHITE;
691         }
693         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
694                 struct line_info *info = &line_info[type];
695                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
696                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
698                 init_pair(type, fg, bg);
699         }
702 struct line {
703         enum line_type type;
705         /* State flags */
706         unsigned int selected:1;
707         unsigned int dirty:1;
709         void *data;             /* User data */
710 };
713 /*
714  * Keys
715  */
717 struct keybinding {
718         int alias;
719         enum request request;
720         struct keybinding *next;
721 };
723 static struct keybinding default_keybindings[] = {
724         /* View switching */
725         { 'm',          REQ_VIEW_MAIN },
726         { 'd',          REQ_VIEW_DIFF },
727         { 'l',          REQ_VIEW_LOG },
728         { 't',          REQ_VIEW_TREE },
729         { 'f',          REQ_VIEW_BLOB },
730         { 'B',          REQ_VIEW_BLAME },
731         { 'p',          REQ_VIEW_PAGER },
732         { 'h',          REQ_VIEW_HELP },
733         { 'S',          REQ_VIEW_STATUS },
734         { 'c',          REQ_VIEW_STAGE },
736         /* View manipulation */
737         { 'q',          REQ_VIEW_CLOSE },
738         { KEY_TAB,      REQ_VIEW_NEXT },
739         { KEY_RETURN,   REQ_ENTER },
740         { KEY_UP,       REQ_PREVIOUS },
741         { KEY_DOWN,     REQ_NEXT },
742         { 'R',          REQ_REFRESH },
743         { KEY_F(5),     REQ_REFRESH },
744         { 'O',          REQ_MAXIMIZE },
746         /* Cursor navigation */
747         { 'k',          REQ_MOVE_UP },
748         { 'j',          REQ_MOVE_DOWN },
749         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
750         { KEY_END,      REQ_MOVE_LAST_LINE },
751         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
752         { ' ',          REQ_MOVE_PAGE_DOWN },
753         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
754         { 'b',          REQ_MOVE_PAGE_UP },
755         { '-',          REQ_MOVE_PAGE_UP },
757         /* Scrolling */
758         { KEY_IC,       REQ_SCROLL_LINE_UP },
759         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
760         { 'w',          REQ_SCROLL_PAGE_UP },
761         { 's',          REQ_SCROLL_PAGE_DOWN },
763         /* Searching */
764         { '/',          REQ_SEARCH },
765         { '?',          REQ_SEARCH_BACK },
766         { 'n',          REQ_FIND_NEXT },
767         { 'N',          REQ_FIND_PREV },
769         /* Misc */
770         { 'Q',          REQ_QUIT },
771         { 'z',          REQ_STOP_LOADING },
772         { 'v',          REQ_SHOW_VERSION },
773         { 'r',          REQ_SCREEN_REDRAW },
774         { '.',          REQ_TOGGLE_LINENO },
775         { 'D',          REQ_TOGGLE_DATE },
776         { 'A',          REQ_TOGGLE_AUTHOR },
777         { 'g',          REQ_TOGGLE_REV_GRAPH },
778         { 'F',          REQ_TOGGLE_REFS },
779         { ':',          REQ_PROMPT },
780         { 'u',          REQ_STATUS_UPDATE },
781         { '!',          REQ_STATUS_CHECKOUT },
782         { 'M',          REQ_STATUS_MERGE },
783         { '@',          REQ_STAGE_NEXT },
784         { ',',          REQ_TREE_PARENT },
785         { 'e',          REQ_EDIT },
787         /* Using the ncurses SIGWINCH handler. */
788         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
789 };
791 #define KEYMAP_INFO \
792         KEYMAP_(GENERIC), \
793         KEYMAP_(MAIN), \
794         KEYMAP_(DIFF), \
795         KEYMAP_(LOG), \
796         KEYMAP_(TREE), \
797         KEYMAP_(BLOB), \
798         KEYMAP_(BLAME), \
799         KEYMAP_(PAGER), \
800         KEYMAP_(HELP), \
801         KEYMAP_(STATUS), \
802         KEYMAP_(STAGE)
804 enum keymap {
805 #define KEYMAP_(name) KEYMAP_##name
806         KEYMAP_INFO
807 #undef  KEYMAP_
808 };
810 static struct int_map keymap_table[] = {
811 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
812         KEYMAP_INFO
813 #undef  KEYMAP_
814 };
816 #define set_keymap(map, name) \
817         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
819 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
821 static void
822 add_keybinding(enum keymap keymap, enum request request, int key)
824         struct keybinding *keybinding;
826         keybinding = calloc(1, sizeof(*keybinding));
827         if (!keybinding)
828                 die("Failed to allocate keybinding");
830         keybinding->alias = key;
831         keybinding->request = request;
832         keybinding->next = keybindings[keymap];
833         keybindings[keymap] = keybinding;
836 /* Looks for a key binding first in the given map, then in the generic map, and
837  * lastly in the default keybindings. */
838 static enum request
839 get_keybinding(enum keymap keymap, int key)
841         struct keybinding *kbd;
842         int i;
844         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
845                 if (kbd->alias == key)
846                         return kbd->request;
848         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
849                 if (kbd->alias == key)
850                         return kbd->request;
852         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
853                 if (default_keybindings[i].alias == key)
854                         return default_keybindings[i].request;
856         return (enum request) key;
860 struct key {
861         char *name;
862         int value;
863 };
865 static struct key key_table[] = {
866         { "Enter",      KEY_RETURN },
867         { "Space",      ' ' },
868         { "Backspace",  KEY_BACKSPACE },
869         { "Tab",        KEY_TAB },
870         { "Escape",     KEY_ESC },
871         { "Left",       KEY_LEFT },
872         { "Right",      KEY_RIGHT },
873         { "Up",         KEY_UP },
874         { "Down",       KEY_DOWN },
875         { "Insert",     KEY_IC },
876         { "Delete",     KEY_DC },
877         { "Hash",       '#' },
878         { "Home",       KEY_HOME },
879         { "End",        KEY_END },
880         { "PageUp",     KEY_PPAGE },
881         { "PageDown",   KEY_NPAGE },
882         { "F1",         KEY_F(1) },
883         { "F2",         KEY_F(2) },
884         { "F3",         KEY_F(3) },
885         { "F4",         KEY_F(4) },
886         { "F5",         KEY_F(5) },
887         { "F6",         KEY_F(6) },
888         { "F7",         KEY_F(7) },
889         { "F8",         KEY_F(8) },
890         { "F9",         KEY_F(9) },
891         { "F10",        KEY_F(10) },
892         { "F11",        KEY_F(11) },
893         { "F12",        KEY_F(12) },
894 };
896 static int
897 get_key_value(const char *name)
899         int i;
901         for (i = 0; i < ARRAY_SIZE(key_table); i++)
902                 if (!strcasecmp(key_table[i].name, name))
903                         return key_table[i].value;
905         if (strlen(name) == 1 && isprint(*name))
906                 return (int) *name;
908         return ERR;
911 static char *
912 get_key_name(int key_value)
914         static char key_char[] = "'X'";
915         char *seq = NULL;
916         int key;
918         for (key = 0; key < ARRAY_SIZE(key_table); key++)
919                 if (key_table[key].value == key_value)
920                         seq = key_table[key].name;
922         if (seq == NULL &&
923             key_value < 127 &&
924             isprint(key_value)) {
925                 key_char[1] = (char) key_value;
926                 seq = key_char;
927         }
929         return seq ? seq : "'?'";
932 static char *
933 get_key(enum request request)
935         static char buf[BUFSIZ];
936         size_t pos = 0;
937         char *sep = "";
938         int i;
940         buf[pos] = 0;
942         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
943                 struct keybinding *keybinding = &default_keybindings[i];
945                 if (keybinding->request != request)
946                         continue;
948                 if (!string_format_from(buf, &pos, "%s%s", sep,
949                                         get_key_name(keybinding->alias)))
950                         return "Too many keybindings!";
951                 sep = ", ";
952         }
954         return buf;
957 struct run_request {
958         enum keymap keymap;
959         int key;
960         char cmd[SIZEOF_STR];
961 };
963 static struct run_request *run_request;
964 static size_t run_requests;
966 static enum request
967 add_run_request(enum keymap keymap, int key, int argc, char **argv)
969         struct run_request *req;
970         char cmd[SIZEOF_STR];
971         size_t bufpos;
973         for (bufpos = 0; argc > 0; argc--, argv++)
974                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
975                         return REQ_NONE;
977         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
978         if (!req)
979                 return REQ_NONE;
981         run_request = req;
982         req = &run_request[run_requests++];
983         string_copy(req->cmd, cmd);
984         req->keymap = keymap;
985         req->key = key;
987         return REQ_NONE + run_requests;
990 static struct run_request *
991 get_run_request(enum request request)
993         if (request <= REQ_NONE)
994                 return NULL;
995         return &run_request[request - REQ_NONE - 1];
998 static void
999 add_builtin_run_requests(void)
1001         struct {
1002                 enum keymap keymap;
1003                 int key;
1004                 char *argv[1];
1005         } reqs[] = {
1006                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1007                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1008         };
1009         int i;
1011         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1012                 enum request req;
1014                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1015                 if (req != REQ_NONE)
1016                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1017         }
1020 /*
1021  * User config file handling.
1022  */
1024 static struct int_map color_map[] = {
1025 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1026         COLOR_MAP(DEFAULT),
1027         COLOR_MAP(BLACK),
1028         COLOR_MAP(BLUE),
1029         COLOR_MAP(CYAN),
1030         COLOR_MAP(GREEN),
1031         COLOR_MAP(MAGENTA),
1032         COLOR_MAP(RED),
1033         COLOR_MAP(WHITE),
1034         COLOR_MAP(YELLOW),
1035 };
1037 #define set_color(color, name) \
1038         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1040 static struct int_map attr_map[] = {
1041 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1042         ATTR_MAP(NORMAL),
1043         ATTR_MAP(BLINK),
1044         ATTR_MAP(BOLD),
1045         ATTR_MAP(DIM),
1046         ATTR_MAP(REVERSE),
1047         ATTR_MAP(STANDOUT),
1048         ATTR_MAP(UNDERLINE),
1049 };
1051 #define set_attribute(attr, name) \
1052         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1054 static int   config_lineno;
1055 static bool  config_errors;
1056 static char *config_msg;
1058 /* Wants: object fgcolor bgcolor [attr] */
1059 static int
1060 option_color_command(int argc, char *argv[])
1062         struct line_info *info;
1064         if (argc != 3 && argc != 4) {
1065                 config_msg = "Wrong number of arguments given to color command";
1066                 return ERR;
1067         }
1069         info = get_line_info(argv[0]);
1070         if (!info) {
1071                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1072                         info = get_line_info("delimiter");
1074                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1075                         info = get_line_info("date");
1077                 } else {
1078                         config_msg = "Unknown color name";
1079                         return ERR;
1080                 }
1081         }
1083         if (set_color(&info->fg, argv[1]) == ERR ||
1084             set_color(&info->bg, argv[2]) == ERR) {
1085                 config_msg = "Unknown color";
1086                 return ERR;
1087         }
1089         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1090                 config_msg = "Unknown attribute";
1091                 return ERR;
1092         }
1094         return OK;
1097 static bool parse_bool(const char *s)
1099         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1100                 !strcmp(s, "yes")) ? TRUE : FALSE;
1103 static int
1104 parse_int(const char *s, int default_value, int min, int max)
1106         int value = atoi(s);
1108         return (value < min || value > max) ? default_value : value;
1111 /* Wants: name = value */
1112 static int
1113 option_set_command(int argc, char *argv[])
1115         if (argc != 3) {
1116                 config_msg = "Wrong number of arguments given to set command";
1117                 return ERR;
1118         }
1120         if (strcmp(argv[1], "=")) {
1121                 config_msg = "No value assigned";
1122                 return ERR;
1123         }
1125         if (!strcmp(argv[0], "show-author")) {
1126                 opt_author = parse_bool(argv[2]);
1127                 return OK;
1128         }
1130         if (!strcmp(argv[0], "show-date")) {
1131                 opt_date = parse_bool(argv[2]);
1132                 return OK;
1133         }
1135         if (!strcmp(argv[0], "show-rev-graph")) {
1136                 opt_rev_graph = parse_bool(argv[2]);
1137                 return OK;
1138         }
1140         if (!strcmp(argv[0], "show-refs")) {
1141                 opt_show_refs = parse_bool(argv[2]);
1142                 return OK;
1143         }
1145         if (!strcmp(argv[0], "show-line-numbers")) {
1146                 opt_line_number = parse_bool(argv[2]);
1147                 return OK;
1148         }
1150         if (!strcmp(argv[0], "line-graphics")) {
1151                 opt_line_graphics = parse_bool(argv[2]);
1152                 return OK;
1153         }
1155         if (!strcmp(argv[0], "line-number-interval")) {
1156                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1157                 return OK;
1158         }
1160         if (!strcmp(argv[0], "author-width")) {
1161                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1162                 return OK;
1163         }
1165         if (!strcmp(argv[0], "tab-size")) {
1166                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1167                 return OK;
1168         }
1170         if (!strcmp(argv[0], "commit-encoding")) {
1171                 char *arg = argv[2];
1172                 int delimiter = *arg;
1173                 int i;
1175                 switch (delimiter) {
1176                 case '"':
1177                 case '\'':
1178                         for (arg++, i = 0; arg[i]; i++)
1179                                 if (arg[i] == delimiter) {
1180                                         arg[i] = 0;
1181                                         break;
1182                                 }
1183                 default:
1184                         string_ncopy(opt_encoding, arg, strlen(arg));
1185                         return OK;
1186                 }
1187         }
1189         config_msg = "Unknown variable name";
1190         return ERR;
1193 /* Wants: mode request key */
1194 static int
1195 option_bind_command(int argc, char *argv[])
1197         enum request request;
1198         int keymap;
1199         int key;
1201         if (argc < 3) {
1202                 config_msg = "Wrong number of arguments given to bind command";
1203                 return ERR;
1204         }
1206         if (set_keymap(&keymap, argv[0]) == ERR) {
1207                 config_msg = "Unknown key map";
1208                 return ERR;
1209         }
1211         key = get_key_value(argv[1]);
1212         if (key == ERR) {
1213                 config_msg = "Unknown key";
1214                 return ERR;
1215         }
1217         request = get_request(argv[2]);
1218         if (request == REQ_NONE) {
1219                 const char *obsolete[] = { "cherry-pick" };
1220                 size_t namelen = strlen(argv[2]);
1221                 int i;
1223                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1224                         if (namelen == strlen(obsolete[i]) &&
1225                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1226                                 config_msg = "Obsolete request name";
1227                                 return ERR;
1228                         }
1229                 }
1230         }
1231         if (request == REQ_NONE && *argv[2]++ == '!')
1232                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1233         if (request == REQ_NONE) {
1234                 config_msg = "Unknown request name";
1235                 return ERR;
1236         }
1238         add_keybinding(keymap, request, key);
1240         return OK;
1243 static int
1244 set_option(char *opt, char *value)
1246         char *argv[16];
1247         int valuelen;
1248         int argc = 0;
1250         /* Tokenize */
1251         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1252                 argv[argc++] = value;
1253                 value += valuelen;
1255                 /* Nothing more to tokenize or last available token. */
1256                 if (!*value || argc >= ARRAY_SIZE(argv))
1257                         break;
1259                 *value++ = 0;
1260                 while (isspace(*value))
1261                         value++;
1262         }
1264         if (!strcmp(opt, "color"))
1265                 return option_color_command(argc, argv);
1267         if (!strcmp(opt, "set"))
1268                 return option_set_command(argc, argv);
1270         if (!strcmp(opt, "bind"))
1271                 return option_bind_command(argc, argv);
1273         config_msg = "Unknown option command";
1274         return ERR;
1277 static int
1278 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1280         int status = OK;
1282         config_lineno++;
1283         config_msg = "Internal error";
1285         /* Check for comment markers, since read_properties() will
1286          * only ensure opt and value are split at first " \t". */
1287         optlen = strcspn(opt, "#");
1288         if (optlen == 0)
1289                 return OK;
1291         if (opt[optlen] != 0) {
1292                 config_msg = "No option value";
1293                 status = ERR;
1295         }  else {
1296                 /* Look for comment endings in the value. */
1297                 size_t len = strcspn(value, "#");
1299                 if (len < valuelen) {
1300                         valuelen = len;
1301                         value[valuelen] = 0;
1302                 }
1304                 status = set_option(opt, value);
1305         }
1307         if (status == ERR) {
1308                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1309                         config_lineno, (int) optlen, opt, config_msg);
1310                 config_errors = TRUE;
1311         }
1313         /* Always keep going if errors are encountered. */
1314         return OK;
1317 static void
1318 load_option_file(const char *path)
1320         FILE *file;
1322         /* It's ok that the file doesn't exist. */
1323         file = fopen(path, "r");
1324         if (!file)
1325                 return;
1327         config_lineno = 0;
1328         config_errors = FALSE;
1330         if (read_properties(file, " \t", read_option) == ERR ||
1331             config_errors == TRUE)
1332                 fprintf(stderr, "Errors while loading %s.\n", path);
1335 static int
1336 load_options(void)
1338         char *home = getenv("HOME");
1339         char *tigrc_user = getenv("TIGRC_USER");
1340         char *tigrc_system = getenv("TIGRC_SYSTEM");
1341         char buf[SIZEOF_STR];
1343         add_builtin_run_requests();
1345         if (!tigrc_system) {
1346                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1347                         return ERR;
1348                 tigrc_system = buf;
1349         }
1350         load_option_file(tigrc_system);
1352         if (!tigrc_user) {
1353                 if (!home || !string_format(buf, "%s/.tigrc", home))
1354                         return ERR;
1355                 tigrc_user = buf;
1356         }
1357         load_option_file(tigrc_user);
1359         return OK;
1363 /*
1364  * The viewer
1365  */
1367 struct view;
1368 struct view_ops;
1370 /* The display array of active views and the index of the current view. */
1371 static struct view *display[2];
1372 static unsigned int current_view;
1374 /* Reading from the prompt? */
1375 static bool input_mode = FALSE;
1377 #define foreach_displayed_view(view, i) \
1378         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1380 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1382 /* Current head and commit ID */
1383 static char ref_blob[SIZEOF_REF]        = "";
1384 static char ref_commit[SIZEOF_REF]      = "HEAD";
1385 static char ref_head[SIZEOF_REF]        = "HEAD";
1387 struct view {
1388         const char *name;       /* View name */
1389         const char *cmd_fmt;    /* Default command line format */
1390         const char *cmd_env;    /* Command line set via environment */
1391         const char *id;         /* Points to either of ref_{head,commit,blob} */
1393         struct view_ops *ops;   /* View operations */
1395         enum keymap keymap;     /* What keymap does this view have */
1396         bool git_dir;           /* Whether the view requires a git directory. */
1398         char cmd[SIZEOF_STR];   /* Command buffer */
1399         char ref[SIZEOF_REF];   /* Hovered commit reference */
1400         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1402         int height, width;      /* The width and height of the main window */
1403         WINDOW *win;            /* The main window */
1404         WINDOW *title;          /* The title window living below the main window */
1406         /* Navigation */
1407         unsigned long offset;   /* Offset of the window top */
1408         unsigned long lineno;   /* Current line number */
1410         /* Searching */
1411         char grep[SIZEOF_STR];  /* Search string */
1412         regex_t *regex;         /* Pre-compiled regex */
1414         /* If non-NULL, points to the view that opened this view. If this view
1415          * is closed tig will switch back to the parent view. */
1416         struct view *parent;
1418         /* Buffering */
1419         size_t lines;           /* Total number of lines */
1420         struct line *line;      /* Line index */
1421         size_t line_alloc;      /* Total number of allocated lines */
1422         size_t line_size;       /* Total number of used lines */
1423         unsigned int digits;    /* Number of digits in the lines member. */
1425         /* Drawing */
1426         struct line *curline;   /* Line currently being drawn. */
1427         enum line_type curtype; /* Attribute currently used for drawing. */
1428         unsigned long col;      /* Column when drawing. */
1430         /* Loading */
1431         FILE *pipe;
1432         time_t start_time;
1433 };
1435 struct view_ops {
1436         /* What type of content being displayed. Used in the title bar. */
1437         const char *type;
1438         /* Open and reads in all view content. */
1439         bool (*open)(struct view *view);
1440         /* Read one line; updates view->line. */
1441         bool (*read)(struct view *view, char *data);
1442         /* Draw one line; @lineno must be < view->height. */
1443         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1444         /* Depending on view handle a special requests. */
1445         enum request (*request)(struct view *view, enum request request, struct line *line);
1446         /* Search for regex in a line. */
1447         bool (*grep)(struct view *view, struct line *line);
1448         /* Select line */
1449         void (*select)(struct view *view, struct line *line);
1450 };
1452 static struct view_ops pager_ops;
1453 static struct view_ops main_ops;
1454 static struct view_ops tree_ops;
1455 static struct view_ops blob_ops;
1456 static struct view_ops blame_ops;
1457 static struct view_ops help_ops;
1458 static struct view_ops status_ops;
1459 static struct view_ops stage_ops;
1461 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1462         { name, cmd, #env, ref, ops, map, git }
1464 #define VIEW_(id, name, ops, git, ref) \
1465         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1468 static struct view views[] = {
1469         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1470         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1471         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1472         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1473         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1474         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1475         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1476         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1477         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1478         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1479 };
1481 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1482 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1484 #define foreach_view(view, i) \
1485         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1487 #define view_is_displayed(view) \
1488         (view == display[0] || view == display[1])
1491 enum line_graphic {
1492         LINE_GRAPHIC_VLINE
1493 };
1495 static int line_graphics[] = {
1496         /* LINE_GRAPHIC_VLINE: */ '|'
1497 };
1499 static inline void
1500 set_view_attr(struct view *view, enum line_type type)
1502         if (!view->curline->selected && view->curtype != type) {
1503                 wattrset(view->win, get_line_attr(type));
1504                 wchgat(view->win, -1, 0, type, NULL);
1505                 view->curtype = type;
1506         }
1509 static int
1510 draw_chars(struct view *view, enum line_type type, const char *string,
1511            int max_len, bool use_tilde)
1513         int len = 0;
1514         int col = 0;
1515         int trimmed = FALSE;
1517         if (max_len <= 0)
1518                 return 0;
1520         if (opt_utf8) {
1521                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1522         } else {
1523                 col = len = strlen(string);
1524                 if (len > max_len) {
1525                         if (use_tilde) {
1526                                 max_len -= 1;
1527                         }
1528                         col = len = max_len;
1529                         trimmed = TRUE;
1530                 }
1531         }
1533         set_view_attr(view, type);
1534         waddnstr(view->win, string, len);
1535         if (trimmed && use_tilde) {
1536                 set_view_attr(view, LINE_DELIMITER);
1537                 waddch(view->win, '~');
1538                 col++;
1539         }
1541         return col;
1544 static int
1545 draw_space(struct view *view, enum line_type type, int max, int spaces)
1547         static char space[] = "                    ";
1548         int col = 0;
1550         spaces = MIN(max, spaces);
1552         while (spaces > 0) {
1553                 int len = MIN(spaces, sizeof(space) - 1);
1555                 col += draw_chars(view, type, space, spaces, FALSE);
1556                 spaces -= len;
1557         }
1559         return col;
1562 static bool
1563 draw_lineno(struct view *view, unsigned int lineno)
1565         char number[10];
1566         int digits3 = view->digits < 3 ? 3 : view->digits;
1567         int max_number = MIN(digits3, STRING_SIZE(number));
1568         int max = view->width - view->col;
1569         int col;
1571         if (max < max_number)
1572                 max_number = max;
1574         lineno += view->offset + 1;
1575         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1576                 static char fmt[] = "%1ld";
1578                 if (view->digits <= 9)
1579                         fmt[1] = '0' + digits3;
1581                 if (!string_format(number, fmt, lineno))
1582                         number[0] = 0;
1583                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1584         } else {
1585                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1586         }
1588         if (col < max) {
1589                 set_view_attr(view, LINE_DEFAULT);
1590                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1591                 col++;
1592         }
1594         if (col < max)
1595                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1596         view->col += col;
1598         return view->width - view->col <= 0;
1601 static bool
1602 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1604         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1605         return view->width - view->col <= 0;
1608 static bool
1609 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1611         int max = view->width - view->col;
1612         int i;
1614         if (max < size)
1615                 size = max;
1617         set_view_attr(view, type);
1618         /* Using waddch() instead of waddnstr() ensures that
1619          * they'll be rendered correctly for the cursor line. */
1620         for (i = 0; i < size; i++)
1621                 waddch(view->win, graphic[i]);
1623         view->col += size;
1624         if (size < max) {
1625                 waddch(view->win, ' ');
1626                 view->col++;
1627         }
1629         return view->width - view->col <= 0;
1632 static bool
1633 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1635         int max = MIN(view->width - view->col, len);
1636         int col;
1638         if (text)
1639                 col = draw_chars(view, type, text, max - 1, trim);
1640         else
1641                 col = draw_space(view, type, max - 1, max - 1);
1643         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1644         return view->width - view->col <= 0;
1647 static bool
1648 draw_date(struct view *view, struct tm *time)
1650         char buf[DATE_COLS];
1651         char *date;
1652         int timelen = 0;
1654         if (time)
1655                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1656         date = timelen ? buf : NULL;
1658         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1661 static bool
1662 draw_view_line(struct view *view, unsigned int lineno)
1664         struct line *line;
1665         bool selected = (view->offset + lineno == view->lineno);
1666         bool draw_ok;
1668         assert(view_is_displayed(view));
1670         if (view->offset + lineno >= view->lines)
1671                 return FALSE;
1673         line = &view->line[view->offset + lineno];
1675         wmove(view->win, lineno, 0);
1676         view->col = 0;
1677         view->curline = line;
1678         view->curtype = LINE_NONE;
1679         line->selected = FALSE;
1681         if (selected) {
1682                 set_view_attr(view, LINE_CURSOR);
1683                 line->selected = TRUE;
1684                 view->ops->select(view, line);
1685         } else if (line->selected) {
1686                 wclrtoeol(view->win);
1687         }
1689         scrollok(view->win, FALSE);
1690         draw_ok = view->ops->draw(view, line, lineno);
1691         scrollok(view->win, TRUE);
1693         return draw_ok;
1696 static void
1697 redraw_view_dirty(struct view *view)
1699         bool dirty = FALSE;
1700         int lineno;
1702         for (lineno = 0; lineno < view->height; lineno++) {
1703                 struct line *line = &view->line[view->offset + lineno];
1705                 if (!line->dirty)
1706                         continue;
1707                 line->dirty = 0;
1708                 dirty = TRUE;
1709                 if (!draw_view_line(view, lineno))
1710                         break;
1711         }
1713         if (!dirty)
1714                 return;
1715         redrawwin(view->win);
1716         if (input_mode)
1717                 wnoutrefresh(view->win);
1718         else
1719                 wrefresh(view->win);
1722 static void
1723 redraw_view_from(struct view *view, int lineno)
1725         assert(0 <= lineno && lineno < view->height);
1727         for (; lineno < view->height; lineno++) {
1728                 if (!draw_view_line(view, lineno))
1729                         break;
1730         }
1732         redrawwin(view->win);
1733         if (input_mode)
1734                 wnoutrefresh(view->win);
1735         else
1736                 wrefresh(view->win);
1739 static void
1740 redraw_view(struct view *view)
1742         wclear(view->win);
1743         redraw_view_from(view, 0);
1747 static void
1748 update_view_title(struct view *view)
1750         char buf[SIZEOF_STR];
1751         char state[SIZEOF_STR];
1752         size_t bufpos = 0, statelen = 0;
1754         assert(view_is_displayed(view));
1756         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1757                 unsigned int view_lines = view->offset + view->height;
1758                 unsigned int lines = view->lines
1759                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1760                                    : 0;
1762                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1763                                    view->ops->type,
1764                                    view->lineno + 1,
1765                                    view->lines,
1766                                    lines);
1768                 if (view->pipe) {
1769                         time_t secs = time(NULL) - view->start_time;
1771                         /* Three git seconds are a long time ... */
1772                         if (secs > 2)
1773                                 string_format_from(state, &statelen, " %lds", secs);
1774                 }
1775         }
1777         string_format_from(buf, &bufpos, "[%s]", view->name);
1778         if (*view->ref && bufpos < view->width) {
1779                 size_t refsize = strlen(view->ref);
1780                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1782                 if (minsize < view->width)
1783                         refsize = view->width - minsize + 7;
1784                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1785         }
1787         if (statelen && bufpos < view->width) {
1788                 string_format_from(buf, &bufpos, " %s", state);
1789         }
1791         if (view == display[current_view])
1792                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1793         else
1794                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1796         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1797         wclrtoeol(view->title);
1798         wmove(view->title, 0, view->width - 1);
1800         if (input_mode)
1801                 wnoutrefresh(view->title);
1802         else
1803                 wrefresh(view->title);
1806 static void
1807 resize_display(void)
1809         int offset, i;
1810         struct view *base = display[0];
1811         struct view *view = display[1] ? display[1] : display[0];
1813         /* Setup window dimensions */
1815         getmaxyx(stdscr, base->height, base->width);
1817         /* Make room for the status window. */
1818         base->height -= 1;
1820         if (view != base) {
1821                 /* Horizontal split. */
1822                 view->width   = base->width;
1823                 view->height  = SCALE_SPLIT_VIEW(base->height);
1824                 base->height -= view->height;
1826                 /* Make room for the title bar. */
1827                 view->height -= 1;
1828         }
1830         /* Make room for the title bar. */
1831         base->height -= 1;
1833         offset = 0;
1835         foreach_displayed_view (view, i) {
1836                 if (!view->win) {
1837                         view->win = newwin(view->height, 0, offset, 0);
1838                         if (!view->win)
1839                                 die("Failed to create %s view", view->name);
1841                         scrollok(view->win, TRUE);
1843                         view->title = newwin(1, 0, offset + view->height, 0);
1844                         if (!view->title)
1845                                 die("Failed to create title window");
1847                 } else {
1848                         wresize(view->win, view->height, view->width);
1849                         mvwin(view->win,   offset, 0);
1850                         mvwin(view->title, offset + view->height, 0);
1851                 }
1853                 offset += view->height + 1;
1854         }
1857 static void
1858 redraw_display(void)
1860         struct view *view;
1861         int i;
1863         foreach_displayed_view (view, i) {
1864                 redraw_view(view);
1865                 update_view_title(view);
1866         }
1869 static void
1870 update_display_cursor(struct view *view)
1872         /* Move the cursor to the right-most column of the cursor line.
1873          *
1874          * XXX: This could turn out to be a bit expensive, but it ensures that
1875          * the cursor does not jump around. */
1876         if (view->lines) {
1877                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1878                 wrefresh(view->win);
1879         }
1882 /*
1883  * Navigation
1884  */
1886 /* Scrolling backend */
1887 static void
1888 do_scroll_view(struct view *view, int lines)
1890         bool redraw_current_line = FALSE;
1892         /* The rendering expects the new offset. */
1893         view->offset += lines;
1895         assert(0 <= view->offset && view->offset < view->lines);
1896         assert(lines);
1898         /* Move current line into the view. */
1899         if (view->lineno < view->offset) {
1900                 view->lineno = view->offset;
1901                 redraw_current_line = TRUE;
1902         } else if (view->lineno >= view->offset + view->height) {
1903                 view->lineno = view->offset + view->height - 1;
1904                 redraw_current_line = TRUE;
1905         }
1907         assert(view->offset <= view->lineno && view->lineno < view->lines);
1909         /* Redraw the whole screen if scrolling is pointless. */
1910         if (view->height < ABS(lines)) {
1911                 redraw_view(view);
1913         } else {
1914                 int line = lines > 0 ? view->height - lines : 0;
1915                 int end = line + ABS(lines);
1917                 wscrl(view->win, lines);
1919                 for (; line < end; line++) {
1920                         if (!draw_view_line(view, line))
1921                                 break;
1922                 }
1924                 if (redraw_current_line)
1925                         draw_view_line(view, view->lineno - view->offset);
1926         }
1928         redrawwin(view->win);
1929         wrefresh(view->win);
1930         report("");
1933 /* Scroll frontend */
1934 static void
1935 scroll_view(struct view *view, enum request request)
1937         int lines = 1;
1939         assert(view_is_displayed(view));
1941         switch (request) {
1942         case REQ_SCROLL_PAGE_DOWN:
1943                 lines = view->height;
1944         case REQ_SCROLL_LINE_DOWN:
1945                 if (view->offset + lines > view->lines)
1946                         lines = view->lines - view->offset;
1948                 if (lines == 0 || view->offset + view->height >= view->lines) {
1949                         report("Cannot scroll beyond the last line");
1950                         return;
1951                 }
1952                 break;
1954         case REQ_SCROLL_PAGE_UP:
1955                 lines = view->height;
1956         case REQ_SCROLL_LINE_UP:
1957                 if (lines > view->offset)
1958                         lines = view->offset;
1960                 if (lines == 0) {
1961                         report("Cannot scroll beyond the first line");
1962                         return;
1963                 }
1965                 lines = -lines;
1966                 break;
1968         default:
1969                 die("request %d not handled in switch", request);
1970         }
1972         do_scroll_view(view, lines);
1975 /* Cursor moving */
1976 static void
1977 move_view(struct view *view, enum request request)
1979         int scroll_steps = 0;
1980         int steps;
1982         switch (request) {
1983         case REQ_MOVE_FIRST_LINE:
1984                 steps = -view->lineno;
1985                 break;
1987         case REQ_MOVE_LAST_LINE:
1988                 steps = view->lines - view->lineno - 1;
1989                 break;
1991         case REQ_MOVE_PAGE_UP:
1992                 steps = view->height > view->lineno
1993                       ? -view->lineno : -view->height;
1994                 break;
1996         case REQ_MOVE_PAGE_DOWN:
1997                 steps = view->lineno + view->height >= view->lines
1998                       ? view->lines - view->lineno - 1 : view->height;
1999                 break;
2001         case REQ_MOVE_UP:
2002                 steps = -1;
2003                 break;
2005         case REQ_MOVE_DOWN:
2006                 steps = 1;
2007                 break;
2009         default:
2010                 die("request %d not handled in switch", request);
2011         }
2013         if (steps <= 0 && view->lineno == 0) {
2014                 report("Cannot move beyond the first line");
2015                 return;
2017         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2018                 report("Cannot move beyond the last line");
2019                 return;
2020         }
2022         /* Move the current line */
2023         view->lineno += steps;
2024         assert(0 <= view->lineno && view->lineno < view->lines);
2026         /* Check whether the view needs to be scrolled */
2027         if (view->lineno < view->offset ||
2028             view->lineno >= view->offset + view->height) {
2029                 scroll_steps = steps;
2030                 if (steps < 0 && -steps > view->offset) {
2031                         scroll_steps = -view->offset;
2033                 } else if (steps > 0) {
2034                         if (view->lineno == view->lines - 1 &&
2035                             view->lines > view->height) {
2036                                 scroll_steps = view->lines - view->offset - 1;
2037                                 if (scroll_steps >= view->height)
2038                                         scroll_steps -= view->height - 1;
2039                         }
2040                 }
2041         }
2043         if (!view_is_displayed(view)) {
2044                 view->offset += scroll_steps;
2045                 assert(0 <= view->offset && view->offset < view->lines);
2046                 view->ops->select(view, &view->line[view->lineno]);
2047                 return;
2048         }
2050         /* Repaint the old "current" line if we be scrolling */
2051         if (ABS(steps) < view->height)
2052                 draw_view_line(view, view->lineno - steps - view->offset);
2054         if (scroll_steps) {
2055                 do_scroll_view(view, scroll_steps);
2056                 return;
2057         }
2059         /* Draw the current line */
2060         draw_view_line(view, view->lineno - view->offset);
2062         redrawwin(view->win);
2063         wrefresh(view->win);
2064         report("");
2068 /*
2069  * Searching
2070  */
2072 static void search_view(struct view *view, enum request request);
2074 static bool
2075 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2077         assert(view_is_displayed(view));
2079         if (!view->ops->grep(view, line))
2080                 return FALSE;
2082         if (lineno - view->offset >= view->height) {
2083                 view->offset = lineno;
2084                 view->lineno = lineno;
2085                 redraw_view(view);
2087         } else {
2088                 unsigned long old_lineno = view->lineno - view->offset;
2090                 view->lineno = lineno;
2091                 draw_view_line(view, old_lineno);
2093                 draw_view_line(view, view->lineno - view->offset);
2094                 redrawwin(view->win);
2095                 wrefresh(view->win);
2096         }
2098         report("Line %ld matches '%s'", lineno + 1, view->grep);
2099         return TRUE;
2102 static void
2103 find_next(struct view *view, enum request request)
2105         unsigned long lineno = view->lineno;
2106         int direction;
2108         if (!*view->grep) {
2109                 if (!*opt_search)
2110                         report("No previous search");
2111                 else
2112                         search_view(view, request);
2113                 return;
2114         }
2116         switch (request) {
2117         case REQ_SEARCH:
2118         case REQ_FIND_NEXT:
2119                 direction = 1;
2120                 break;
2122         case REQ_SEARCH_BACK:
2123         case REQ_FIND_PREV:
2124                 direction = -1;
2125                 break;
2127         default:
2128                 return;
2129         }
2131         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2132                 lineno += direction;
2134         /* Note, lineno is unsigned long so will wrap around in which case it
2135          * will become bigger than view->lines. */
2136         for (; lineno < view->lines; lineno += direction) {
2137                 struct line *line = &view->line[lineno];
2139                 if (find_next_line(view, lineno, line))
2140                         return;
2141         }
2143         report("No match found for '%s'", view->grep);
2146 static void
2147 search_view(struct view *view, enum request request)
2149         int regex_err;
2151         if (view->regex) {
2152                 regfree(view->regex);
2153                 *view->grep = 0;
2154         } else {
2155                 view->regex = calloc(1, sizeof(*view->regex));
2156                 if (!view->regex)
2157                         return;
2158         }
2160         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2161         if (regex_err != 0) {
2162                 char buf[SIZEOF_STR] = "unknown error";
2164                 regerror(regex_err, view->regex, buf, sizeof(buf));
2165                 report("Search failed: %s", buf);
2166                 return;
2167         }
2169         string_copy(view->grep, opt_search);
2171         find_next(view, request);
2174 /*
2175  * Incremental updating
2176  */
2178 static void
2179 end_update(struct view *view, bool force)
2181         if (!view->pipe)
2182                 return;
2183         while (!view->ops->read(view, NULL))
2184                 if (!force)
2185                         return;
2186         set_nonblocking_input(FALSE);
2187         if (view->pipe == stdin)
2188                 fclose(view->pipe);
2189         else
2190                 pclose(view->pipe);
2191         view->pipe = NULL;
2194 static bool
2195 begin_update(struct view *view)
2197         if (opt_cmd[0]) {
2198                 string_copy(view->cmd, opt_cmd);
2199                 opt_cmd[0] = 0;
2200                 /* When running random commands, initially show the
2201                  * command in the title. However, it maybe later be
2202                  * overwritten if a commit line is selected. */
2203                 if (view == VIEW(REQ_VIEW_PAGER))
2204                         string_copy(view->ref, view->cmd);
2205                 else
2206                         view->ref[0] = 0;
2208         } else if (view == VIEW(REQ_VIEW_TREE)) {
2209                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2210                 char path[SIZEOF_STR];
2212                 if (strcmp(view->vid, view->id))
2213                         opt_path[0] = path[0] = 0;
2214                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2215                         return FALSE;
2217                 if (!string_format(view->cmd, format, view->id, path))
2218                         return FALSE;
2220         } else {
2221                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2222                 const char *id = view->id;
2224                 if (!string_format(view->cmd, format, id, id, id, id, id))
2225                         return FALSE;
2227                 /* Put the current ref_* value to the view title ref
2228                  * member. This is needed by the blob view. Most other
2229                  * views sets it automatically after loading because the
2230                  * first line is a commit line. */
2231                 string_copy_rev(view->ref, view->id);
2232         }
2234         /* Special case for the pager view. */
2235         if (opt_pipe) {
2236                 view->pipe = opt_pipe;
2237                 opt_pipe = NULL;
2238         } else {
2239                 view->pipe = popen(view->cmd, "r");
2240         }
2242         if (!view->pipe)
2243                 return FALSE;
2245         set_nonblocking_input(TRUE);
2247         view->offset = 0;
2248         view->lines  = 0;
2249         view->lineno = 0;
2250         string_copy_rev(view->vid, view->id);
2252         if (view->line) {
2253                 int i;
2255                 for (i = 0; i < view->lines; i++)
2256                         if (view->line[i].data)
2257                                 free(view->line[i].data);
2259                 free(view->line);
2260                 view->line = NULL;
2261         }
2263         view->start_time = time(NULL);
2265         return TRUE;
2268 #define ITEM_CHUNK_SIZE 256
2269 static void *
2270 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2272         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2273         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2275         if (mem == NULL || num_chunks != num_chunks_new) {
2276                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2277                 mem = realloc(mem, *size * item_size);
2278         }
2280         return mem;
2283 static struct line *
2284 realloc_lines(struct view *view, size_t line_size)
2286         size_t alloc = view->line_alloc;
2287         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2288                                          sizeof(*view->line));
2290         if (!tmp)
2291                 return NULL;
2293         view->line = tmp;
2294         view->line_alloc = alloc;
2295         view->line_size = line_size;
2296         return view->line;
2299 static bool
2300 update_view(struct view *view)
2302         char in_buffer[BUFSIZ];
2303         char out_buffer[BUFSIZ * 2];
2304         char *line;
2305         /* The number of lines to read. If too low it will cause too much
2306          * redrawing (and possible flickering), if too high responsiveness
2307          * will suffer. */
2308         unsigned long lines = view->height;
2309         int redraw_from = -1;
2311         if (!view->pipe)
2312                 return TRUE;
2314         /* Only redraw if lines are visible. */
2315         if (view->offset + view->height >= view->lines)
2316                 redraw_from = view->lines - view->offset;
2318         /* FIXME: This is probably not perfect for backgrounded views. */
2319         if (!realloc_lines(view, view->lines + lines))
2320                 goto alloc_error;
2322         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2323                 size_t linelen = strlen(line);
2325                 if (linelen)
2326                         line[linelen - 1] = 0;
2328                 if (opt_iconv != ICONV_NONE) {
2329                         ICONV_CONST char *inbuf = line;
2330                         size_t inlen = linelen;
2332                         char *outbuf = out_buffer;
2333                         size_t outlen = sizeof(out_buffer);
2335                         size_t ret;
2337                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2338                         if (ret != (size_t) -1) {
2339                                 line = out_buffer;
2340                                 linelen = strlen(out_buffer);
2341                         }
2342                 }
2344                 if (!view->ops->read(view, line))
2345                         goto alloc_error;
2347                 if (lines-- == 1)
2348                         break;
2349         }
2351         {
2352                 int digits;
2354                 lines = view->lines;
2355                 for (digits = 0; lines; digits++)
2356                         lines /= 10;
2358                 /* Keep the displayed view in sync with line number scaling. */
2359                 if (digits != view->digits) {
2360                         view->digits = digits;
2361                         redraw_from = 0;
2362                 }
2363         }
2365         if (!view_is_displayed(view))
2366                 goto check_pipe;
2368         if (view == VIEW(REQ_VIEW_TREE)) {
2369                 /* Clear the view and redraw everything since the tree sorting
2370                  * might have rearranged things. */
2371                 redraw_view(view);
2373         } else if (redraw_from >= 0) {
2374                 /* If this is an incremental update, redraw the previous line
2375                  * since for commits some members could have changed when
2376                  * loading the main view. */
2377                 if (redraw_from > 0)
2378                         redraw_from--;
2380                 /* Since revision graph visualization requires knowledge
2381                  * about the parent commit, it causes a further one-off
2382                  * needed to be redrawn for incremental updates. */
2383                 if (redraw_from > 0 && opt_rev_graph)
2384                         redraw_from--;
2386                 /* Incrementally draw avoids flickering. */
2387                 redraw_view_from(view, redraw_from);
2388         }
2390         if (view == VIEW(REQ_VIEW_BLAME))
2391                 redraw_view_dirty(view);
2393         /* Update the title _after_ the redraw so that if the redraw picks up a
2394          * commit reference in view->ref it'll be available here. */
2395         update_view_title(view);
2397 check_pipe:
2398         if (ferror(view->pipe) && errno != 0) {
2399                 report("Failed to read: %s", strerror(errno));
2400                 end_update(view, TRUE);
2402         } else if (feof(view->pipe)) {
2403                 report("");
2404                 end_update(view, FALSE);
2405         }
2407         return TRUE;
2409 alloc_error:
2410         report("Allocation failure");
2411         end_update(view, TRUE);
2412         return FALSE;
2415 static struct line *
2416 add_line_data(struct view *view, void *data, enum line_type type)
2418         struct line *line = &view->line[view->lines++];
2420         memset(line, 0, sizeof(*line));
2421         line->type = type;
2422         line->data = data;
2424         return line;
2427 static struct line *
2428 add_line_text(struct view *view, char *data, enum line_type type)
2430         if (data)
2431                 data = strdup(data);
2433         return data ? add_line_data(view, data, type) : NULL;
2437 /*
2438  * View opening
2439  */
2441 enum open_flags {
2442         OPEN_DEFAULT = 0,       /* Use default view switching. */
2443         OPEN_SPLIT = 1,         /* Split current view. */
2444         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2445         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2446         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2447 };
2449 static void
2450 open_view(struct view *prev, enum request request, enum open_flags flags)
2452         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2453         bool split = !!(flags & OPEN_SPLIT);
2454         bool reload = !!(flags & OPEN_RELOAD);
2455         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2456         struct view *view = VIEW(request);
2457         int nviews = displayed_views();
2458         struct view *base_view = display[0];
2460         if (view == prev && nviews == 1 && !reload) {
2461                 report("Already in %s view", view->name);
2462                 return;
2463         }
2465         if (view->git_dir && !opt_git_dir[0]) {
2466                 report("The %s view is disabled in pager view", view->name);
2467                 return;
2468         }
2470         if (split) {
2471                 display[1] = view;
2472                 if (!backgrounded)
2473                         current_view = 1;
2474         } else if (!nomaximize) {
2475                 /* Maximize the current view. */
2476                 memset(display, 0, sizeof(display));
2477                 current_view = 0;
2478                 display[current_view] = view;
2479         }
2481         /* Resize the view when switching between split- and full-screen,
2482          * or when switching between two different full-screen views. */
2483         if (nviews != displayed_views() ||
2484             (nviews == 1 && base_view != display[0]))
2485                 resize_display();
2487         if (view->pipe)
2488                 end_update(view, TRUE);
2490         if (view->ops->open) {
2491                 if (!view->ops->open(view)) {
2492                         report("Failed to load %s view", view->name);
2493                         return;
2494                 }
2496         } else if ((reload || strcmp(view->vid, view->id)) &&
2497                    !begin_update(view)) {
2498                 report("Failed to load %s view", view->name);
2499                 return;
2500         }
2502         if (split && prev->lineno - prev->offset >= prev->height) {
2503                 /* Take the title line into account. */
2504                 int lines = prev->lineno - prev->offset - prev->height + 1;
2506                 /* Scroll the view that was split if the current line is
2507                  * outside the new limited view. */
2508                 do_scroll_view(prev, lines);
2509         }
2511         if (prev && view != prev) {
2512                 if (split && !backgrounded) {
2513                         /* "Blur" the previous view. */
2514                         update_view_title(prev);
2515                 }
2517                 view->parent = prev;
2518         }
2520         if (view->pipe && view->lines == 0) {
2521                 /* Clear the old view and let the incremental updating refill
2522                  * the screen. */
2523                 werase(view->win);
2524                 report("");
2525         } else {
2526                 redraw_view(view);
2527                 report("");
2528         }
2530         /* If the view is backgrounded the above calls to report()
2531          * won't redraw the view title. */
2532         if (backgrounded)
2533                 update_view_title(view);
2536 static void
2537 run_confirm(const char *cmd, const char *prompt)
2539         if (prompt_yesno(prompt)) {
2540                 system(cmd);
2541         }
2544 static void
2545 open_external_viewer(const char *cmd)
2547         def_prog_mode();           /* save current tty modes */
2548         endwin();                  /* restore original tty modes */
2549         system(cmd);
2550         fprintf(stderr, "Press Enter to continue");
2551         getc(stdin);
2552         reset_prog_mode();
2553         redraw_display();
2556 static void
2557 open_mergetool(const char *file)
2559         char cmd[SIZEOF_STR];
2560         char file_sq[SIZEOF_STR];
2562         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2563             string_format(cmd, "git mergetool %s", file_sq)) {
2564                 open_external_viewer(cmd);
2565         }
2568 static void
2569 open_editor(bool from_root, const char *file)
2571         char cmd[SIZEOF_STR];
2572         char file_sq[SIZEOF_STR];
2573         char *editor;
2574         char *prefix = from_root ? opt_cdup : "";
2576         editor = getenv("GIT_EDITOR");
2577         if (!editor && *opt_editor)
2578                 editor = opt_editor;
2579         if (!editor)
2580                 editor = getenv("VISUAL");
2581         if (!editor)
2582                 editor = getenv("EDITOR");
2583         if (!editor)
2584                 editor = "vi";
2586         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2587             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2588                 open_external_viewer(cmd);
2589         }
2592 static void
2593 open_run_request(enum request request)
2595         struct run_request *req = get_run_request(request);
2596         char buf[SIZEOF_STR * 2];
2597         size_t bufpos;
2598         char *cmd;
2600         if (!req) {
2601                 report("Unknown run request");
2602                 return;
2603         }
2605         bufpos = 0;
2606         cmd = req->cmd;
2608         while (cmd) {
2609                 char *next = strstr(cmd, "%(");
2610                 int len = next - cmd;
2611                 char *value;
2613                 if (!next) {
2614                         len = strlen(cmd);
2615                         value = "";
2617                 } else if (!strncmp(next, "%(head)", 7)) {
2618                         value = ref_head;
2620                 } else if (!strncmp(next, "%(commit)", 9)) {
2621                         value = ref_commit;
2623                 } else if (!strncmp(next, "%(blob)", 7)) {
2624                         value = ref_blob;
2626                 } else {
2627                         report("Unknown replacement in run request: `%s`", req->cmd);
2628                         return;
2629                 }
2631                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2632                         return;
2634                 if (next)
2635                         next = strchr(next, ')') + 1;
2636                 cmd = next;
2637         }
2639         open_external_viewer(buf);
2642 /*
2643  * User request switch noodle
2644  */
2646 static int
2647 view_driver(struct view *view, enum request request)
2649         int i;
2651         if (request == REQ_NONE) {
2652                 doupdate();
2653                 return TRUE;
2654         }
2656         if (request > REQ_NONE) {
2657                 open_run_request(request);
2658                 /* FIXME: When all views can refresh always do this. */
2659                 if (view == VIEW(REQ_VIEW_STATUS) ||
2660                     view == VIEW(REQ_VIEW_STAGE))
2661                         request = REQ_REFRESH;
2662                 else
2663                         return TRUE;
2664         }
2666         if (view && view->lines) {
2667                 request = view->ops->request(view, request, &view->line[view->lineno]);
2668                 if (request == REQ_NONE)
2669                         return TRUE;
2670         }
2672         switch (request) {
2673         case REQ_MOVE_UP:
2674         case REQ_MOVE_DOWN:
2675         case REQ_MOVE_PAGE_UP:
2676         case REQ_MOVE_PAGE_DOWN:
2677         case REQ_MOVE_FIRST_LINE:
2678         case REQ_MOVE_LAST_LINE:
2679                 move_view(view, request);
2680                 break;
2682         case REQ_SCROLL_LINE_DOWN:
2683         case REQ_SCROLL_LINE_UP:
2684         case REQ_SCROLL_PAGE_DOWN:
2685         case REQ_SCROLL_PAGE_UP:
2686                 scroll_view(view, request);
2687                 break;
2689         case REQ_VIEW_BLAME:
2690                 if (!opt_file[0]) {
2691                         report("No file chosen, press %s to open tree view",
2692                                get_key(REQ_VIEW_TREE));
2693                         break;
2694                 }
2695                 open_view(view, request, OPEN_DEFAULT);
2696                 break;
2698         case REQ_VIEW_BLOB:
2699                 if (!ref_blob[0]) {
2700                         report("No file chosen, press %s to open tree view",
2701                                get_key(REQ_VIEW_TREE));
2702                         break;
2703                 }
2704                 open_view(view, request, OPEN_DEFAULT);
2705                 break;
2707         case REQ_VIEW_PAGER:
2708                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2709                         report("No pager content, press %s to run command from prompt",
2710                                get_key(REQ_PROMPT));
2711                         break;
2712                 }
2713                 open_view(view, request, OPEN_DEFAULT);
2714                 break;
2716         case REQ_VIEW_STAGE:
2717                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2718                         report("No stage content, press %s to open the status view and choose file",
2719                                get_key(REQ_VIEW_STATUS));
2720                         break;
2721                 }
2722                 open_view(view, request, OPEN_DEFAULT);
2723                 break;
2725         case REQ_VIEW_STATUS:
2726                 if (opt_is_inside_work_tree == FALSE) {
2727                         report("The status view requires a working tree");
2728                         break;
2729                 }
2730                 open_view(view, request, OPEN_DEFAULT);
2731                 break;
2733         case REQ_VIEW_MAIN:
2734         case REQ_VIEW_DIFF:
2735         case REQ_VIEW_LOG:
2736         case REQ_VIEW_TREE:
2737         case REQ_VIEW_HELP:
2738                 open_view(view, request, OPEN_DEFAULT);
2739                 break;
2741         case REQ_NEXT:
2742         case REQ_PREVIOUS:
2743                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2745                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2746                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2747                    (view == VIEW(REQ_VIEW_DIFF) &&
2748                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2749                    (view == VIEW(REQ_VIEW_STAGE) &&
2750                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2751                    (view == VIEW(REQ_VIEW_BLOB) &&
2752                      view->parent == VIEW(REQ_VIEW_TREE))) {
2753                         int line;
2755                         view = view->parent;
2756                         line = view->lineno;
2757                         move_view(view, request);
2758                         if (view_is_displayed(view))
2759                                 update_view_title(view);
2760                         if (line != view->lineno)
2761                                 view->ops->request(view, REQ_ENTER,
2762                                                    &view->line[view->lineno]);
2764                 } else {
2765                         move_view(view, request);
2766                 }
2767                 break;
2769         case REQ_VIEW_NEXT:
2770         {
2771                 int nviews = displayed_views();
2772                 int next_view = (current_view + 1) % nviews;
2774                 if (next_view == current_view) {
2775                         report("Only one view is displayed");
2776                         break;
2777                 }
2779                 current_view = next_view;
2780                 /* Blur out the title of the previous view. */
2781                 update_view_title(view);
2782                 report("");
2783                 break;
2784         }
2785         case REQ_REFRESH:
2786                 report("Refreshing is not yet supported for the %s view", view->name);
2787                 break;
2789         case REQ_MAXIMIZE:
2790                 if (displayed_views() == 2)
2791                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2792                 break;
2794         case REQ_TOGGLE_LINENO:
2795                 opt_line_number = !opt_line_number;
2796                 redraw_display();
2797                 break;
2799         case REQ_TOGGLE_DATE:
2800                 opt_date = !opt_date;
2801                 redraw_display();
2802                 break;
2804         case REQ_TOGGLE_AUTHOR:
2805                 opt_author = !opt_author;
2806                 redraw_display();
2807                 break;
2809         case REQ_TOGGLE_REV_GRAPH:
2810                 opt_rev_graph = !opt_rev_graph;
2811                 redraw_display();
2812                 break;
2814         case REQ_TOGGLE_REFS:
2815                 opt_show_refs = !opt_show_refs;
2816                 redraw_display();
2817                 break;
2819         case REQ_SEARCH:
2820         case REQ_SEARCH_BACK:
2821                 search_view(view, request);
2822                 break;
2824         case REQ_FIND_NEXT:
2825         case REQ_FIND_PREV:
2826                 find_next(view, request);
2827                 break;
2829         case REQ_STOP_LOADING:
2830                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2831                         view = &views[i];
2832                         if (view->pipe)
2833                                 report("Stopped loading the %s view", view->name),
2834                         end_update(view, TRUE);
2835                 }
2836                 break;
2838         case REQ_SHOW_VERSION:
2839                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2840                 return TRUE;
2842         case REQ_SCREEN_RESIZE:
2843                 resize_display();
2844                 /* Fall-through */
2845         case REQ_SCREEN_REDRAW:
2846                 redraw_display();
2847                 break;
2849         case REQ_EDIT:
2850                 report("Nothing to edit");
2851                 break;
2853         case REQ_ENTER:
2854                 report("Nothing to enter");
2855                 break;
2857         case REQ_VIEW_CLOSE:
2858                 /* XXX: Mark closed views by letting view->parent point to the
2859                  * view itself. Parents to closed view should never be
2860                  * followed. */
2861                 if (view->parent &&
2862                     view->parent->parent != view->parent) {
2863                         memset(display, 0, sizeof(display));
2864                         current_view = 0;
2865                         display[current_view] = view->parent;
2866                         view->parent = view;
2867                         resize_display();
2868                         redraw_display();
2869                         break;
2870                 }
2871                 /* Fall-through */
2872         case REQ_QUIT:
2873                 return FALSE;
2875         default:
2876                 /* An unknown key will show most commonly used commands. */
2877                 report("Unknown key, press 'h' for help");
2878                 return TRUE;
2879         }
2881         return TRUE;
2885 /*
2886  * Pager backend
2887  */
2889 static bool
2890 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2892         char *text = line->data;
2894         if (opt_line_number && draw_lineno(view, lineno))
2895                 return TRUE;
2897         draw_text(view, line->type, text, TRUE);
2898         return TRUE;
2901 static bool
2902 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2904         char refbuf[SIZEOF_STR];
2905         char *ref = NULL;
2906         FILE *pipe;
2908         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2909                 return TRUE;
2911         pipe = popen(refbuf, "r");
2912         if (!pipe)
2913                 return TRUE;
2915         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2916                 ref = chomp_string(ref);
2917         pclose(pipe);
2919         if (!ref || !*ref)
2920                 return TRUE;
2922         /* This is the only fatal call, since it can "corrupt" the buffer. */
2923         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2924                 return FALSE;
2926         return TRUE;
2929 static void
2930 add_pager_refs(struct view *view, struct line *line)
2932         char buf[SIZEOF_STR];
2933         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2934         struct ref **refs;
2935         size_t bufpos = 0, refpos = 0;
2936         const char *sep = "Refs: ";
2937         bool is_tag = FALSE;
2939         assert(line->type == LINE_COMMIT);
2941         refs = get_refs(commit_id);
2942         if (!refs) {
2943                 if (view == VIEW(REQ_VIEW_DIFF))
2944                         goto try_add_describe_ref;
2945                 return;
2946         }
2948         do {
2949                 struct ref *ref = refs[refpos];
2950                 char *fmt = ref->tag    ? "%s[%s]" :
2951                             ref->remote ? "%s<%s>" : "%s%s";
2953                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2954                         return;
2955                 sep = ", ";
2956                 if (ref->tag)
2957                         is_tag = TRUE;
2958         } while (refs[refpos++]->next);
2960         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2961 try_add_describe_ref:
2962                 /* Add <tag>-g<commit_id> "fake" reference. */
2963                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2964                         return;
2965         }
2967         if (bufpos == 0)
2968                 return;
2970         if (!realloc_lines(view, view->line_size + 1))
2971                 return;
2973         add_line_text(view, buf, LINE_PP_REFS);
2976 static bool
2977 pager_read(struct view *view, char *data)
2979         struct line *line;
2981         if (!data)
2982                 return TRUE;
2984         line = add_line_text(view, data, get_line_type(data));
2985         if (!line)
2986                 return FALSE;
2988         if (line->type == LINE_COMMIT &&
2989             (view == VIEW(REQ_VIEW_DIFF) ||
2990              view == VIEW(REQ_VIEW_LOG)))
2991                 add_pager_refs(view, line);
2993         return TRUE;
2996 static enum request
2997 pager_request(struct view *view, enum request request, struct line *line)
2999         int split = 0;
3001         if (request != REQ_ENTER)
3002                 return request;
3004         if (line->type == LINE_COMMIT &&
3005            (view == VIEW(REQ_VIEW_LOG) ||
3006             view == VIEW(REQ_VIEW_PAGER))) {
3007                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3008                 split = 1;
3009         }
3011         /* Always scroll the view even if it was split. That way
3012          * you can use Enter to scroll through the log view and
3013          * split open each commit diff. */
3014         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3016         /* FIXME: A minor workaround. Scrolling the view will call report("")
3017          * but if we are scrolling a non-current view this won't properly
3018          * update the view title. */
3019         if (split)
3020                 update_view_title(view);
3022         return REQ_NONE;
3025 static bool
3026 pager_grep(struct view *view, struct line *line)
3028         regmatch_t pmatch;
3029         char *text = line->data;
3031         if (!*text)
3032                 return FALSE;
3034         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3035                 return FALSE;
3037         return TRUE;
3040 static void
3041 pager_select(struct view *view, struct line *line)
3043         if (line->type == LINE_COMMIT) {
3044                 char *text = (char *)line->data + STRING_SIZE("commit ");
3046                 if (view != VIEW(REQ_VIEW_PAGER))
3047                         string_copy_rev(view->ref, text);
3048                 string_copy_rev(ref_commit, text);
3049         }
3052 static struct view_ops pager_ops = {
3053         "line",
3054         NULL,
3055         pager_read,
3056         pager_draw,
3057         pager_request,
3058         pager_grep,
3059         pager_select,
3060 };
3063 /*
3064  * Help backend
3065  */
3067 static bool
3068 help_open(struct view *view)
3070         char buf[BUFSIZ];
3071         int lines = ARRAY_SIZE(req_info) + 2;
3072         int i;
3074         if (view->lines > 0)
3075                 return TRUE;
3077         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3078                 if (!req_info[i].request)
3079                         lines++;
3081         lines += run_requests + 1;
3083         view->line = calloc(lines, sizeof(*view->line));
3084         if (!view->line)
3085                 return FALSE;
3087         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3089         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3090                 char *key;
3092                 if (req_info[i].request == REQ_NONE)
3093                         continue;
3095                 if (!req_info[i].request) {
3096                         add_line_text(view, "", LINE_DEFAULT);
3097                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3098                         continue;
3099                 }
3101                 key = get_key(req_info[i].request);
3102                 if (!*key)
3103                         key = "(no key defined)";
3105                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3106                         continue;
3108                 add_line_text(view, buf, LINE_DEFAULT);
3109         }
3111         if (run_requests) {
3112                 add_line_text(view, "", LINE_DEFAULT);
3113                 add_line_text(view, "External commands:", LINE_DEFAULT);
3114         }
3116         for (i = 0; i < run_requests; i++) {
3117                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3118                 char *key;
3120                 if (!req)
3121                         continue;
3123                 key = get_key_name(req->key);
3124                 if (!*key)
3125                         key = "(no key defined)";
3127                 if (!string_format(buf, "    %-10s %-14s `%s`",
3128                                    keymap_table[req->keymap].name,
3129                                    key, req->cmd))
3130                         continue;
3132                 add_line_text(view, buf, LINE_DEFAULT);
3133         }
3135         return TRUE;
3138 static struct view_ops help_ops = {
3139         "line",
3140         help_open,
3141         NULL,
3142         pager_draw,
3143         pager_request,
3144         pager_grep,
3145         pager_select,
3146 };
3149 /*
3150  * Tree backend
3151  */
3153 struct tree_stack_entry {
3154         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3155         unsigned long lineno;           /* Line number to restore */
3156         char *name;                     /* Position of name in opt_path */
3157 };
3159 /* The top of the path stack. */
3160 static struct tree_stack_entry *tree_stack = NULL;
3161 unsigned long tree_lineno = 0;
3163 static void
3164 pop_tree_stack_entry(void)
3166         struct tree_stack_entry *entry = tree_stack;
3168         tree_lineno = entry->lineno;
3169         entry->name[0] = 0;
3170         tree_stack = entry->prev;
3171         free(entry);
3174 static void
3175 push_tree_stack_entry(char *name, unsigned long lineno)
3177         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3178         size_t pathlen = strlen(opt_path);
3180         if (!entry)
3181                 return;
3183         entry->prev = tree_stack;
3184         entry->name = opt_path + pathlen;
3185         tree_stack = entry;
3187         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3188                 pop_tree_stack_entry();
3189                 return;
3190         }
3192         /* Move the current line to the first tree entry. */
3193         tree_lineno = 1;
3194         entry->lineno = lineno;
3197 /* Parse output from git-ls-tree(1):
3198  *
3199  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3200  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3201  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3202  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3203  */
3205 #define SIZEOF_TREE_ATTR \
3206         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3208 #define TREE_UP_FORMAT "040000 tree %s\t.."
3210 static int
3211 tree_compare_entry(enum line_type type1, char *name1,
3212                    enum line_type type2, char *name2)
3214         if (type1 != type2) {
3215                 if (type1 == LINE_TREE_DIR)
3216                         return -1;
3217                 return 1;
3218         }
3220         return strcmp(name1, name2);
3223 static char *
3224 tree_path(struct line *line)
3226         char *path = line->data;
3228         return path + SIZEOF_TREE_ATTR;
3231 static bool
3232 tree_read(struct view *view, char *text)
3234         size_t textlen = text ? strlen(text) : 0;
3235         char buf[SIZEOF_STR];
3236         unsigned long pos;
3237         enum line_type type;
3238         bool first_read = view->lines == 0;
3240         if (!text)
3241                 return TRUE;
3242         if (textlen <= SIZEOF_TREE_ATTR)
3243                 return FALSE;
3245         type = text[STRING_SIZE("100644 ")] == 't'
3246              ? LINE_TREE_DIR : LINE_TREE_FILE;
3248         if (first_read) {
3249                 /* Add path info line */
3250                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3251                     !realloc_lines(view, view->line_size + 1) ||
3252                     !add_line_text(view, buf, LINE_DEFAULT))
3253                         return FALSE;
3255                 /* Insert "link" to parent directory. */
3256                 if (*opt_path) {
3257                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3258                             !realloc_lines(view, view->line_size + 1) ||
3259                             !add_line_text(view, buf, LINE_TREE_DIR))
3260                                 return FALSE;
3261                 }
3262         }
3264         /* Strip the path part ... */
3265         if (*opt_path) {
3266                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3267                 size_t striplen = strlen(opt_path);
3268                 char *path = text + SIZEOF_TREE_ATTR;
3270                 if (pathlen > striplen)
3271                         memmove(path, path + striplen,
3272                                 pathlen - striplen + 1);
3273         }
3275         /* Skip "Directory ..." and ".." line. */
3276         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3277                 struct line *line = &view->line[pos];
3278                 char *path1 = tree_path(line);
3279                 char *path2 = text + SIZEOF_TREE_ATTR;
3280                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3282                 if (cmp <= 0)
3283                         continue;
3285                 text = strdup(text);
3286                 if (!text)
3287                         return FALSE;
3289                 if (view->lines > pos)
3290                         memmove(&view->line[pos + 1], &view->line[pos],
3291                                 (view->lines - pos) * sizeof(*line));
3293                 line = &view->line[pos];
3294                 line->data = text;
3295                 line->type = type;
3296                 view->lines++;
3297                 return TRUE;
3298         }
3300         if (!add_line_text(view, text, type))
3301                 return FALSE;
3303         if (tree_lineno > view->lineno) {
3304                 view->lineno = tree_lineno;
3305                 tree_lineno = 0;
3306         }
3308         return TRUE;
3311 static enum request
3312 tree_request(struct view *view, enum request request, struct line *line)
3314         enum open_flags flags;
3316         if (request == REQ_VIEW_BLAME) {
3317                 char *filename = tree_path(line);
3319                 if (line->type == LINE_TREE_DIR) {
3320                         report("Cannot show blame for directory %s", opt_path);
3321                         return REQ_NONE;
3322                 }
3324                 string_copy(opt_ref, view->vid);
3325                 string_format(opt_file, "%s%s", opt_path, filename);
3326                 return request;
3327         }
3328         if (request == REQ_TREE_PARENT) {
3329                 if (*opt_path) {
3330                         /* fake 'cd  ..' */
3331                         request = REQ_ENTER;
3332                         line = &view->line[1];
3333                 } else {
3334                         /* quit view if at top of tree */
3335                         return REQ_VIEW_CLOSE;
3336                 }
3337         }
3338         if (request != REQ_ENTER)
3339                 return request;
3341         /* Cleanup the stack if the tree view is at a different tree. */
3342         while (!*opt_path && tree_stack)
3343                 pop_tree_stack_entry();
3345         switch (line->type) {
3346         case LINE_TREE_DIR:
3347                 /* Depending on whether it is a subdir or parent (updir?) link
3348                  * mangle the path buffer. */
3349                 if (line == &view->line[1] && *opt_path) {
3350                         pop_tree_stack_entry();
3352                 } else {
3353                         char *basename = tree_path(line);
3355                         push_tree_stack_entry(basename, view->lineno);
3356                 }
3358                 /* Trees and subtrees share the same ID, so they are not not
3359                  * unique like blobs. */
3360                 flags = OPEN_RELOAD;
3361                 request = REQ_VIEW_TREE;
3362                 break;
3364         case LINE_TREE_FILE:
3365                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3366                 request = REQ_VIEW_BLOB;
3367                 break;
3369         default:
3370                 return TRUE;
3371         }
3373         open_view(view, request, flags);
3374         if (request == REQ_VIEW_TREE) {
3375                 view->lineno = tree_lineno;
3376         }
3378         return REQ_NONE;
3381 static void
3382 tree_select(struct view *view, struct line *line)
3384         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3386         if (line->type == LINE_TREE_FILE) {
3387                 string_copy_rev(ref_blob, text);
3389         } else if (line->type != LINE_TREE_DIR) {
3390                 return;
3391         }
3393         string_copy_rev(view->ref, text);
3396 static struct view_ops tree_ops = {
3397         "file",
3398         NULL,
3399         tree_read,
3400         pager_draw,
3401         tree_request,
3402         pager_grep,
3403         tree_select,
3404 };
3406 static bool
3407 blob_read(struct view *view, char *line)
3409         if (!line)
3410                 return TRUE;
3411         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3414 static struct view_ops blob_ops = {
3415         "line",
3416         NULL,
3417         blob_read,
3418         pager_draw,
3419         pager_request,
3420         pager_grep,
3421         pager_select,
3422 };
3424 /*
3425  * Blame backend
3426  *
3427  * Loading the blame view is a two phase job:
3428  *
3429  *  1. File content is read either using opt_file from the
3430  *     filesystem or using git-cat-file.
3431  *  2. Then blame information is incrementally added by
3432  *     reading output from git-blame.
3433  */
3435 struct blame_commit {
3436         char id[SIZEOF_REV];            /* SHA1 ID. */
3437         char title[128];                /* First line of the commit message. */
3438         char author[75];                /* Author of the commit. */
3439         struct tm time;                 /* Date from the author ident. */
3440         char filename[128];             /* Name of file. */
3441 };
3443 struct blame {
3444         struct blame_commit *commit;
3445         unsigned int header:1;
3446         char text[1];
3447 };
3449 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3450 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3452 static bool
3453 blame_open(struct view *view)
3455         char path[SIZEOF_STR];
3456         char ref[SIZEOF_STR] = "";
3458         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3459                 return FALSE;
3461         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3462                 return FALSE;
3464         if (*opt_ref) {
3465                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3466                         return FALSE;
3467         } else {
3468                 view->pipe = fopen(opt_file, "r");
3469                 if (!view->pipe &&
3470                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3471                         return FALSE;
3472         }
3474         if (!view->pipe)
3475                 view->pipe = popen(view->cmd, "r");
3476         if (!view->pipe)
3477                 return FALSE;
3479         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3480                 return FALSE;
3482         string_format(view->ref, "%s ...", opt_file);
3483         string_copy_rev(view->vid, opt_file);
3484         set_nonblocking_input(TRUE);
3486         if (view->line) {
3487                 int i;
3489                 for (i = 0; i < view->lines; i++)
3490                         free(view->line[i].data);
3491                 free(view->line);
3492         }
3494         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3495         view->offset = view->lines  = view->lineno = 0;
3496         view->line = NULL;
3497         view->start_time = time(NULL);
3499         return TRUE;
3502 static struct blame_commit *
3503 get_blame_commit(struct view *view, const char *id)
3505         size_t i;
3507         for (i = 0; i < view->lines; i++) {
3508                 struct blame *blame = view->line[i].data;
3510                 if (!blame->commit)
3511                         continue;
3513                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3514                         return blame->commit;
3515         }
3517         {
3518                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3520                 if (commit)
3521                         string_ncopy(commit->id, id, SIZEOF_REV);
3522                 return commit;
3523         }
3526 static bool
3527 parse_number(char **posref, size_t *number, size_t min, size_t max)
3529         char *pos = *posref;
3531         *posref = NULL;
3532         pos = strchr(pos + 1, ' ');
3533         if (!pos || !isdigit(pos[1]))
3534                 return FALSE;
3535         *number = atoi(pos + 1);
3536         if (*number < min || *number > max)
3537                 return FALSE;
3539         *posref = pos;
3540         return TRUE;
3543 static struct blame_commit *
3544 parse_blame_commit(struct view *view, char *text, int *blamed)
3546         struct blame_commit *commit;
3547         struct blame *blame;
3548         char *pos = text + SIZEOF_REV - 1;
3549         size_t lineno;
3550         size_t group;
3552         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3553                 return NULL;
3555         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3556             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3557                 return NULL;
3559         commit = get_blame_commit(view, text);
3560         if (!commit)
3561                 return NULL;
3563         *blamed += group;
3564         while (group--) {
3565                 struct line *line = &view->line[lineno + group - 1];
3567                 blame = line->data;
3568                 blame->commit = commit;
3569                 blame->header = !group;
3570                 line->dirty = 1;
3571         }
3573         return commit;
3576 static bool
3577 blame_read_file(struct view *view, char *line)
3579         if (!line) {
3580                 FILE *pipe = NULL;
3582                 if (view->lines > 0)
3583                         pipe = popen(view->cmd, "r");
3584                 else if (!view->parent)
3585                         die("No blame exist for %s", view->vid);
3586                 view->cmd[0] = 0;
3587                 if (!pipe) {
3588                         report("Failed to load blame data");
3589                         return TRUE;
3590                 }
3592                 fclose(view->pipe);
3593                 view->pipe = pipe;
3594                 return FALSE;
3596         } else {
3597                 size_t linelen = strlen(line);
3598                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3600                 blame->commit = NULL;
3601                 strncpy(blame->text, line, linelen);
3602                 blame->text[linelen] = 0;
3603                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3604         }
3607 static bool
3608 match_blame_header(const char *name, char **line)
3610         size_t namelen = strlen(name);
3611         bool matched = !strncmp(name, *line, namelen);
3613         if (matched)
3614                 *line += namelen;
3616         return matched;
3619 static bool
3620 blame_read(struct view *view, char *line)
3622         static struct blame_commit *commit = NULL;
3623         static int blamed = 0;
3624         static time_t author_time;
3626         if (*view->cmd)
3627                 return blame_read_file(view, line);
3629         if (!line) {
3630                 /* Reset all! */
3631                 commit = NULL;
3632                 blamed = 0;
3633                 string_format(view->ref, "%s", view->vid);
3634                 if (view_is_displayed(view)) {
3635                         update_view_title(view);
3636                         redraw_view_from(view, 0);
3637                 }
3638                 return TRUE;
3639         }
3641         if (!commit) {
3642                 commit = parse_blame_commit(view, line, &blamed);
3643                 string_format(view->ref, "%s %2d%%", view->vid,
3644                               blamed * 100 / view->lines);
3646         } else if (match_blame_header("author ", &line)) {
3647                 string_ncopy(commit->author, line, strlen(line));
3649         } else if (match_blame_header("author-time ", &line)) {
3650                 author_time = (time_t) atol(line);
3652         } else if (match_blame_header("author-tz ", &line)) {
3653                 long tz;
3655                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3656                 tz += ('0' - line[2]) * 60 * 60;
3657                 tz += ('0' - line[3]) * 60;
3658                 tz += ('0' - line[4]) * 60;
3660                 if (line[0] == '-')
3661                         tz = -tz;
3663                 author_time -= tz;
3664                 gmtime_r(&author_time, &commit->time);
3666         } else if (match_blame_header("summary ", &line)) {
3667                 string_ncopy(commit->title, line, strlen(line));
3669         } else if (match_blame_header("filename ", &line)) {
3670                 string_ncopy(commit->filename, line, strlen(line));
3671                 commit = NULL;
3672         }
3674         return TRUE;
3677 static bool
3678 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3680         struct blame *blame = line->data;
3681         struct tm *time = NULL;
3682         char *id = NULL, *author = NULL;
3684         if (blame->commit && *blame->commit->filename) {
3685                 id = blame->commit->id;
3686                 author = blame->commit->author;
3687                 time = &blame->commit->time;
3688         }
3690         if (opt_date && draw_date(view, time))
3691                 return TRUE;
3693         if (opt_author &&
3694             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3695                 return TRUE;
3697         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3698                 return TRUE;
3700         if (draw_lineno(view, lineno))
3701                 return TRUE;
3703         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3704         return TRUE;
3707 static enum request
3708 blame_request(struct view *view, enum request request, struct line *line)
3710         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3711         struct blame *blame = line->data;
3713         switch (request) {
3714         case REQ_ENTER:
3715                 if (!blame->commit) {
3716                         report("No commit loaded yet");
3717                         break;
3718                 }
3720                 if (!strcmp(blame->commit->id, NULL_ID)) {
3721                         char path[SIZEOF_STR];
3723                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3724                                 break;
3725                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3726                 }
3728                 open_view(view, REQ_VIEW_DIFF, flags);
3729                 break;
3731         default:
3732                 return request;
3733         }
3735         return REQ_NONE;
3738 static bool
3739 blame_grep(struct view *view, struct line *line)
3741         struct blame *blame = line->data;
3742         struct blame_commit *commit = blame->commit;
3743         regmatch_t pmatch;
3745 #define MATCH(text, on)                                                 \
3746         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3748         if (commit) {
3749                 char buf[DATE_COLS + 1];
3751                 if (MATCH(commit->title, 1) ||
3752                     MATCH(commit->author, opt_author) ||
3753                     MATCH(commit->id, opt_date))
3754                         return TRUE;
3756                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3757                     MATCH(buf, 1))
3758                         return TRUE;
3759         }
3761         return MATCH(blame->text, 1);
3763 #undef MATCH
3766 static void
3767 blame_select(struct view *view, struct line *line)
3769         struct blame *blame = line->data;
3770         struct blame_commit *commit = blame->commit;
3772         if (!commit)
3773                 return;
3775         if (!strcmp(commit->id, NULL_ID))
3776                 string_ncopy(ref_commit, "HEAD", 4);
3777         else
3778                 string_copy_rev(ref_commit, commit->id);
3781 static struct view_ops blame_ops = {
3782         "line",
3783         blame_open,
3784         blame_read,
3785         blame_draw,
3786         blame_request,
3787         blame_grep,
3788         blame_select,
3789 };
3791 /*
3792  * Status backend
3793  */
3795 struct status {
3796         char status;
3797         struct {
3798                 mode_t mode;
3799                 char rev[SIZEOF_REV];
3800                 char name[SIZEOF_STR];
3801         } old;
3802         struct {
3803                 mode_t mode;
3804                 char rev[SIZEOF_REV];
3805                 char name[SIZEOF_STR];
3806         } new;
3807 };
3809 static char status_onbranch[SIZEOF_STR];
3810 static struct status stage_status;
3811 static enum line_type stage_line_type;
3812 static size_t stage_chunks;
3813 static int *stage_chunk;
3815 /* Get fields from the diff line:
3816  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3817  */
3818 static inline bool
3819 status_get_diff(struct status *file, char *buf, size_t bufsize)
3821         char *old_mode = buf +  1;
3822         char *new_mode = buf +  8;
3823         char *old_rev  = buf + 15;
3824         char *new_rev  = buf + 56;
3825         char *status   = buf + 97;
3827         if (bufsize < 99 ||
3828             old_mode[-1] != ':' ||
3829             new_mode[-1] != ' ' ||
3830             old_rev[-1]  != ' ' ||
3831             new_rev[-1]  != ' ' ||
3832             status[-1]   != ' ')
3833                 return FALSE;
3835         file->status = *status;
3837         string_copy_rev(file->old.rev, old_rev);
3838         string_copy_rev(file->new.rev, new_rev);
3840         file->old.mode = strtoul(old_mode, NULL, 8);
3841         file->new.mode = strtoul(new_mode, NULL, 8);
3843         file->old.name[0] = file->new.name[0] = 0;
3845         return TRUE;
3848 static bool
3849 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3851         struct status *file = NULL;
3852         struct status *unmerged = NULL;
3853         char buf[SIZEOF_STR * 4];
3854         size_t bufsize = 0;
3855         FILE *pipe;
3857         pipe = popen(cmd, "r");
3858         if (!pipe)
3859                 return FALSE;
3861         add_line_data(view, NULL, type);
3863         while (!feof(pipe) && !ferror(pipe)) {
3864                 char *sep;
3865                 size_t readsize;
3867                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3868                 if (!readsize)
3869                         break;
3870                 bufsize += readsize;
3872                 /* Process while we have NUL chars. */
3873                 while ((sep = memchr(buf, 0, bufsize))) {
3874                         size_t sepsize = sep - buf + 1;
3876                         if (!file) {
3877                                 if (!realloc_lines(view, view->line_size + 1))
3878                                         goto error_out;
3880                                 file = calloc(1, sizeof(*file));
3881                                 if (!file)
3882                                         goto error_out;
3884                                 add_line_data(view, file, type);
3885                         }
3887                         /* Parse diff info part. */
3888                         if (status) {
3889                                 file->status = status;
3890                                 if (status == 'A')
3891                                         string_copy(file->old.rev, NULL_ID);
3893                         } else if (!file->status) {
3894                                 if (!status_get_diff(file, buf, sepsize))
3895                                         goto error_out;
3897                                 bufsize -= sepsize;
3898                                 memmove(buf, sep + 1, bufsize);
3900                                 sep = memchr(buf, 0, bufsize);
3901                                 if (!sep)
3902                                         break;
3903                                 sepsize = sep - buf + 1;
3905                                 /* Collapse all 'M'odified entries that
3906                                  * follow a associated 'U'nmerged entry.
3907                                  */
3908                                 if (file->status == 'U') {
3909                                         unmerged = file;
3911                                 } else if (unmerged) {
3912                                         int collapse = !strcmp(buf, unmerged->new.name);
3914                                         unmerged = NULL;
3915                                         if (collapse) {
3916                                                 free(file);
3917                                                 view->lines--;
3918                                                 continue;
3919                                         }
3920                                 }
3921                         }
3923                         /* Grab the old name for rename/copy. */
3924                         if (!*file->old.name &&
3925                             (file->status == 'R' || file->status == 'C')) {
3926                                 sepsize = sep - buf + 1;
3927                                 string_ncopy(file->old.name, buf, sepsize);
3928                                 bufsize -= sepsize;
3929                                 memmove(buf, sep + 1, bufsize);
3931                                 sep = memchr(buf, 0, bufsize);
3932                                 if (!sep)
3933                                         break;
3934                                 sepsize = sep - buf + 1;
3935                         }
3937                         /* git-ls-files just delivers a NUL separated
3938                          * list of file names similar to the second half
3939                          * of the git-diff-* output. */
3940                         string_ncopy(file->new.name, buf, sepsize);
3941                         if (!*file->old.name)
3942                                 string_copy(file->old.name, file->new.name);
3943                         bufsize -= sepsize;
3944                         memmove(buf, sep + 1, bufsize);
3945                         file = NULL;
3946                 }
3947         }
3949         if (ferror(pipe)) {
3950 error_out:
3951                 pclose(pipe);
3952                 return FALSE;
3953         }
3955         if (!view->line[view->lines - 1].data)
3956                 add_line_data(view, NULL, LINE_STAT_NONE);
3958         pclose(pipe);
3959         return TRUE;
3962 /* Don't show unmerged entries in the staged section. */
3963 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3964 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3965 #define STATUS_LIST_OTHER_CMD \
3966         "git ls-files -z --others --exclude-per-directory=.gitignore"
3967 #define STATUS_LIST_NO_HEAD_CMD \
3968         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3970 #define STATUS_DIFF_INDEX_SHOW_CMD \
3971         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3973 #define STATUS_DIFF_FILES_SHOW_CMD \
3974         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3976 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3977         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3979 /* First parse staged info using git-diff-index(1), then parse unstaged
3980  * info using git-diff-files(1), and finally untracked files using
3981  * git-ls-files(1). */
3982 static bool
3983 status_open(struct view *view)
3985         struct stat statbuf;
3986         char exclude[SIZEOF_STR];
3987         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3988         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3989         unsigned long prev_lineno = view->lineno;
3990         char indexstatus = 0;
3991         size_t i;
3993         for (i = 0; i < view->lines; i++)
3994                 free(view->line[i].data);
3995         free(view->line);
3996         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3997         view->line = NULL;
3999         if (!realloc_lines(view, view->line_size + 7))
4000                 return FALSE;
4002         add_line_data(view, NULL, LINE_STAT_HEAD);
4003         if (opt_no_head)
4004                 string_copy(status_onbranch, "Initial commit");
4005         else if (!*opt_head)
4006                 string_copy(status_onbranch, "Not currently on any branch");
4007         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4008                 return FALSE;
4010         if (opt_no_head) {
4011                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
4012                 indexstatus = 'A';
4013         }
4015         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4016                 return FALSE;
4018         if (stat(exclude, &statbuf) >= 0) {
4019                 size_t cmdsize = strlen(othercmd);
4021                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4022                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4023                         return FALSE;
4025                 cmdsize = strlen(indexcmd);
4026                 if (opt_no_head &&
4027                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4028                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4029                         return FALSE;
4030         }
4032         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4034         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4035             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4036             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4037                 return FALSE;
4039         /* If all went well restore the previous line number to stay in
4040          * the context or select a line with something that can be
4041          * updated. */
4042         if (prev_lineno >= view->lines)
4043                 prev_lineno = view->lines - 1;
4044         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4045                 prev_lineno++;
4046         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4047                 prev_lineno--;
4049         /* If the above fails, always skip the "On branch" line. */
4050         if (prev_lineno < view->lines)
4051                 view->lineno = prev_lineno;
4052         else
4053                 view->lineno = 1;
4055         if (view->lineno < view->offset)
4056                 view->offset = view->lineno;
4057         else if (view->offset + view->height <= view->lineno)
4058                 view->offset = view->lineno - view->height + 1;
4060         return TRUE;
4063 static bool
4064 status_draw(struct view *view, struct line *line, unsigned int lineno)
4066         struct status *status = line->data;
4067         enum line_type type;
4068         char *text;
4070         if (!status) {
4071                 switch (line->type) {
4072                 case LINE_STAT_STAGED:
4073                         type = LINE_STAT_SECTION;
4074                         text = "Changes to be committed:";
4075                         break;
4077                 case LINE_STAT_UNSTAGED:
4078                         type = LINE_STAT_SECTION;
4079                         text = "Changed but not updated:";
4080                         break;
4082                 case LINE_STAT_UNTRACKED:
4083                         type = LINE_STAT_SECTION;
4084                         text = "Untracked files:";
4085                         break;
4087                 case LINE_STAT_NONE:
4088                         type = LINE_DEFAULT;
4089                         text = "    (no files)";
4090                         break;
4092                 case LINE_STAT_HEAD:
4093                         type = LINE_STAT_HEAD;
4094                         text = status_onbranch;
4095                         break;
4097                 default:
4098                         return FALSE;
4099                 }
4100         } else {
4101                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4103                 buf[0] = status->status;
4104                 if (draw_text(view, line->type, buf, TRUE))
4105                         return TRUE;
4106                 type = LINE_DEFAULT;
4107                 text = status->new.name;
4108         }
4110         draw_text(view, type, text, TRUE);
4111         return TRUE;
4114 static enum request
4115 status_enter(struct view *view, struct line *line)
4117         struct status *status = line->data;
4118         char oldpath[SIZEOF_STR] = "";
4119         char newpath[SIZEOF_STR] = "";
4120         char *info;
4121         size_t cmdsize = 0;
4122         enum open_flags split;
4124         if (line->type == LINE_STAT_NONE ||
4125             (!status && line[1].type == LINE_STAT_NONE)) {
4126                 report("No file to diff");
4127                 return REQ_NONE;
4128         }
4130         if (status) {
4131                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4132                         return REQ_QUIT;
4133                 /* Diffs for unmerged entries are empty when pasing the
4134                  * new path, so leave it empty. */
4135                 if (status->status != 'U' &&
4136                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4137                         return REQ_QUIT;
4138         }
4140         if (opt_cdup[0] &&
4141             line->type != LINE_STAT_UNTRACKED &&
4142             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4143                 return REQ_QUIT;
4145         switch (line->type) {
4146         case LINE_STAT_STAGED:
4147                 if (opt_no_head) {
4148                         if (!string_format_from(opt_cmd, &cmdsize,
4149                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4150                                                 newpath))
4151                                 return REQ_QUIT;
4152                 } else {
4153                         if (!string_format_from(opt_cmd, &cmdsize,
4154                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4155                                                 oldpath, newpath))
4156                                 return REQ_QUIT;
4157                 }
4159                 if (status)
4160                         info = "Staged changes to %s";
4161                 else
4162                         info = "Staged changes";
4163                 break;
4165         case LINE_STAT_UNSTAGED:
4166                 if (!string_format_from(opt_cmd, &cmdsize,
4167                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4168                         return REQ_QUIT;
4169                 if (status)
4170                         info = "Unstaged changes to %s";
4171                 else
4172                         info = "Unstaged changes";
4173                 break;
4175         case LINE_STAT_UNTRACKED:
4176                 if (opt_pipe)
4177                         return REQ_QUIT;
4179                 if (!status) {
4180                         report("No file to show");
4181                         return REQ_NONE;
4182                 }
4184                 opt_pipe = fopen(status->new.name, "r");
4185                 info = "Untracked file %s";
4186                 break;
4188         case LINE_STAT_HEAD:
4189                 return REQ_NONE;
4191         default:
4192                 die("line type %d not handled in switch", line->type);
4193         }
4195         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4196         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4197         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4198                 if (status) {
4199                         stage_status = *status;
4200                 } else {
4201                         memset(&stage_status, 0, sizeof(stage_status));
4202                 }
4204                 stage_line_type = line->type;
4205                 stage_chunks = 0;
4206                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4207         }
4209         return REQ_NONE;
4212 static bool
4213 status_exists(struct status *status, enum line_type type)
4215         struct view *view = VIEW(REQ_VIEW_STATUS);
4216         struct line *line;
4218         for (line = view->line; line < view->line + view->lines; line++) {
4219                 struct status *pos = line->data;
4221                 if (line->type == type && pos &&
4222                     !strcmp(status->new.name, pos->new.name))
4223                         return TRUE;
4224         }
4226         return FALSE;
4230 static FILE *
4231 status_update_prepare(enum line_type type)
4233         char cmd[SIZEOF_STR];
4234         size_t cmdsize = 0;
4236         if (opt_cdup[0] &&
4237             type != LINE_STAT_UNTRACKED &&
4238             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4239                 return NULL;
4241         switch (type) {
4242         case LINE_STAT_STAGED:
4243                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4244                 break;
4246         case LINE_STAT_UNSTAGED:
4247         case LINE_STAT_UNTRACKED:
4248                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4249                 break;
4251         default:
4252                 die("line type %d not handled in switch", type);
4253         }
4255         return popen(cmd, "w");
4258 static bool
4259 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4261         char buf[SIZEOF_STR];
4262         size_t bufsize = 0;
4263         size_t written = 0;
4265         switch (type) {
4266         case LINE_STAT_STAGED:
4267                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4268                                         status->old.mode,
4269                                         status->old.rev,
4270                                         status->old.name, 0))
4271                         return FALSE;
4272                 break;
4274         case LINE_STAT_UNSTAGED:
4275         case LINE_STAT_UNTRACKED:
4276                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4277                         return FALSE;
4278                 break;
4280         default:
4281                 die("line type %d not handled in switch", type);
4282         }
4284         while (!ferror(pipe) && written < bufsize) {
4285                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4286         }
4288         return written == bufsize;
4291 static bool
4292 status_update_file(struct status *status, enum line_type type)
4294         FILE *pipe = status_update_prepare(type);
4295         bool result;
4297         if (!pipe)
4298                 return FALSE;
4300         result = status_update_write(pipe, status, type);
4301         pclose(pipe);
4302         return result;
4305 static bool
4306 status_update_files(struct view *view, struct line *line)
4308         FILE *pipe = status_update_prepare(line->type);
4309         bool result = TRUE;
4310         struct line *pos = view->line + view->lines;
4311         int files = 0;
4312         int file, done;
4314         if (!pipe)
4315                 return FALSE;
4317         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4318                 files++;
4320         for (file = 0, done = 0; result && file < files; line++, file++) {
4321                 int almost_done = file * 100 / files;
4323                 if (almost_done > done) {
4324                         done = almost_done;
4325                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4326                                       file, files, done);
4327                         update_view_title(view);
4328                 }
4329                 result = status_update_write(pipe, line->data, line->type);
4330         }
4332         pclose(pipe);
4333         return result;
4336 static bool
4337 status_update(struct view *view)
4339         struct line *line = &view->line[view->lineno];
4341         assert(view->lines);
4343         if (!line->data) {
4344                 /* This should work even for the "On branch" line. */
4345                 if (line < view->line + view->lines && !line[1].data) {
4346                         report("Nothing to update");
4347                         return FALSE;
4348                 }
4350                 if (!status_update_files(view, line + 1)) {
4351                         report("Failed to update file status");
4352                         return FALSE;
4353                 }
4355         } else if (!status_update_file(line->data, line->type)) {
4356                 report("Failed to update file status");
4357                 return FALSE;
4358         }
4360         return TRUE;
4363 static bool
4364 status_checkout(struct view *view)
4366         struct line *line = &view->line[view->lineno];
4368         assert(view->lines);
4370         if (!line->data || line->type != LINE_STAT_UNSTAGED) {
4371                 /* This should work even for the "On branch" line. */
4372                 if (line < view->line + view->lines && !line[1].data) {
4373                         report("Nothing to checkout");
4374                 } else if (line->type == LINE_STAT_UNTRACKED) {
4375                         report("Cannot checkout untracked files");
4376                 } else if (line->type == LINE_STAT_STAGED) {
4377                         report("Cannot checkout staged files");
4378                 } else {
4379                         report("Cannot checkout multiple files");
4380                 }
4381                 return FALSE;
4383         } else {
4384                 struct status *status = line->data;
4385                 char cmd[SIZEOF_STR];
4386                 char file_sq[SIZEOF_STR];
4388                 if (sq_quote(file_sq, 0, status->old.name) < sizeof(file_sq) &&
4389                     string_format(cmd, "git checkout %s%s", opt_cdup, file_sq)) {
4390                         run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4391                 }
4393                 return TRUE;
4394         }
4397 static enum request
4398 status_request(struct view *view, enum request request, struct line *line)
4400         struct status *status = line->data;
4402         switch (request) {
4403         case REQ_STATUS_UPDATE:
4404                 if (!status_update(view))
4405                         return REQ_NONE;
4406                 break;
4408         case REQ_STATUS_CHECKOUT:
4409                 if (!status_checkout(view))
4410                         return REQ_NONE;
4411                 break;
4413         case REQ_STATUS_MERGE:
4414                 if (!status || status->status != 'U') {
4415                         report("Merging only possible for files with unmerged status ('U').");
4416                         return REQ_NONE;
4417                 }
4418                 open_mergetool(status->new.name);
4419                 break;
4421         case REQ_EDIT:
4422                 if (!status)
4423                         return request;
4425                 open_editor(status->status != '?', status->new.name);
4426                 break;
4428         case REQ_VIEW_BLAME:
4429                 if (status) {
4430                         string_copy(opt_file, status->new.name);
4431                         opt_ref[0] = 0;
4432                 }
4433                 return request;
4435         case REQ_ENTER:
4436                 /* After returning the status view has been split to
4437                  * show the stage view. No further reloading is
4438                  * necessary. */
4439                 status_enter(view, line);
4440                 return REQ_NONE;
4442         case REQ_REFRESH:
4443                 /* Simply reload the view. */
4444                 break;
4446         default:
4447                 return request;
4448         }
4450         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4452         return REQ_NONE;
4455 static void
4456 status_select(struct view *view, struct line *line)
4458         struct status *status = line->data;
4459         char file[SIZEOF_STR] = "all files";
4460         char *text;
4461         char *key;
4463         if (status && !string_format(file, "'%s'", status->new.name))
4464                 return;
4466         if (!status && line[1].type == LINE_STAT_NONE)
4467                 line++;
4469         switch (line->type) {
4470         case LINE_STAT_STAGED:
4471                 text = "Press %s to unstage %s for commit";
4472                 break;
4474         case LINE_STAT_UNSTAGED:
4475                 text = "Press %s to stage %s for commit";
4476                 break;
4478         case LINE_STAT_UNTRACKED:
4479                 text = "Press %s to stage %s for addition";
4480                 break;
4482         case LINE_STAT_HEAD:
4483         case LINE_STAT_NONE:
4484                 text = "Nothing to update";
4485                 break;
4487         default:
4488                 die("line type %d not handled in switch", line->type);
4489         }
4491         if (status && status->status == 'U') {
4492                 text = "Press %s to resolve conflict in %s";
4493                 key = get_key(REQ_STATUS_MERGE);
4495         } else {
4496                 key = get_key(REQ_STATUS_UPDATE);
4497         }
4499         string_format(view->ref, text, key, file);
4502 static bool
4503 status_grep(struct view *view, struct line *line)
4505         struct status *status = line->data;
4506         enum { S_STATUS, S_NAME, S_END } state;
4507         char buf[2] = "?";
4508         regmatch_t pmatch;
4510         if (!status)
4511                 return FALSE;
4513         for (state = S_STATUS; state < S_END; state++) {
4514                 char *text;
4516                 switch (state) {
4517                 case S_NAME:    text = status->new.name;        break;
4518                 case S_STATUS:
4519                         buf[0] = status->status;
4520                         text = buf;
4521                         break;
4523                 default:
4524                         return FALSE;
4525                 }
4527                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4528                         return TRUE;
4529         }
4531         return FALSE;
4534 static struct view_ops status_ops = {
4535         "file",
4536         status_open,
4537         NULL,
4538         status_draw,
4539         status_request,
4540         status_grep,
4541         status_select,
4542 };
4545 static bool
4546 stage_diff_line(FILE *pipe, struct line *line)
4548         char *buf = line->data;
4549         size_t bufsize = strlen(buf);
4550         size_t written = 0;
4552         while (!ferror(pipe) && written < bufsize) {
4553                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4554         }
4556         fputc('\n', pipe);
4558         return written == bufsize;
4561 static bool
4562 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4564         while (line < end) {
4565                 if (!stage_diff_line(pipe, line++))
4566                         return FALSE;
4567                 if (line->type == LINE_DIFF_CHUNK ||
4568                     line->type == LINE_DIFF_HEADER)
4569                         break;
4570         }
4572         return TRUE;
4575 static struct line *
4576 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4578         for (; view->line < line; line--)
4579                 if (line->type == type)
4580                         return line;
4582         return NULL;
4585 static bool
4586 stage_update_chunk(struct view *view, struct line *chunk)
4588         char cmd[SIZEOF_STR];
4589         size_t cmdsize = 0;
4590         struct line *diff_hdr;
4591         FILE *pipe;
4593         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4594         if (!diff_hdr)
4595                 return FALSE;
4597         if (opt_cdup[0] &&
4598             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4599                 return FALSE;
4601         if (!string_format_from(cmd, &cmdsize,
4602                                 "git apply --whitespace=nowarn --cached %s - && "
4603                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4604                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4605                 return FALSE;
4607         pipe = popen(cmd, "w");
4608         if (!pipe)
4609                 return FALSE;
4611         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4612             !stage_diff_write(pipe, chunk, view->line + view->lines))
4613                 chunk = NULL;
4615         pclose(pipe);
4617         return chunk ? TRUE : FALSE;
4620 static bool
4621 stage_update(struct view *view, struct line *line)
4623         struct line *chunk = NULL;
4625         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4626                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4628         if (chunk) {
4629                 if (!stage_update_chunk(view, chunk)) {
4630                         report("Failed to apply chunk");
4631                         return FALSE;
4632                 }
4634         } else if (!stage_status.status) {
4635                 view = VIEW(REQ_VIEW_STATUS);
4637                 for (line = view->line; line < view->line + view->lines; line++)
4638                         if (line->type == stage_line_type)
4639                                 break;
4641                 if (!status_update_files(view, line + 1)) {
4642                         report("Failed to update files");
4643                         return FALSE;
4644                 }
4646         } else if (!status_update_file(&stage_status, stage_line_type)) {
4647                 report("Failed to update file");
4648                 return FALSE;
4649         }
4651         return TRUE;
4654 static void
4655 stage_next(struct view *view, struct line *line)
4657         int i;
4659         if (!stage_chunks) {
4660                 static size_t alloc = 0;
4661                 int *tmp;
4663                 for (line = view->line; line < view->line + view->lines; line++) {
4664                         if (line->type != LINE_DIFF_CHUNK)
4665                                 continue;
4667                         tmp = realloc_items(stage_chunk, &alloc,
4668                                             stage_chunks, sizeof(*tmp));
4669                         if (!tmp) {
4670                                 report("Allocation failure");
4671                                 return;
4672                         }
4674                         stage_chunk = tmp;
4675                         stage_chunk[stage_chunks++] = line - view->line;
4676                 }
4677         }
4679         for (i = 0; i < stage_chunks; i++) {
4680                 if (stage_chunk[i] > view->lineno) {
4681                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4682                         report("Chunk %d of %d", i + 1, stage_chunks);
4683                         return;
4684                 }
4685         }
4687         report("No next chunk found");
4690 static enum request
4691 stage_request(struct view *view, enum request request, struct line *line)
4693         switch (request) {
4694         case REQ_STATUS_UPDATE:
4695                 if (!stage_update(view, line))
4696                         return REQ_NONE;
4697                 break;
4699         case REQ_STAGE_NEXT:
4700                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4701                         report("File is untracked; press %s to add",
4702                                get_key(REQ_STATUS_UPDATE));
4703                         return REQ_NONE;
4704                 }
4705                 stage_next(view, line);
4706                 return REQ_NONE;
4708         case REQ_EDIT:
4709                 if (!stage_status.new.name[0])
4710                         return request;
4712                 open_editor(stage_status.status != '?', stage_status.new.name);
4713                 break;
4715         case REQ_REFRESH:
4716                 /* Reload everything ... */
4717                 break;
4719         case REQ_VIEW_BLAME:
4720                 if (stage_status.new.name[0]) {
4721                         string_copy(opt_file, stage_status.new.name);
4722                         opt_ref[0] = 0;
4723                 }
4724                 return request;
4726         case REQ_ENTER:
4727                 return pager_request(view, request, line);
4729         default:
4730                 return request;
4731         }
4733         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4735         /* Check whether the staged entry still exists, and close the
4736          * stage view if it doesn't. */
4737         if (!status_exists(&stage_status, stage_line_type))
4738                 return REQ_VIEW_CLOSE;
4740         if (stage_line_type == LINE_STAT_UNTRACKED)
4741                 opt_pipe = fopen(stage_status.new.name, "r");
4742         else
4743                 string_copy(opt_cmd, view->cmd);
4744         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4746         return REQ_NONE;
4749 static struct view_ops stage_ops = {
4750         "line",
4751         NULL,
4752         pager_read,
4753         pager_draw,
4754         stage_request,
4755         pager_grep,
4756         pager_select,
4757 };
4760 /*
4761  * Revision graph
4762  */
4764 struct commit {
4765         char id[SIZEOF_REV];            /* SHA1 ID. */
4766         char title[128];                /* First line of the commit message. */
4767         char author[75];                /* Author of the commit. */
4768         struct tm time;                 /* Date from the author ident. */
4769         struct ref **refs;              /* Repository references. */
4770         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4771         size_t graph_size;              /* The width of the graph array. */
4772         bool has_parents;               /* Rewritten --parents seen. */
4773 };
4775 /* Size of rev graph with no  "padding" columns */
4776 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4778 struct rev_graph {
4779         struct rev_graph *prev, *next, *parents;
4780         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4781         size_t size;
4782         struct commit *commit;
4783         size_t pos;
4784         unsigned int boundary:1;
4785 };
4787 /* Parents of the commit being visualized. */
4788 static struct rev_graph graph_parents[4];
4790 /* The current stack of revisions on the graph. */
4791 static struct rev_graph graph_stacks[4] = {
4792         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4793         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4794         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4795         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4796 };
4798 static inline bool
4799 graph_parent_is_merge(struct rev_graph *graph)
4801         return graph->parents->size > 1;
4804 static inline void
4805 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4807         struct commit *commit = graph->commit;
4809         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4810                 commit->graph[commit->graph_size++] = symbol;
4813 static void
4814 clear_rev_graph(struct rev_graph *graph)
4816         graph->boundary = 0;
4817         graph->size = graph->pos = 0;
4818         graph->commit = NULL;
4819         memset(graph->parents, 0, sizeof(*graph->parents));
4822 static void
4823 done_rev_graph(struct rev_graph *graph)
4825         if (graph_parent_is_merge(graph) &&
4826             graph->pos < graph->size - 1 &&
4827             graph->next->size == graph->size + graph->parents->size - 1) {
4828                 size_t i = graph->pos + graph->parents->size - 1;
4830                 graph->commit->graph_size = i * 2;
4831                 while (i < graph->next->size - 1) {
4832                         append_to_rev_graph(graph, ' ');
4833                         append_to_rev_graph(graph, '\\');
4834                         i++;
4835                 }
4836         }
4838         clear_rev_graph(graph);
4841 static void
4842 push_rev_graph(struct rev_graph *graph, char *parent)
4844         int i;
4846         /* "Collapse" duplicate parents lines.
4847          *
4848          * FIXME: This needs to also update update the drawn graph but
4849          * for now it just serves as a method for pruning graph lines. */
4850         for (i = 0; i < graph->size; i++)
4851                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4852                         return;
4854         if (graph->size < SIZEOF_REVITEMS) {
4855                 string_copy_rev(graph->rev[graph->size++], parent);
4856         }
4859 static chtype
4860 get_rev_graph_symbol(struct rev_graph *graph)
4862         chtype symbol;
4864         if (graph->boundary)
4865                 symbol = REVGRAPH_BOUND;
4866         else if (graph->parents->size == 0)
4867                 symbol = REVGRAPH_INIT;
4868         else if (graph_parent_is_merge(graph))
4869                 symbol = REVGRAPH_MERGE;
4870         else if (graph->pos >= graph->size)
4871                 symbol = REVGRAPH_BRANCH;
4872         else
4873                 symbol = REVGRAPH_COMMIT;
4875         return symbol;
4878 static void
4879 draw_rev_graph(struct rev_graph *graph)
4881         struct rev_filler {
4882                 chtype separator, line;
4883         };
4884         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4885         static struct rev_filler fillers[] = {
4886                 { ' ',  '|' },
4887                 { '`',  '.' },
4888                 { '\'', ' ' },
4889                 { '/',  ' ' },
4890         };
4891         chtype symbol = get_rev_graph_symbol(graph);
4892         struct rev_filler *filler;
4893         size_t i;
4895         if (opt_line_graphics)
4896                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4898         filler = &fillers[DEFAULT];
4900         for (i = 0; i < graph->pos; i++) {
4901                 append_to_rev_graph(graph, filler->line);
4902                 if (graph_parent_is_merge(graph->prev) &&
4903                     graph->prev->pos == i)
4904                         filler = &fillers[RSHARP];
4906                 append_to_rev_graph(graph, filler->separator);
4907         }
4909         /* Place the symbol for this revision. */
4910         append_to_rev_graph(graph, symbol);
4912         if (graph->prev->size > graph->size)
4913                 filler = &fillers[RDIAG];
4914         else
4915                 filler = &fillers[DEFAULT];
4917         i++;
4919         for (; i < graph->size; i++) {
4920                 append_to_rev_graph(graph, filler->separator);
4921                 append_to_rev_graph(graph, filler->line);
4922                 if (graph_parent_is_merge(graph->prev) &&
4923                     i < graph->prev->pos + graph->parents->size)
4924                         filler = &fillers[RSHARP];
4925                 if (graph->prev->size > graph->size)
4926                         filler = &fillers[LDIAG];
4927         }
4929         if (graph->prev->size > graph->size) {
4930                 append_to_rev_graph(graph, filler->separator);
4931                 if (filler->line != ' ')
4932                         append_to_rev_graph(graph, filler->line);
4933         }
4936 /* Prepare the next rev graph */
4937 static void
4938 prepare_rev_graph(struct rev_graph *graph)
4940         size_t i;
4942         /* First, traverse all lines of revisions up to the active one. */
4943         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4944                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4945                         break;
4947                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4948         }
4950         /* Interleave the new revision parent(s). */
4951         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4952                 push_rev_graph(graph->next, graph->parents->rev[i]);
4954         /* Lastly, put any remaining revisions. */
4955         for (i = graph->pos + 1; i < graph->size; i++)
4956                 push_rev_graph(graph->next, graph->rev[i]);
4959 static void
4960 update_rev_graph(struct rev_graph *graph)
4962         /* If this is the finalizing update ... */
4963         if (graph->commit)
4964                 prepare_rev_graph(graph);
4966         /* Graph visualization needs a one rev look-ahead,
4967          * so the first update doesn't visualize anything. */
4968         if (!graph->prev->commit)
4969                 return;
4971         draw_rev_graph(graph->prev);
4972         done_rev_graph(graph->prev->prev);
4976 /*
4977  * Main view backend
4978  */
4980 static bool
4981 main_draw(struct view *view, struct line *line, unsigned int lineno)
4983         struct commit *commit = line->data;
4985         if (!*commit->author)
4986                 return FALSE;
4988         if (opt_date && draw_date(view, &commit->time))
4989                 return TRUE;
4991         if (opt_author &&
4992             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
4993                 return TRUE;
4995         if (opt_rev_graph && commit->graph_size &&
4996             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4997                 return TRUE;
4999         if (opt_show_refs && commit->refs) {
5000                 size_t i = 0;
5002                 do {
5003                         enum line_type type;
5005                         if (commit->refs[i]->head)
5006                                 type = LINE_MAIN_HEAD;
5007                         else if (commit->refs[i]->ltag)
5008                                 type = LINE_MAIN_LOCAL_TAG;
5009                         else if (commit->refs[i]->tag)
5010                                 type = LINE_MAIN_TAG;
5011                         else if (commit->refs[i]->tracked)
5012                                 type = LINE_MAIN_TRACKED;
5013                         else if (commit->refs[i]->remote)
5014                                 type = LINE_MAIN_REMOTE;
5015                         else
5016                                 type = LINE_MAIN_REF;
5018                         if (draw_text(view, type, "[", TRUE) ||
5019                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5020                             draw_text(view, type, "]", TRUE))
5021                                 return TRUE;
5023                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5024                                 return TRUE;
5025                 } while (commit->refs[i++]->next);
5026         }
5028         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5029         return TRUE;
5032 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5033 static bool
5034 main_read(struct view *view, char *line)
5036         static struct rev_graph *graph = graph_stacks;
5037         enum line_type type;
5038         struct commit *commit;
5040         if (!line) {
5041                 int i;
5043                 if (!view->lines && !view->parent)
5044                         die("No revisions match the given arguments.");
5045                 if (view->lines > 0) {
5046                         commit = view->line[view->lines - 1].data;
5047                         if (!*commit->author) {
5048                                 view->lines--;
5049                                 free(commit);
5050                                 graph->commit = NULL;
5051                         }
5052                 }
5053                 update_rev_graph(graph);
5055                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5056                         clear_rev_graph(&graph_stacks[i]);
5057                 return TRUE;
5058         }
5060         type = get_line_type(line);
5061         if (type == LINE_COMMIT) {
5062                 commit = calloc(1, sizeof(struct commit));
5063                 if (!commit)
5064                         return FALSE;
5066                 line += STRING_SIZE("commit ");
5067                 if (*line == '-') {
5068                         graph->boundary = 1;
5069                         line++;
5070                 }
5072                 string_copy_rev(commit->id, line);
5073                 commit->refs = get_refs(commit->id);
5074                 graph->commit = commit;
5075                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5077                 while ((line = strchr(line, ' '))) {
5078                         line++;
5079                         push_rev_graph(graph->parents, line);
5080                         commit->has_parents = TRUE;
5081                 }
5082                 return TRUE;
5083         }
5085         if (!view->lines)
5086                 return TRUE;
5087         commit = view->line[view->lines - 1].data;
5089         switch (type) {
5090         case LINE_PARENT:
5091                 if (commit->has_parents)
5092                         break;
5093                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5094                 break;
5096         case LINE_AUTHOR:
5097         {
5098                 /* Parse author lines where the name may be empty:
5099                  *      author  <email@address.tld> 1138474660 +0100
5100                  */
5101                 char *ident = line + STRING_SIZE("author ");
5102                 char *nameend = strchr(ident, '<');
5103                 char *emailend = strchr(ident, '>');
5105                 if (!nameend || !emailend)
5106                         break;
5108                 update_rev_graph(graph);
5109                 graph = graph->next;
5111                 *nameend = *emailend = 0;
5112                 ident = chomp_string(ident);
5113                 if (!*ident) {
5114                         ident = chomp_string(nameend + 1);
5115                         if (!*ident)
5116                                 ident = "Unknown";
5117                 }
5119                 string_ncopy(commit->author, ident, strlen(ident));
5121                 /* Parse epoch and timezone */
5122                 if (emailend[1] == ' ') {
5123                         char *secs = emailend + 2;
5124                         char *zone = strchr(secs, ' ');
5125                         time_t time = (time_t) atol(secs);
5127                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5128                                 long tz;
5130                                 zone++;
5131                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5132                                 tz += ('0' - zone[2]) * 60 * 60;
5133                                 tz += ('0' - zone[3]) * 60;
5134                                 tz += ('0' - zone[4]) * 60;
5136                                 if (zone[0] == '-')
5137                                         tz = -tz;
5139                                 time -= tz;
5140                         }
5142                         gmtime_r(&time, &commit->time);
5143                 }
5144                 break;
5145         }
5146         default:
5147                 /* Fill in the commit title if it has not already been set. */
5148                 if (commit->title[0])
5149                         break;
5151                 /* Require titles to start with a non-space character at the
5152                  * offset used by git log. */
5153                 if (strncmp(line, "    ", 4))
5154                         break;
5155                 line += 4;
5156                 /* Well, if the title starts with a whitespace character,
5157                  * try to be forgiving.  Otherwise we end up with no title. */
5158                 while (isspace(*line))
5159                         line++;
5160                 if (*line == '\0')
5161                         break;
5162                 /* FIXME: More graceful handling of titles; append "..." to
5163                  * shortened titles, etc. */
5165                 string_ncopy(commit->title, line, strlen(line));
5166         }
5168         return TRUE;
5171 static enum request
5172 main_request(struct view *view, enum request request, struct line *line)
5174         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5176         switch (request) {
5177         case REQ_ENTER:
5178                 open_view(view, REQ_VIEW_DIFF, flags);
5179                 break;
5180         case REQ_REFRESH:
5181                 string_copy(opt_cmd, view->cmd);
5182                 open_view(view, REQ_VIEW_MAIN, OPEN_RELOAD);
5183                 break;
5184         default:
5185                 return request;
5186         }
5188         return REQ_NONE;
5191 static bool
5192 grep_refs(struct ref **refs, regex_t *regex)
5194         regmatch_t pmatch;
5195         size_t i = 0;
5197         if (!refs)
5198                 return FALSE;
5199         do {
5200                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5201                         return TRUE;
5202         } while (refs[i++]->next);
5204         return FALSE;
5207 static bool
5208 main_grep(struct view *view, struct line *line)
5210         struct commit *commit = line->data;
5211         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5212         char buf[DATE_COLS + 1];
5213         regmatch_t pmatch;
5215         for (state = S_TITLE; state < S_END; state++) {
5216                 char *text;
5218                 switch (state) {
5219                 case S_TITLE:   text = commit->title;   break;
5220                 case S_AUTHOR:
5221                         if (!opt_author)
5222                                 continue;
5223                         text = commit->author;
5224                         break;
5225                 case S_DATE:
5226                         if (!opt_date)
5227                                 continue;
5228                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5229                                 continue;
5230                         text = buf;
5231                         break;
5232                 case S_REFS:
5233                         if (!opt_show_refs)
5234                                 continue;
5235                         if (grep_refs(commit->refs, view->regex) == TRUE)
5236                                 return TRUE;
5237                         continue;
5238                 default:
5239                         return FALSE;
5240                 }
5242                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5243                         return TRUE;
5244         }
5246         return FALSE;
5249 static void
5250 main_select(struct view *view, struct line *line)
5252         struct commit *commit = line->data;
5254         string_copy_rev(view->ref, commit->id);
5255         string_copy_rev(ref_commit, view->ref);
5258 static struct view_ops main_ops = {
5259         "commit",
5260         NULL,
5261         main_read,
5262         main_draw,
5263         main_request,
5264         main_grep,
5265         main_select,
5266 };
5269 /*
5270  * Unicode / UTF-8 handling
5271  *
5272  * NOTE: Much of the following code for dealing with unicode is derived from
5273  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5274  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5275  */
5277 /* I've (over)annotated a lot of code snippets because I am not entirely
5278  * confident that the approach taken by this small UTF-8 interface is correct.
5279  * --jonas */
5281 static inline int
5282 unicode_width(unsigned long c)
5284         if (c >= 0x1100 &&
5285            (c <= 0x115f                         /* Hangul Jamo */
5286             || c == 0x2329
5287             || c == 0x232a
5288             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5289                                                 /* CJK ... Yi */
5290             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5291             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5292             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5293             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5294             || (c >= 0xffe0  && c <= 0xffe6)
5295             || (c >= 0x20000 && c <= 0x2fffd)
5296             || (c >= 0x30000 && c <= 0x3fffd)))
5297                 return 2;
5299         if (c == '\t')
5300                 return opt_tab_size;
5302         return 1;
5305 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5306  * Illegal bytes are set one. */
5307 static const unsigned char utf8_bytes[256] = {
5308         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,
5309         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,
5310         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,
5311         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,
5312         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,
5313         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,
5314         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,
5315         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,
5316 };
5318 /* Decode UTF-8 multi-byte representation into a unicode character. */
5319 static inline unsigned long
5320 utf8_to_unicode(const char *string, size_t length)
5322         unsigned long unicode;
5324         switch (length) {
5325         case 1:
5326                 unicode  =   string[0];
5327                 break;
5328         case 2:
5329                 unicode  =  (string[0] & 0x1f) << 6;
5330                 unicode +=  (string[1] & 0x3f);
5331                 break;
5332         case 3:
5333                 unicode  =  (string[0] & 0x0f) << 12;
5334                 unicode += ((string[1] & 0x3f) << 6);
5335                 unicode +=  (string[2] & 0x3f);
5336                 break;
5337         case 4:
5338                 unicode  =  (string[0] & 0x0f) << 18;
5339                 unicode += ((string[1] & 0x3f) << 12);
5340                 unicode += ((string[2] & 0x3f) << 6);
5341                 unicode +=  (string[3] & 0x3f);
5342                 break;
5343         case 5:
5344                 unicode  =  (string[0] & 0x0f) << 24;
5345                 unicode += ((string[1] & 0x3f) << 18);
5346                 unicode += ((string[2] & 0x3f) << 12);
5347                 unicode += ((string[3] & 0x3f) << 6);
5348                 unicode +=  (string[4] & 0x3f);
5349                 break;
5350         case 6:
5351                 unicode  =  (string[0] & 0x01) << 30;
5352                 unicode += ((string[1] & 0x3f) << 24);
5353                 unicode += ((string[2] & 0x3f) << 18);
5354                 unicode += ((string[3] & 0x3f) << 12);
5355                 unicode += ((string[4] & 0x3f) << 6);
5356                 unicode +=  (string[5] & 0x3f);
5357                 break;
5358         default:
5359                 die("Invalid unicode length");
5360         }
5362         /* Invalid characters could return the special 0xfffd value but NUL
5363          * should be just as good. */
5364         return unicode > 0xffff ? 0 : unicode;
5367 /* Calculates how much of string can be shown within the given maximum width
5368  * and sets trimmed parameter to non-zero value if all of string could not be
5369  * shown. If the reserve flag is TRUE, it will reserve at least one
5370  * trailing character, which can be useful when drawing a delimiter.
5371  *
5372  * Returns the number of bytes to output from string to satisfy max_width. */
5373 static size_t
5374 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5376         const char *start = string;
5377         const char *end = strchr(string, '\0');
5378         unsigned char last_bytes = 0;
5379         size_t last_ucwidth = 0;
5381         *width = 0;
5382         *trimmed = 0;
5384         while (string < end) {
5385                 int c = *(unsigned char *) string;
5386                 unsigned char bytes = utf8_bytes[c];
5387                 size_t ucwidth;
5388                 unsigned long unicode;
5390                 if (string + bytes > end)
5391                         break;
5393                 /* Change representation to figure out whether
5394                  * it is a single- or double-width character. */
5396                 unicode = utf8_to_unicode(string, bytes);
5397                 /* FIXME: Graceful handling of invalid unicode character. */
5398                 if (!unicode)
5399                         break;
5401                 ucwidth = unicode_width(unicode);
5402                 *width  += ucwidth;
5403                 if (*width > max_width) {
5404                         *trimmed = 1;
5405                         *width -= ucwidth;
5406                         if (reserve && *width == max_width) {
5407                                 string -= last_bytes;
5408                                 *width -= last_ucwidth;
5409                         }
5410                         break;
5411                 }
5413                 string  += bytes;
5414                 last_bytes = bytes;
5415                 last_ucwidth = ucwidth;
5416         }
5418         return string - start;
5422 /*
5423  * Status management
5424  */
5426 /* Whether or not the curses interface has been initialized. */
5427 static bool cursed = FALSE;
5429 /* The status window is used for polling keystrokes. */
5430 static WINDOW *status_win;
5432 static bool status_empty = TRUE;
5434 /* Update status and title window. */
5435 static void
5436 report(const char *msg, ...)
5438         struct view *view = display[current_view];
5440         if (input_mode)
5441                 return;
5443         if (!view) {
5444                 char buf[SIZEOF_STR];
5445                 va_list args;
5447                 va_start(args, msg);
5448                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5449                         buf[sizeof(buf) - 1] = 0;
5450                         buf[sizeof(buf) - 2] = '.';
5451                         buf[sizeof(buf) - 3] = '.';
5452                         buf[sizeof(buf) - 4] = '.';
5453                 }
5454                 va_end(args);
5455                 die("%s", buf);
5456         }
5458         if (!status_empty || *msg) {
5459                 va_list args;
5461                 va_start(args, msg);
5463                 wmove(status_win, 0, 0);
5464                 if (*msg) {
5465                         vwprintw(status_win, msg, args);
5466                         status_empty = FALSE;
5467                 } else {
5468                         status_empty = TRUE;
5469                 }
5470                 wclrtoeol(status_win);
5471                 wrefresh(status_win);
5473                 va_end(args);
5474         }
5476         update_view_title(view);
5477         update_display_cursor(view);
5480 /* Controls when nodelay should be in effect when polling user input. */
5481 static void
5482 set_nonblocking_input(bool loading)
5484         static unsigned int loading_views;
5486         if ((loading == FALSE && loading_views-- == 1) ||
5487             (loading == TRUE  && loading_views++ == 0))
5488                 nodelay(status_win, loading);
5491 static void
5492 init_display(void)
5494         int x, y;
5496         /* Initialize the curses library */
5497         if (isatty(STDIN_FILENO)) {
5498                 cursed = !!initscr();
5499         } else {
5500                 /* Leave stdin and stdout alone when acting as a pager. */
5501                 FILE *io = fopen("/dev/tty", "r+");
5503                 if (!io)
5504                         die("Failed to open /dev/tty");
5505                 cursed = !!newterm(NULL, io, io);
5506         }
5508         if (!cursed)
5509                 die("Failed to initialize curses");
5511         nonl();         /* Tell curses not to do NL->CR/NL on output */
5512         cbreak();       /* Take input chars one at a time, no wait for \n */
5513         noecho();       /* Don't echo input */
5514         leaveok(stdscr, TRUE);
5516         if (has_colors())
5517                 init_colors();
5519         getmaxyx(stdscr, y, x);
5520         status_win = newwin(1, 0, y - 1, 0);
5521         if (!status_win)
5522                 die("Failed to create status window");
5524         /* Enable keyboard mapping */
5525         keypad(status_win, TRUE);
5526         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5528         TABSIZE = opt_tab_size;
5529         if (opt_line_graphics) {
5530                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5531         }
5534 static bool
5535 prompt_yesno(const char *prompt)
5537         enum { WAIT, STOP, CANCEL  } status = WAIT;
5538         bool answer = FALSE;
5540         while (status == WAIT) {
5541                 struct view *view;
5542                 int i, key;
5544                 input_mode = TRUE;
5546                 foreach_view (view, i)
5547                         update_view(view);
5549                 input_mode = FALSE;
5551                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5552                 wclrtoeol(status_win);
5554                 /* Refresh, accept single keystroke of input */
5555                 key = wgetch(status_win);
5556                 switch (key) {
5557                 case ERR:
5558                         break;
5560                 case 'y':
5561                 case 'Y':
5562                         answer = TRUE;
5563                         status = STOP;
5564                         break;
5566                 case KEY_ESC:
5567                 case KEY_RETURN:
5568                 case KEY_ENTER:
5569                 case KEY_BACKSPACE:
5570                 case 'n':
5571                 case 'N':
5572                 case '\n':
5573                 default:
5574                         answer = FALSE;
5575                         status = CANCEL;
5576                 }
5577         }
5579         /* Clear the status window */
5580         status_empty = FALSE;
5581         report("");
5583         return answer;
5586 static char *
5587 read_prompt(const char *prompt)
5589         enum { READING, STOP, CANCEL } status = READING;
5590         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5591         int pos = 0;
5593         while (status == READING) {
5594                 struct view *view;
5595                 int i, key;
5597                 input_mode = TRUE;
5599                 foreach_view (view, i)
5600                         update_view(view);
5602                 input_mode = FALSE;
5604                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5605                 wclrtoeol(status_win);
5607                 /* Refresh, accept single keystroke of input */
5608                 key = wgetch(status_win);
5609                 switch (key) {
5610                 case KEY_RETURN:
5611                 case KEY_ENTER:
5612                 case '\n':
5613                         status = pos ? STOP : CANCEL;
5614                         break;
5616                 case KEY_BACKSPACE:
5617                         if (pos > 0)
5618                                 pos--;
5619                         else
5620                                 status = CANCEL;
5621                         break;
5623                 case KEY_ESC:
5624                         status = CANCEL;
5625                         break;
5627                 case ERR:
5628                         break;
5630                 default:
5631                         if (pos >= sizeof(buf)) {
5632                                 report("Input string too long");
5633                                 return NULL;
5634                         }
5636                         if (isprint(key))
5637                                 buf[pos++] = (char) key;
5638                 }
5639         }
5641         /* Clear the status window */
5642         status_empty = FALSE;
5643         report("");
5645         if (status == CANCEL)
5646                 return NULL;
5648         buf[pos++] = 0;
5650         return buf;
5653 /*
5654  * Repository references
5655  */
5657 static struct ref *refs = NULL;
5658 static size_t refs_alloc = 0;
5659 static size_t refs_size = 0;
5661 /* Id <-> ref store */
5662 static struct ref ***id_refs = NULL;
5663 static size_t id_refs_alloc = 0;
5664 static size_t id_refs_size = 0;
5666 static struct ref **
5667 get_refs(char *id)
5669         struct ref ***tmp_id_refs;
5670         struct ref **ref_list = NULL;
5671         size_t ref_list_alloc = 0;
5672         size_t ref_list_size = 0;
5673         size_t i;
5675         for (i = 0; i < id_refs_size; i++)
5676                 if (!strcmp(id, id_refs[i][0]->id))
5677                         return id_refs[i];
5679         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5680                                     sizeof(*id_refs));
5681         if (!tmp_id_refs)
5682                 return NULL;
5684         id_refs = tmp_id_refs;
5686         for (i = 0; i < refs_size; i++) {
5687                 struct ref **tmp;
5689                 if (strcmp(id, refs[i].id))
5690                         continue;
5692                 tmp = realloc_items(ref_list, &ref_list_alloc,
5693                                     ref_list_size + 1, sizeof(*ref_list));
5694                 if (!tmp) {
5695                         if (ref_list)
5696                                 free(ref_list);
5697                         return NULL;
5698                 }
5700                 ref_list = tmp;
5701                 if (ref_list_size > 0)
5702                         ref_list[ref_list_size - 1]->next = 1;
5703                 ref_list[ref_list_size] = &refs[i];
5705                 /* XXX: The properties of the commit chains ensures that we can
5706                  * safely modify the shared ref. The repo references will
5707                  * always be similar for the same id. */
5708                 ref_list[ref_list_size]->next = 0;
5709                 ref_list_size++;
5710         }
5712         if (ref_list)
5713                 id_refs[id_refs_size++] = ref_list;
5715         return ref_list;
5718 static int
5719 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5721         struct ref *ref;
5722         bool tag = FALSE;
5723         bool ltag = FALSE;
5724         bool remote = FALSE;
5725         bool tracked = FALSE;
5726         bool check_replace = FALSE;
5727         bool head = FALSE;
5729         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5730                 if (!strcmp(name + namelen - 3, "^{}")) {
5731                         namelen -= 3;
5732                         name[namelen] = 0;
5733                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5734                                 check_replace = TRUE;
5735                 } else {
5736                         ltag = TRUE;
5737                 }
5739                 tag = TRUE;
5740                 namelen -= STRING_SIZE("refs/tags/");
5741                 name    += STRING_SIZE("refs/tags/");
5743         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5744                 remote = TRUE;
5745                 namelen -= STRING_SIZE("refs/remotes/");
5746                 name    += STRING_SIZE("refs/remotes/");
5747                 tracked  = !strcmp(opt_remote, name);
5749         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5750                 namelen -= STRING_SIZE("refs/heads/");
5751                 name    += STRING_SIZE("refs/heads/");
5752                 head     = !strncmp(opt_head, name, namelen);
5754         } else if (!strcmp(name, "HEAD")) {
5755                 opt_no_head = FALSE;
5756                 return OK;
5757         }
5759         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5760                 /* it's an annotated tag, replace the previous sha1 with the
5761                  * resolved commit id; relies on the fact git-ls-remote lists
5762                  * the commit id of an annotated tag right beofre the commit id
5763                  * it points to. */
5764                 refs[refs_size - 1].ltag = ltag;
5765                 string_copy_rev(refs[refs_size - 1].id, id);
5767                 return OK;
5768         }
5769         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5770         if (!refs)
5771                 return ERR;
5773         ref = &refs[refs_size++];
5774         ref->name = malloc(namelen + 1);
5775         if (!ref->name)
5776                 return ERR;
5778         strncpy(ref->name, name, namelen);
5779         ref->name[namelen] = 0;
5780         ref->head = head;
5781         ref->tag = tag;
5782         ref->ltag = ltag;
5783         ref->remote = remote;
5784         ref->tracked = tracked;
5785         string_copy_rev(ref->id, id);
5787         return OK;
5790 static int
5791 load_refs(void)
5793         const char *cmd_env = getenv("TIG_LS_REMOTE");
5794         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5796         return read_properties(popen(cmd, "r"), "\t", read_ref);
5799 static int
5800 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5802         if (!strcmp(name, "i18n.commitencoding"))
5803                 string_ncopy(opt_encoding, value, valuelen);
5805         if (!strcmp(name, "core.editor"))
5806                 string_ncopy(opt_editor, value, valuelen);
5808         /* branch.<head>.remote */
5809         if (*opt_head &&
5810             !strncmp(name, "branch.", 7) &&
5811             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5812             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5813                 string_ncopy(opt_remote, value, valuelen);
5815         if (*opt_head && *opt_remote &&
5816             !strncmp(name, "branch.", 7) &&
5817             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5818             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5819                 size_t from = strlen(opt_remote);
5821                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5822                         value += STRING_SIZE("refs/heads/");
5823                         valuelen -= STRING_SIZE("refs/heads/");
5824                 }
5826                 if (!string_format_from(opt_remote, &from, "/%s", value))
5827                         opt_remote[0] = 0;
5828         }
5830         return OK;
5833 static int
5834 load_git_config(void)
5836         return read_properties(popen(GIT_CONFIG " --list", "r"),
5837                                "=", read_repo_config_option);
5840 static int
5841 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5843         if (!opt_git_dir[0]) {
5844                 string_ncopy(opt_git_dir, name, namelen);
5846         } else if (opt_is_inside_work_tree == -1) {
5847                 /* This can be 3 different values depending on the
5848                  * version of git being used. If git-rev-parse does not
5849                  * understand --is-inside-work-tree it will simply echo
5850                  * the option else either "true" or "false" is printed.
5851                  * Default to true for the unknown case. */
5852                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5854         } else if (opt_cdup[0] == ' ') {
5855                 string_ncopy(opt_cdup, name, namelen);
5856         } else {
5857                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5858                         namelen -= STRING_SIZE("refs/heads/");
5859                         name    += STRING_SIZE("refs/heads/");
5860                         string_ncopy(opt_head, name, namelen);
5861                 }
5862         }
5864         return OK;
5867 static int
5868 load_repo_info(void)
5870         int result;
5871         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5872                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5874         /* XXX: The line outputted by "--show-cdup" can be empty so
5875          * initialize it to something invalid to make it possible to
5876          * detect whether it has been set or not. */
5877         opt_cdup[0] = ' ';
5879         result = read_properties(pipe, "=", read_repo_info);
5880         if (opt_cdup[0] == ' ')
5881                 opt_cdup[0] = 0;
5883         return result;
5886 static int
5887 read_properties(FILE *pipe, const char *separators,
5888                 int (*read_property)(char *, size_t, char *, size_t))
5890         char buffer[BUFSIZ];
5891         char *name;
5892         int state = OK;
5894         if (!pipe)
5895                 return ERR;
5897         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5898                 char *value;
5899                 size_t namelen;
5900                 size_t valuelen;
5902                 name = chomp_string(name);
5903                 namelen = strcspn(name, separators);
5905                 if (name[namelen]) {
5906                         name[namelen] = 0;
5907                         value = chomp_string(name + namelen + 1);
5908                         valuelen = strlen(value);
5910                 } else {
5911                         value = "";
5912                         valuelen = 0;
5913                 }
5915                 state = read_property(name, namelen, value, valuelen);
5916         }
5918         if (state != ERR && ferror(pipe))
5919                 state = ERR;
5921         pclose(pipe);
5923         return state;
5927 /*
5928  * Main
5929  */
5931 static void __NORETURN
5932 quit(int sig)
5934         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5935         if (cursed)
5936                 endwin();
5937         exit(0);
5940 static void __NORETURN
5941 die(const char *err, ...)
5943         va_list args;
5945         endwin();
5947         va_start(args, err);
5948         fputs("tig: ", stderr);
5949         vfprintf(stderr, err, args);
5950         fputs("\n", stderr);
5951         va_end(args);
5953         exit(1);
5956 static void
5957 warn(const char *msg, ...)
5959         va_list args;
5961         va_start(args, msg);
5962         fputs("tig warning: ", stderr);
5963         vfprintf(stderr, msg, args);
5964         fputs("\n", stderr);
5965         va_end(args);
5968 int
5969 main(int argc, char *argv[])
5971         struct view *view;
5972         enum request request;
5973         size_t i;
5975         signal(SIGINT, quit);
5977         if (setlocale(LC_ALL, "")) {
5978                 char *codeset = nl_langinfo(CODESET);
5980                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5981         }
5983         if (load_repo_info() == ERR)
5984                 die("Failed to load repo info.");
5986         if (load_options() == ERR)
5987                 die("Failed to load user config.");
5989         if (load_git_config() == ERR)
5990                 die("Failed to load repo config.");
5992         request = parse_options(argc, argv);
5993         if (request == REQ_NONE)
5994                 return 0;
5996         /* Require a git repository unless when running in pager mode. */
5997         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
5998                 die("Not a git repository");
6000         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6001                 opt_utf8 = FALSE;
6003         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6004                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6005                 if (opt_iconv == ICONV_NONE)
6006                         die("Failed to initialize character set conversion");
6007         }
6009         if (*opt_git_dir && load_refs() == ERR)
6010                 die("Failed to load refs.");
6012         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
6013                 view->cmd_env = getenv(view->cmd_env);
6015         init_display();
6017         while (view_driver(display[current_view], request)) {
6018                 int key;
6019                 int i;
6021                 foreach_view (view, i)
6022                         update_view(view);
6024                 /* Refresh, accept single keystroke of input */
6025                 key = wgetch(status_win);
6027                 /* wgetch() with nodelay() enabled returns ERR when there's no
6028                  * input. */
6029                 if (key == ERR) {
6030                         request = REQ_NONE;
6031                         continue;
6032                 }
6034                 request = get_keybinding(display[current_view]->keymap, key);
6036                 /* Some low-level request handling. This keeps access to
6037                  * status_win restricted. */
6038                 switch (request) {
6039                 case REQ_PROMPT:
6040                 {
6041                         char *cmd = read_prompt(":");
6043                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6044                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6045                                         request = REQ_VIEW_DIFF;
6046                                 } else {
6047                                         request = REQ_VIEW_PAGER;
6048                                 }
6050                                 /* Always reload^Wrerun commands from the prompt. */
6051                                 open_view(view, request, OPEN_RELOAD);
6052                         }
6054                         request = REQ_NONE;
6055                         break;
6056                 }
6057                 case REQ_SEARCH:
6058                 case REQ_SEARCH_BACK:
6059                 {
6060                         const char *prompt = request == REQ_SEARCH
6061                                            ? "/" : "?";
6062                         char *search = read_prompt(prompt);
6064                         if (search)
6065                                 string_ncopy(opt_search, search, strlen(search));
6066                         else
6067                                 request = REQ_NONE;
6068                         break;
6069                 }
6070                 case REQ_SCREEN_RESIZE:
6071                 {
6072                         int height, width;
6074                         getmaxyx(stdscr, height, width);
6076                         /* Resize the status view and let the view driver take
6077                          * care of resizing the displayed views. */
6078                         wresize(status_win, 1, width);
6079                         mvwin(status_win, height - 1, 0);
6080                         wrefresh(status_win);
6081                         break;
6082                 }
6083                 default:
6084                         break;
6085                 }
6086         }
6088         quit(0);
6090         return 0;