Code

3802204dc05e6f294e4ecf3fcafbbc42b7c654b8
[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);
71 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
72 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
75 #define STRING_SIZE(x)  (sizeof(x) - 1)
77 #define SIZEOF_STR      1024    /* Default string size. */
78 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
79 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
81 /* Revision graph */
83 #define REVGRAPH_INIT   'I'
84 #define REVGRAPH_MERGE  'M'
85 #define REVGRAPH_BRANCH '+'
86 #define REVGRAPH_COMMIT '*'
87 #define REVGRAPH_BOUND  '^'
89 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
91 /* This color name can be used to refer to the default term colors. */
92 #define COLOR_DEFAULT   (-1)
94 #define ICONV_NONE      ((iconv_t) -1)
95 #ifndef ICONV_CONST
96 #define ICONV_CONST     /* nothing */
97 #endif
99 /* The format and size of the date column in the main view. */
100 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
101 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
103 #define AUTHOR_COLS     20
104 #define ID_COLS         8
106 /* The default interval between line numbers. */
107 #define NUMBER_INTERVAL 5
109 #define TAB_SIZE        8
111 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "git config"
117 #endif
119 #define TIG_LS_REMOTE \
120         "git ls-remote . 2>/dev/null"
122 #define TIG_DIFF_CMD \
123         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
125 #define TIG_LOG_CMD     \
126         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
128 #define TIG_MAIN_CMD \
129         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
131 #define TIG_TREE_CMD    \
132         "git ls-tree %s %s"
134 #define TIG_BLOB_CMD    \
135         "git cat-file blob %s"
137 /* XXX: Needs to be defined to the empty string. */
138 #define TIG_HELP_CMD    ""
139 #define TIG_PAGER_CMD   ""
140 #define TIG_STATUS_CMD  ""
141 #define TIG_STAGE_CMD   ""
142 #define TIG_BLAME_CMD   ""
144 /* Some ascii-shorthands fitted into the ncurses namespace. */
145 #define KEY_TAB         '\t'
146 #define KEY_RETURN      '\r'
147 #define KEY_ESC         27
150 struct ref {
151         char *name;             /* Ref name; tag or head names are shortened. */
152         char id[SIZEOF_REV];    /* Commit SHA1 ID */
153         unsigned int head:1;    /* Is it the current HEAD? */
154         unsigned int tag:1;     /* Is it a tag? */
155         unsigned int ltag:1;    /* If so, is the tag local? */
156         unsigned int remote:1;  /* Is it a remote ref? */
157         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
158         unsigned int next:1;    /* For ref lists: are there more refs? */
159 };
161 static struct ref **get_refs(char *id);
163 struct int_map {
164         const char *name;
165         int namelen;
166         int value;
167 };
169 static int
170 set_from_int_map(struct int_map *map, size_t map_size,
171                  int *value, const char *name, int namelen)
174         int i;
176         for (i = 0; i < map_size; i++)
177                 if (namelen == map[i].namelen &&
178                     !strncasecmp(name, map[i].name, namelen)) {
179                         *value = map[i].value;
180                         return OK;
181                 }
183         return ERR;
187 /*
188  * String helpers
189  */
191 static inline void
192 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
194         if (srclen > dstlen - 1)
195                 srclen = dstlen - 1;
197         strncpy(dst, src, srclen);
198         dst[srclen] = 0;
201 /* Shorthands for safely copying into a fixed buffer. */
203 #define string_copy(dst, src) \
204         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
206 #define string_ncopy(dst, src, srclen) \
207         string_ncopy_do(dst, sizeof(dst), src, srclen)
209 #define string_copy_rev(dst, src) \
210         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
212 #define string_add(dst, from, src) \
213         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 static char *
216 chomp_string(char *name)
218         int namelen;
220         while (isspace(*name))
221                 name++;
223         namelen = strlen(name) - 1;
224         while (namelen > 0 && isspace(name[namelen]))
225                 name[namelen--] = 0;
227         return name;
230 static bool
231 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
233         va_list args;
234         size_t pos = bufpos ? *bufpos : 0;
236         va_start(args, fmt);
237         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
238         va_end(args);
240         if (bufpos)
241                 *bufpos = pos;
243         return pos >= bufsize ? FALSE : TRUE;
246 #define string_format(buf, fmt, args...) \
247         string_nformat(buf, sizeof(buf), NULL, fmt, args)
249 #define string_format_from(buf, from, fmt, args...) \
250         string_nformat(buf, sizeof(buf), from, fmt, args)
252 static int
253 string_enum_compare(const char *str1, const char *str2, int len)
255         size_t i;
257 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
259         /* Diff-Header == DIFF_HEADER */
260         for (i = 0; i < len; i++) {
261                 if (toupper(str1[i]) == toupper(str2[i]))
262                         continue;
264                 if (string_enum_sep(str1[i]) &&
265                     string_enum_sep(str2[i]))
266                         continue;
268                 return str1[i] - str2[i];
269         }
271         return 0;
274 /* Shell quoting
275  *
276  * NOTE: The following is a slightly modified copy of the git project's shell
277  * quoting routines found in the quote.c file.
278  *
279  * Help to copy the thing properly quoted for the shell safety.  any single
280  * quote is replaced with '\'', any exclamation point is replaced with '\!',
281  * and the whole thing is enclosed in a
282  *
283  * E.g.
284  *  original     sq_quote     result
285  *  name     ==> name      ==> 'name'
286  *  a b      ==> a b       ==> 'a b'
287  *  a'b      ==> a'\''b    ==> 'a'\''b'
288  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
289  */
291 static size_t
292 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
294         char c;
296 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
298         BUFPUT('\'');
299         while ((c = *src++)) {
300                 if (c == '\'' || c == '!') {
301                         BUFPUT('\'');
302                         BUFPUT('\\');
303                         BUFPUT(c);
304                         BUFPUT('\'');
305                 } else {
306                         BUFPUT(c);
307                 }
308         }
309         BUFPUT('\'');
311         if (bufsize < SIZEOF_STR)
312                 buf[bufsize] = 0;
314         return bufsize;
318 /*
319  * User requests
320  */
322 #define REQ_INFO \
323         /* XXX: Keep the view request first and in sync with views[]. */ \
324         REQ_GROUP("View switching") \
325         REQ_(VIEW_MAIN,         "Show main view"), \
326         REQ_(VIEW_DIFF,         "Show diff view"), \
327         REQ_(VIEW_LOG,          "Show log view"), \
328         REQ_(VIEW_TREE,         "Show tree view"), \
329         REQ_(VIEW_BLOB,         "Show blob view"), \
330         REQ_(VIEW_BLAME,        "Show blame view"), \
331         REQ_(VIEW_HELP,         "Show help page"), \
332         REQ_(VIEW_PAGER,        "Show pager view"), \
333         REQ_(VIEW_STATUS,       "Show status view"), \
334         REQ_(VIEW_STAGE,        "Show stage view"), \
335         \
336         REQ_GROUP("View manipulation") \
337         REQ_(ENTER,             "Enter current line and scroll"), \
338         REQ_(NEXT,              "Move to next"), \
339         REQ_(PREVIOUS,          "Move to previous"), \
340         REQ_(VIEW_NEXT,         "Move focus to next view"), \
341         REQ_(REFRESH,           "Reload and refresh"), \
342         REQ_(MAXIMIZE,          "Maximize the current view"), \
343         REQ_(VIEW_CLOSE,        "Close the current view"), \
344         REQ_(QUIT,              "Close all views and quit"), \
345         \
346         REQ_GROUP("Cursor navigation") \
347         REQ_(MOVE_UP,           "Move cursor one line up"), \
348         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
349         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
350         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
351         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
352         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
353         \
354         REQ_GROUP("Scrolling") \
355         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
356         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
357         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
358         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
359         \
360         REQ_GROUP("Searching") \
361         REQ_(SEARCH,            "Search the view"), \
362         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
363         REQ_(FIND_NEXT,         "Find next search match"), \
364         REQ_(FIND_PREV,         "Find previous search match"), \
365         \
366         REQ_GROUP("Misc") \
367         REQ_(PROMPT,            "Bring up the prompt"), \
368         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
369         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
370         REQ_(SHOW_VERSION,      "Show version information"), \
371         REQ_(STOP_LOADING,      "Stop all loading views"), \
372         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
373         REQ_(TOGGLE_DATE,       "Toggle date display"), \
374         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
375         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
376         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
377         REQ_(STATUS_UPDATE,     "Update file status"), \
378         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
379         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
380         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
381         REQ_(EDIT,              "Open in editor"), \
382         REQ_(NONE,              "Do nothing")
385 /* User action requests. */
386 enum request {
387 #define REQ_GROUP(help)
388 #define REQ_(req, help) REQ_##req
390         /* Offset all requests to avoid conflicts with ncurses getch values. */
391         REQ_OFFSET = KEY_MAX + 1,
392         REQ_INFO
394 #undef  REQ_GROUP
395 #undef  REQ_
396 };
398 struct request_info {
399         enum request request;
400         char *name;
401         int namelen;
402         char *help;
403 };
405 static struct request_info req_info[] = {
406 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
407 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
408         REQ_INFO
409 #undef  REQ_GROUP
410 #undef  REQ_
411 };
413 static enum request
414 get_request(const char *name)
416         int namelen = strlen(name);
417         int i;
419         for (i = 0; i < ARRAY_SIZE(req_info); i++)
420                 if (req_info[i].namelen == namelen &&
421                     !string_enum_compare(req_info[i].name, name, namelen))
422                         return req_info[i].request;
424         return REQ_NONE;
428 /*
429  * Options
430  */
432 static const char usage[] =
433 "tig " TIG_VERSION " (" __DATE__ ")\n"
434 "\n"
435 "Usage: tig        [options] [revs] [--] [paths]\n"
436 "   or: tig show   [options] [revs] [--] [paths]\n"
437 "   or: tig blame  [rev] path\n"
438 "   or: tig status\n"
439 "   or: tig <      [git command output]\n"
440 "\n"
441 "Options:\n"
442 "  -v, --version   Show version and exit\n"
443 "  -h, --help      Show help message and exit";
445 /* Option and state variables. */
446 static bool opt_date                    = TRUE;
447 static bool opt_author                  = TRUE;
448 static bool opt_line_number             = FALSE;
449 static bool opt_line_graphics           = TRUE;
450 static bool opt_rev_graph               = FALSE;
451 static bool opt_show_refs               = TRUE;
452 static int opt_num_interval             = NUMBER_INTERVAL;
453 static int opt_tab_size                 = TAB_SIZE;
454 static enum request opt_request         = REQ_VIEW_MAIN;
455 static char opt_cmd[SIZEOF_STR]         = "";
456 static char opt_path[SIZEOF_STR]        = "";
457 static char opt_file[SIZEOF_STR]        = "";
458 static char opt_ref[SIZEOF_REF]         = "";
459 static char opt_head[SIZEOF_REF]        = "";
460 static char opt_remote[SIZEOF_REF]      = "";
461 static bool opt_no_head                 = TRUE;
462 static FILE *opt_pipe                   = NULL;
463 static char opt_encoding[20]            = "UTF-8";
464 static bool opt_utf8                    = TRUE;
465 static char opt_codeset[20]             = "UTF-8";
466 static iconv_t opt_iconv                = ICONV_NONE;
467 static char opt_search[SIZEOF_STR]      = "";
468 static char opt_cdup[SIZEOF_STR]        = "";
469 static char opt_git_dir[SIZEOF_STR]     = "";
470 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
471 static char opt_editor[SIZEOF_STR]      = "";
473 static bool
474 parse_options(int argc, char *argv[])
476         size_t buf_size;
477         char *subcommand;
478         bool seen_dashdash = FALSE;
479         int i;
481         if (!isatty(STDIN_FILENO)) {
482                 opt_request = REQ_VIEW_PAGER;
483                 opt_pipe = stdin;
484                 return TRUE;
485         }
487         if (argc <= 1)
488                 return TRUE;
490         subcommand = argv[1];
491         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
492                 opt_request = REQ_VIEW_STATUS;
493                 if (!strcmp(subcommand, "-S"))
494                         warn("`-S' has been deprecated; use `tig status' instead");
495                 if (argc > 2)
496                         warn("ignoring arguments after `%s'", subcommand);
497                 return TRUE;
499         } else if (!strcmp(subcommand, "blame")) {
500                 opt_request = REQ_VIEW_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 TRUE;
513         } else if (!strcmp(subcommand, "show")) {
514                 opt_request = REQ_VIEW_DIFF;
516         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
517                 opt_request = subcommand[0] == 'l'
518                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
519                 warn("`tig %s' has been deprecated", subcommand);
521         } else {
522                 subcommand = NULL;
523         }
525         if (!subcommand)
526                 /* XXX: This is vulnerable to the user overriding
527                  * options required for the main view parser. */
528                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
529         else
530                 string_format(opt_cmd, "git %s", subcommand);
532         buf_size = strlen(opt_cmd);
534         for (i = 1 + !!subcommand; i < argc; i++) {
535                 char *opt = argv[i];
537                 if (seen_dashdash || !strcmp(opt, "--")) {
538                         seen_dashdash = TRUE;
540                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
541                         printf("tig version %s\n", TIG_VERSION);
542                         return FALSE;
544                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
545                         printf("%s\n", usage);
546                         return FALSE;
547                 }
549                 opt_cmd[buf_size++] = ' ';
550                 buf_size = sq_quote(opt_cmd, buf_size, opt);
551                 if (buf_size >= sizeof(opt_cmd))
552                         die("command too long");
553         }
555         opt_cmd[buf_size] = 0;
557         return TRUE;
561 /*
562  * Line-oriented content detection.
563  */
565 #define LINE_INFO \
566 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
567 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
568 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
569 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
570 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
572 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
573 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
574 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
575 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
579 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
580 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
581 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
582 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
583 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
586 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
587 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
590 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
591 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
592 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
593 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
594 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
595 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
596 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
597 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
598 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
599 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
601 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
602 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
603 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
604 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
605 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
606 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
608 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
609 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
610 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
611 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
612 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
613 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
614 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
615 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
616 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
617 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
618 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
619 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
621 enum line_type {
622 #define LINE(type, line, fg, bg, attr) \
623         LINE_##type
624         LINE_INFO,
625         LINE_NONE
626 #undef  LINE
627 };
629 struct line_info {
630         const char *name;       /* Option name. */
631         int namelen;            /* Size of option name. */
632         const char *line;       /* The start of line to match. */
633         int linelen;            /* Size of string to match. */
634         int fg, bg, attr;       /* Color and text attributes for the lines. */
635 };
637 static struct line_info line_info[] = {
638 #define LINE(type, line, fg, bg, attr) \
639         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
640         LINE_INFO
641 #undef  LINE
642 };
644 static enum line_type
645 get_line_type(char *line)
647         int linelen = strlen(line);
648         enum line_type type;
650         for (type = 0; type < ARRAY_SIZE(line_info); type++)
651                 /* Case insensitive search matches Signed-off-by lines better. */
652                 if (linelen >= line_info[type].linelen &&
653                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
654                         return type;
656         return LINE_DEFAULT;
659 static inline int
660 get_line_attr(enum line_type type)
662         assert(type < ARRAY_SIZE(line_info));
663         return COLOR_PAIR(type) | line_info[type].attr;
666 static struct line_info *
667 get_line_info(char *name)
669         size_t namelen = strlen(name);
670         enum line_type type;
672         for (type = 0; type < ARRAY_SIZE(line_info); type++)
673                 if (namelen == line_info[type].namelen &&
674                     !string_enum_compare(line_info[type].name, name, namelen))
675                         return &line_info[type];
677         return NULL;
680 static void
681 init_colors(void)
683         int default_bg = line_info[LINE_DEFAULT].bg;
684         int default_fg = line_info[LINE_DEFAULT].fg;
685         enum line_type type;
687         start_color();
689         if (assume_default_colors(default_fg, default_bg) == ERR) {
690                 default_bg = COLOR_BLACK;
691                 default_fg = COLOR_WHITE;
692         }
694         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
695                 struct line_info *info = &line_info[type];
696                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
697                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
699                 init_pair(type, fg, bg);
700         }
703 struct line {
704         enum line_type type;
706         /* State flags */
707         unsigned int selected:1;
708         unsigned int dirty:1;
710         void *data;             /* User data */
711 };
714 /*
715  * Keys
716  */
718 struct keybinding {
719         int alias;
720         enum request request;
721         struct keybinding *next;
722 };
724 static struct keybinding default_keybindings[] = {
725         /* View switching */
726         { 'm',          REQ_VIEW_MAIN },
727         { 'd',          REQ_VIEW_DIFF },
728         { 'l',          REQ_VIEW_LOG },
729         { 't',          REQ_VIEW_TREE },
730         { 'f',          REQ_VIEW_BLOB },
731         { 'B',          REQ_VIEW_BLAME },
732         { 'p',          REQ_VIEW_PAGER },
733         { 'h',          REQ_VIEW_HELP },
734         { 'S',          REQ_VIEW_STATUS },
735         { 'c',          REQ_VIEW_STAGE },
737         /* View manipulation */
738         { 'q',          REQ_VIEW_CLOSE },
739         { KEY_TAB,      REQ_VIEW_NEXT },
740         { KEY_RETURN,   REQ_ENTER },
741         { KEY_UP,       REQ_PREVIOUS },
742         { KEY_DOWN,     REQ_NEXT },
743         { 'R',          REQ_REFRESH },
744         { KEY_F(5),     REQ_REFRESH },
745         { 'O',          REQ_MAXIMIZE },
747         /* Cursor navigation */
748         { 'k',          REQ_MOVE_UP },
749         { 'j',          REQ_MOVE_DOWN },
750         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
751         { KEY_END,      REQ_MOVE_LAST_LINE },
752         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
753         { ' ',          REQ_MOVE_PAGE_DOWN },
754         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
755         { 'b',          REQ_MOVE_PAGE_UP },
756         { '-',          REQ_MOVE_PAGE_UP },
758         /* Scrolling */
759         { KEY_IC,       REQ_SCROLL_LINE_UP },
760         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
761         { 'w',          REQ_SCROLL_PAGE_UP },
762         { 's',          REQ_SCROLL_PAGE_DOWN },
764         /* Searching */
765         { '/',          REQ_SEARCH },
766         { '?',          REQ_SEARCH_BACK },
767         { 'n',          REQ_FIND_NEXT },
768         { 'N',          REQ_FIND_PREV },
770         /* Misc */
771         { 'Q',          REQ_QUIT },
772         { 'z',          REQ_STOP_LOADING },
773         { 'v',          REQ_SHOW_VERSION },
774         { 'r',          REQ_SCREEN_REDRAW },
775         { '.',          REQ_TOGGLE_LINENO },
776         { 'D',          REQ_TOGGLE_DATE },
777         { 'A',          REQ_TOGGLE_AUTHOR },
778         { 'g',          REQ_TOGGLE_REV_GRAPH },
779         { 'F',          REQ_TOGGLE_REFS },
780         { ':',          REQ_PROMPT },
781         { 'u',          REQ_STATUS_UPDATE },
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 /* Wants: name = value */
1104 static int
1105 option_set_command(int argc, char *argv[])
1107         if (argc != 3) {
1108                 config_msg = "Wrong number of arguments given to set command";
1109                 return ERR;
1110         }
1112         if (strcmp(argv[1], "=")) {
1113                 config_msg = "No value assigned";
1114                 return ERR;
1115         }
1117         if (!strcmp(argv[0], "show-author")) {
1118                 opt_author = parse_bool(argv[2]);
1119                 return OK;
1120         }
1122         if (!strcmp(argv[0], "show-date")) {
1123                 opt_date = parse_bool(argv[2]);
1124                 return OK;
1125         }
1127         if (!strcmp(argv[0], "show-rev-graph")) {
1128                 opt_rev_graph = parse_bool(argv[2]);
1129                 return OK;
1130         }
1132         if (!strcmp(argv[0], "show-refs")) {
1133                 opt_show_refs = parse_bool(argv[2]);
1134                 return OK;
1135         }
1137         if (!strcmp(argv[0], "show-line-numbers")) {
1138                 opt_line_number = parse_bool(argv[2]);
1139                 return OK;
1140         }
1142         if (!strcmp(argv[0], "line-graphics")) {
1143                 opt_line_graphics = parse_bool(argv[2]);
1144                 return OK;
1145         }
1147         if (!strcmp(argv[0], "line-number-interval")) {
1148                 opt_num_interval = atoi(argv[2]);
1149                 return OK;
1150         }
1152         if (!strcmp(argv[0], "tab-size")) {
1153                 opt_tab_size = atoi(argv[2]);
1154                 return OK;
1155         }
1157         if (!strcmp(argv[0], "commit-encoding")) {
1158                 char *arg = argv[2];
1159                 int delimiter = *arg;
1160                 int i;
1162                 switch (delimiter) {
1163                 case '"':
1164                 case '\'':
1165                         for (arg++, i = 0; arg[i]; i++)
1166                                 if (arg[i] == delimiter) {
1167                                         arg[i] = 0;
1168                                         break;
1169                                 }
1170                 default:
1171                         string_ncopy(opt_encoding, arg, strlen(arg));
1172                         return OK;
1173                 }
1174         }
1176         config_msg = "Unknown variable name";
1177         return ERR;
1180 /* Wants: mode request key */
1181 static int
1182 option_bind_command(int argc, char *argv[])
1184         enum request request;
1185         int keymap;
1186         int key;
1188         if (argc < 3) {
1189                 config_msg = "Wrong number of arguments given to bind command";
1190                 return ERR;
1191         }
1193         if (set_keymap(&keymap, argv[0]) == ERR) {
1194                 config_msg = "Unknown key map";
1195                 return ERR;
1196         }
1198         key = get_key_value(argv[1]);
1199         if (key == ERR) {
1200                 config_msg = "Unknown key";
1201                 return ERR;
1202         }
1204         request = get_request(argv[2]);
1205         if (request == REQ_NONE) {
1206                 const char *obsolete[] = { "cherry-pick" };
1207                 size_t namelen = strlen(argv[2]);
1208                 int i;
1210                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1211                         if (namelen == strlen(obsolete[i]) &&
1212                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1213                                 config_msg = "Obsolete request name";
1214                                 return ERR;
1215                         }
1216                 }
1217         }
1218         if (request == REQ_NONE && *argv[2]++ == '!')
1219                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1220         if (request == REQ_NONE) {
1221                 config_msg = "Unknown request name";
1222                 return ERR;
1223         }
1225         add_keybinding(keymap, request, key);
1227         return OK;
1230 static int
1231 set_option(char *opt, char *value)
1233         char *argv[16];
1234         int valuelen;
1235         int argc = 0;
1237         /* Tokenize */
1238         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1239                 argv[argc++] = value;
1240                 value += valuelen;
1242                 /* Nothing more to tokenize or last available token. */
1243                 if (!*value || argc >= ARRAY_SIZE(argv))
1244                         break;
1246                 *value++ = 0;
1247                 while (isspace(*value))
1248                         value++;
1249         }
1251         if (!strcmp(opt, "color"))
1252                 return option_color_command(argc, argv);
1254         if (!strcmp(opt, "set"))
1255                 return option_set_command(argc, argv);
1257         if (!strcmp(opt, "bind"))
1258                 return option_bind_command(argc, argv);
1260         config_msg = "Unknown option command";
1261         return ERR;
1264 static int
1265 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1267         int status = OK;
1269         config_lineno++;
1270         config_msg = "Internal error";
1272         /* Check for comment markers, since read_properties() will
1273          * only ensure opt and value are split at first " \t". */
1274         optlen = strcspn(opt, "#");
1275         if (optlen == 0)
1276                 return OK;
1278         if (opt[optlen] != 0) {
1279                 config_msg = "No option value";
1280                 status = ERR;
1282         }  else {
1283                 /* Look for comment endings in the value. */
1284                 size_t len = strcspn(value, "#");
1286                 if (len < valuelen) {
1287                         valuelen = len;
1288                         value[valuelen] = 0;
1289                 }
1291                 status = set_option(opt, value);
1292         }
1294         if (status == ERR) {
1295                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1296                         config_lineno, (int) optlen, opt, config_msg);
1297                 config_errors = TRUE;
1298         }
1300         /* Always keep going if errors are encountered. */
1301         return OK;
1304 static void
1305 load_option_file(const char *path)
1307         FILE *file;
1309         /* It's ok that the file doesn't exist. */
1310         file = fopen(path, "r");
1311         if (!file)
1312                 return;
1314         config_lineno = 0;
1315         config_errors = FALSE;
1317         if (read_properties(file, " \t", read_option) == ERR ||
1318             config_errors == TRUE)
1319                 fprintf(stderr, "Errors while loading %s.\n", path);
1322 static int
1323 load_options(void)
1325         char *home = getenv("HOME");
1326         char *tigrc_user = getenv("TIGRC_USER");
1327         char *tigrc_system = getenv("TIGRC_SYSTEM");
1328         char buf[SIZEOF_STR];
1330         add_builtin_run_requests();
1332         if (!tigrc_system) {
1333                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1334                         return ERR;
1335                 tigrc_system = buf;
1336         }
1337         load_option_file(tigrc_system);
1339         if (!tigrc_user) {
1340                 if (!home || !string_format(buf, "%s/.tigrc", home))
1341                         return ERR;
1342                 tigrc_user = buf;
1343         }
1344         load_option_file(tigrc_user);
1346         return OK;
1350 /*
1351  * The viewer
1352  */
1354 struct view;
1355 struct view_ops;
1357 /* The display array of active views and the index of the current view. */
1358 static struct view *display[2];
1359 static unsigned int current_view;
1361 /* Reading from the prompt? */
1362 static bool input_mode = FALSE;
1364 #define foreach_displayed_view(view, i) \
1365         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1367 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1369 /* Current head and commit ID */
1370 static char ref_blob[SIZEOF_REF]        = "";
1371 static char ref_commit[SIZEOF_REF]      = "HEAD";
1372 static char ref_head[SIZEOF_REF]        = "HEAD";
1374 struct view {
1375         const char *name;       /* View name */
1376         const char *cmd_fmt;    /* Default command line format */
1377         const char *cmd_env;    /* Command line set via environment */
1378         const char *id;         /* Points to either of ref_{head,commit,blob} */
1380         struct view_ops *ops;   /* View operations */
1382         enum keymap keymap;     /* What keymap does this view have */
1383         bool git_dir;           /* Whether the view requires a git directory. */
1385         char cmd[SIZEOF_STR];   /* Command buffer */
1386         char ref[SIZEOF_REF];   /* Hovered commit reference */
1387         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1389         int height, width;      /* The width and height of the main window */
1390         WINDOW *win;            /* The main window */
1391         WINDOW *title;          /* The title window living below the main window */
1393         /* Navigation */
1394         unsigned long offset;   /* Offset of the window top */
1395         unsigned long lineno;   /* Current line number */
1397         /* Searching */
1398         char grep[SIZEOF_STR];  /* Search string */
1399         regex_t *regex;         /* Pre-compiled regex */
1401         /* If non-NULL, points to the view that opened this view. If this view
1402          * is closed tig will switch back to the parent view. */
1403         struct view *parent;
1405         /* Buffering */
1406         size_t lines;           /* Total number of lines */
1407         struct line *line;      /* Line index */
1408         size_t line_alloc;      /* Total number of allocated lines */
1409         size_t line_size;       /* Total number of used lines */
1410         unsigned int digits;    /* Number of digits in the lines member. */
1412         /* Drawing */
1413         struct line *curline;   /* Line currently being drawn. */
1414         enum line_type curtype; /* Attribute currently used for drawing. */
1415         unsigned long col;      /* Column when drawing. */
1417         /* Loading */
1418         FILE *pipe;
1419         time_t start_time;
1420 };
1422 struct view_ops {
1423         /* What type of content being displayed. Used in the title bar. */
1424         const char *type;
1425         /* Open and reads in all view content. */
1426         bool (*open)(struct view *view);
1427         /* Read one line; updates view->line. */
1428         bool (*read)(struct view *view, char *data);
1429         /* Draw one line; @lineno must be < view->height. */
1430         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1431         /* Depending on view handle a special requests. */
1432         enum request (*request)(struct view *view, enum request request, struct line *line);
1433         /* Search for regex in a line. */
1434         bool (*grep)(struct view *view, struct line *line);
1435         /* Select line */
1436         void (*select)(struct view *view, struct line *line);
1437 };
1439 static struct view_ops pager_ops;
1440 static struct view_ops main_ops;
1441 static struct view_ops tree_ops;
1442 static struct view_ops blob_ops;
1443 static struct view_ops blame_ops;
1444 static struct view_ops help_ops;
1445 static struct view_ops status_ops;
1446 static struct view_ops stage_ops;
1448 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1449         { name, cmd, #env, ref, ops, map, git }
1451 #define VIEW_(id, name, ops, git, ref) \
1452         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1455 static struct view views[] = {
1456         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1457         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1458         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1459         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1460         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1461         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1462         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1463         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1464         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1465         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1466 };
1468 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1469 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1471 #define foreach_view(view, i) \
1472         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1474 #define view_is_displayed(view) \
1475         (view == display[0] || view == display[1])
1478 enum line_graphic {
1479         LINE_GRAPHIC_VLINE
1480 };
1482 static int line_graphics[] = {
1483         /* LINE_GRAPHIC_VLINE: */ '|'
1484 };
1486 static inline void
1487 set_view_attr(struct view *view, enum line_type type)
1489         if (!view->curline->selected && view->curtype != type) {
1490                 wattrset(view->win, get_line_attr(type));
1491                 wchgat(view->win, -1, 0, type, NULL);
1492                 view->curtype = type;
1493         }
1496 static int
1497 draw_chars(struct view *view, enum line_type type, const char *string,
1498            int max_len, bool use_tilde)
1500         int len = 0;
1501         int col = 0;
1502         int trimmed = FALSE;
1504         if (max_len <= 0)
1505                 return 0;
1507         if (opt_utf8) {
1508                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1509         } else {
1510                 col = len = strlen(string);
1511                 if (len > max_len) {
1512                         if (use_tilde) {
1513                                 max_len -= 1;
1514                         }
1515                         col = len = max_len;
1516                         trimmed = TRUE;
1517                 }
1518         }
1520         set_view_attr(view, type);
1521         waddnstr(view->win, string, len);
1522         if (trimmed && use_tilde) {
1523                 set_view_attr(view, LINE_DELIMITER);
1524                 waddch(view->win, '~');
1525                 col++;
1526         }
1528         return col;
1531 static int
1532 draw_space(struct view *view, enum line_type type, int max, int spaces)
1534         static char space[] = "                    ";
1535         int col = 0;
1537         spaces = MIN(max, spaces);
1539         while (spaces > 0) {
1540                 int len = MIN(spaces, sizeof(space) - 1);
1542                 col += draw_chars(view, type, space, spaces, FALSE);
1543                 spaces -= len;
1544         }
1546         return col;
1549 static bool
1550 draw_lineno(struct view *view, unsigned int lineno)
1552         char number[10];
1553         int digits3 = view->digits < 3 ? 3 : view->digits;
1554         int max_number = MIN(digits3, STRING_SIZE(number));
1555         int max = view->width - view->col;
1556         int col;
1558         if (max < max_number)
1559                 max_number = max;
1561         lineno += view->offset + 1;
1562         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1563                 static char fmt[] = "%1ld";
1565                 if (view->digits <= 9)
1566                         fmt[1] = '0' + digits3;
1568                 if (!string_format(number, fmt, lineno))
1569                         number[0] = 0;
1570                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1571         } else {
1572                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1573         }
1575         if (col < max) {
1576                 set_view_attr(view, LINE_DEFAULT);
1577                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1578                 col++;
1579         }
1581         if (col < max)
1582                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1583         view->col += col;
1585         return view->width - view->col <= 0;
1588 static bool
1589 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1591         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1592         return view->width - view->col <= 0;
1595 static bool
1596 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1598         int max = view->width - view->col;
1599         int i;
1601         if (max < size)
1602                 size = max;
1604         set_view_attr(view, type);
1605         /* Using waddch() instead of waddnstr() ensures that
1606          * they'll be rendered correctly for the cursor line. */
1607         for (i = 0; i < size; i++)
1608                 waddch(view->win, graphic[i]);
1610         view->col += size;
1611         if (size < max) {
1612                 waddch(view->win, ' ');
1613                 view->col++;
1614         }
1616         return view->width - view->col <= 0;
1619 static bool
1620 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1622         int max = MIN(view->width - view->col, len);
1623         int col;
1625         if (text)
1626                 col = draw_chars(view, type, text, max - 1, trim);
1627         else
1628                 col = draw_space(view, type, max - 1, max - 1);
1630         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1631         return view->width - view->col <= 0;
1634 static bool
1635 draw_date(struct view *view, struct tm *time)
1637         char buf[DATE_COLS];
1638         char *date;
1639         int timelen = 0;
1641         if (time)
1642                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1643         date = timelen ? buf : NULL;
1645         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1648 static bool
1649 draw_view_line(struct view *view, unsigned int lineno)
1651         struct line *line;
1652         bool selected = (view->offset + lineno == view->lineno);
1653         bool draw_ok;
1655         assert(view_is_displayed(view));
1657         if (view->offset + lineno >= view->lines)
1658                 return FALSE;
1660         line = &view->line[view->offset + lineno];
1662         wmove(view->win, lineno, 0);
1663         view->col = 0;
1664         view->curline = line;
1665         view->curtype = LINE_NONE;
1666         line->selected = FALSE;
1668         if (selected) {
1669                 set_view_attr(view, LINE_CURSOR);
1670                 line->selected = TRUE;
1671                 view->ops->select(view, line);
1672         } else if (line->selected) {
1673                 wclrtoeol(view->win);
1674         }
1676         scrollok(view->win, FALSE);
1677         draw_ok = view->ops->draw(view, line, lineno);
1678         scrollok(view->win, TRUE);
1680         return draw_ok;
1683 static void
1684 redraw_view_dirty(struct view *view)
1686         bool dirty = FALSE;
1687         int lineno;
1689         for (lineno = 0; lineno < view->height; lineno++) {
1690                 struct line *line = &view->line[view->offset + lineno];
1692                 if (!line->dirty)
1693                         continue;
1694                 line->dirty = 0;
1695                 dirty = TRUE;
1696                 if (!draw_view_line(view, lineno))
1697                         break;
1698         }
1700         if (!dirty)
1701                 return;
1702         redrawwin(view->win);
1703         if (input_mode)
1704                 wnoutrefresh(view->win);
1705         else
1706                 wrefresh(view->win);
1709 static void
1710 redraw_view_from(struct view *view, int lineno)
1712         assert(0 <= lineno && lineno < view->height);
1714         for (; lineno < view->height; lineno++) {
1715                 if (!draw_view_line(view, lineno))
1716                         break;
1717         }
1719         redrawwin(view->win);
1720         if (input_mode)
1721                 wnoutrefresh(view->win);
1722         else
1723                 wrefresh(view->win);
1726 static void
1727 redraw_view(struct view *view)
1729         wclear(view->win);
1730         redraw_view_from(view, 0);
1734 static void
1735 update_view_title(struct view *view)
1737         char buf[SIZEOF_STR];
1738         char state[SIZEOF_STR];
1739         size_t bufpos = 0, statelen = 0;
1741         assert(view_is_displayed(view));
1743         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1744                 unsigned int view_lines = view->offset + view->height;
1745                 unsigned int lines = view->lines
1746                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1747                                    : 0;
1749                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1750                                    view->ops->type,
1751                                    view->lineno + 1,
1752                                    view->lines,
1753                                    lines);
1755                 if (view->pipe) {
1756                         time_t secs = time(NULL) - view->start_time;
1758                         /* Three git seconds are a long time ... */
1759                         if (secs > 2)
1760                                 string_format_from(state, &statelen, " %lds", secs);
1761                 }
1762         }
1764         string_format_from(buf, &bufpos, "[%s]", view->name);
1765         if (*view->ref && bufpos < view->width) {
1766                 size_t refsize = strlen(view->ref);
1767                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1769                 if (minsize < view->width)
1770                         refsize = view->width - minsize + 7;
1771                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1772         }
1774         if (statelen && bufpos < view->width) {
1775                 string_format_from(buf, &bufpos, " %s", state);
1776         }
1778         if (view == display[current_view])
1779                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1780         else
1781                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1783         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1784         wclrtoeol(view->title);
1785         wmove(view->title, 0, view->width - 1);
1787         if (input_mode)
1788                 wnoutrefresh(view->title);
1789         else
1790                 wrefresh(view->title);
1793 static void
1794 resize_display(void)
1796         int offset, i;
1797         struct view *base = display[0];
1798         struct view *view = display[1] ? display[1] : display[0];
1800         /* Setup window dimensions */
1802         getmaxyx(stdscr, base->height, base->width);
1804         /* Make room for the status window. */
1805         base->height -= 1;
1807         if (view != base) {
1808                 /* Horizontal split. */
1809                 view->width   = base->width;
1810                 view->height  = SCALE_SPLIT_VIEW(base->height);
1811                 base->height -= view->height;
1813                 /* Make room for the title bar. */
1814                 view->height -= 1;
1815         }
1817         /* Make room for the title bar. */
1818         base->height -= 1;
1820         offset = 0;
1822         foreach_displayed_view (view, i) {
1823                 if (!view->win) {
1824                         view->win = newwin(view->height, 0, offset, 0);
1825                         if (!view->win)
1826                                 die("Failed to create %s view", view->name);
1828                         scrollok(view->win, TRUE);
1830                         view->title = newwin(1, 0, offset + view->height, 0);
1831                         if (!view->title)
1832                                 die("Failed to create title window");
1834                 } else {
1835                         wresize(view->win, view->height, view->width);
1836                         mvwin(view->win,   offset, 0);
1837                         mvwin(view->title, offset + view->height, 0);
1838                 }
1840                 offset += view->height + 1;
1841         }
1844 static void
1845 redraw_display(void)
1847         struct view *view;
1848         int i;
1850         foreach_displayed_view (view, i) {
1851                 redraw_view(view);
1852                 update_view_title(view);
1853         }
1856 static void
1857 update_display_cursor(struct view *view)
1859         /* Move the cursor to the right-most column of the cursor line.
1860          *
1861          * XXX: This could turn out to be a bit expensive, but it ensures that
1862          * the cursor does not jump around. */
1863         if (view->lines) {
1864                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1865                 wrefresh(view->win);
1866         }
1869 /*
1870  * Navigation
1871  */
1873 /* Scrolling backend */
1874 static void
1875 do_scroll_view(struct view *view, int lines)
1877         bool redraw_current_line = FALSE;
1879         /* The rendering expects the new offset. */
1880         view->offset += lines;
1882         assert(0 <= view->offset && view->offset < view->lines);
1883         assert(lines);
1885         /* Move current line into the view. */
1886         if (view->lineno < view->offset) {
1887                 view->lineno = view->offset;
1888                 redraw_current_line = TRUE;
1889         } else if (view->lineno >= view->offset + view->height) {
1890                 view->lineno = view->offset + view->height - 1;
1891                 redraw_current_line = TRUE;
1892         }
1894         assert(view->offset <= view->lineno && view->lineno < view->lines);
1896         /* Redraw the whole screen if scrolling is pointless. */
1897         if (view->height < ABS(lines)) {
1898                 redraw_view(view);
1900         } else {
1901                 int line = lines > 0 ? view->height - lines : 0;
1902                 int end = line + ABS(lines);
1904                 wscrl(view->win, lines);
1906                 for (; line < end; line++) {
1907                         if (!draw_view_line(view, line))
1908                                 break;
1909                 }
1911                 if (redraw_current_line)
1912                         draw_view_line(view, view->lineno - view->offset);
1913         }
1915         redrawwin(view->win);
1916         wrefresh(view->win);
1917         report("");
1920 /* Scroll frontend */
1921 static void
1922 scroll_view(struct view *view, enum request request)
1924         int lines = 1;
1926         assert(view_is_displayed(view));
1928         switch (request) {
1929         case REQ_SCROLL_PAGE_DOWN:
1930                 lines = view->height;
1931         case REQ_SCROLL_LINE_DOWN:
1932                 if (view->offset + lines > view->lines)
1933                         lines = view->lines - view->offset;
1935                 if (lines == 0 || view->offset + view->height >= view->lines) {
1936                         report("Cannot scroll beyond the last line");
1937                         return;
1938                 }
1939                 break;
1941         case REQ_SCROLL_PAGE_UP:
1942                 lines = view->height;
1943         case REQ_SCROLL_LINE_UP:
1944                 if (lines > view->offset)
1945                         lines = view->offset;
1947                 if (lines == 0) {
1948                         report("Cannot scroll beyond the first line");
1949                         return;
1950                 }
1952                 lines = -lines;
1953                 break;
1955         default:
1956                 die("request %d not handled in switch", request);
1957         }
1959         do_scroll_view(view, lines);
1962 /* Cursor moving */
1963 static void
1964 move_view(struct view *view, enum request request)
1966         int scroll_steps = 0;
1967         int steps;
1969         switch (request) {
1970         case REQ_MOVE_FIRST_LINE:
1971                 steps = -view->lineno;
1972                 break;
1974         case REQ_MOVE_LAST_LINE:
1975                 steps = view->lines - view->lineno - 1;
1976                 break;
1978         case REQ_MOVE_PAGE_UP:
1979                 steps = view->height > view->lineno
1980                       ? -view->lineno : -view->height;
1981                 break;
1983         case REQ_MOVE_PAGE_DOWN:
1984                 steps = view->lineno + view->height >= view->lines
1985                       ? view->lines - view->lineno - 1 : view->height;
1986                 break;
1988         case REQ_MOVE_UP:
1989                 steps = -1;
1990                 break;
1992         case REQ_MOVE_DOWN:
1993                 steps = 1;
1994                 break;
1996         default:
1997                 die("request %d not handled in switch", request);
1998         }
2000         if (steps <= 0 && view->lineno == 0) {
2001                 report("Cannot move beyond the first line");
2002                 return;
2004         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2005                 report("Cannot move beyond the last line");
2006                 return;
2007         }
2009         /* Move the current line */
2010         view->lineno += steps;
2011         assert(0 <= view->lineno && view->lineno < view->lines);
2013         /* Check whether the view needs to be scrolled */
2014         if (view->lineno < view->offset ||
2015             view->lineno >= view->offset + view->height) {
2016                 scroll_steps = steps;
2017                 if (steps < 0 && -steps > view->offset) {
2018                         scroll_steps = -view->offset;
2020                 } else if (steps > 0) {
2021                         if (view->lineno == view->lines - 1 &&
2022                             view->lines > view->height) {
2023                                 scroll_steps = view->lines - view->offset - 1;
2024                                 if (scroll_steps >= view->height)
2025                                         scroll_steps -= view->height - 1;
2026                         }
2027                 }
2028         }
2030         if (!view_is_displayed(view)) {
2031                 view->offset += scroll_steps;
2032                 assert(0 <= view->offset && view->offset < view->lines);
2033                 view->ops->select(view, &view->line[view->lineno]);
2034                 return;
2035         }
2037         /* Repaint the old "current" line if we be scrolling */
2038         if (ABS(steps) < view->height)
2039                 draw_view_line(view, view->lineno - steps - view->offset);
2041         if (scroll_steps) {
2042                 do_scroll_view(view, scroll_steps);
2043                 return;
2044         }
2046         /* Draw the current line */
2047         draw_view_line(view, view->lineno - view->offset);
2049         redrawwin(view->win);
2050         wrefresh(view->win);
2051         report("");
2055 /*
2056  * Searching
2057  */
2059 static void search_view(struct view *view, enum request request);
2061 static bool
2062 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2064         assert(view_is_displayed(view));
2066         if (!view->ops->grep(view, line))
2067                 return FALSE;
2069         if (lineno - view->offset >= view->height) {
2070                 view->offset = lineno;
2071                 view->lineno = lineno;
2072                 redraw_view(view);
2074         } else {
2075                 unsigned long old_lineno = view->lineno - view->offset;
2077                 view->lineno = lineno;
2078                 draw_view_line(view, old_lineno);
2080                 draw_view_line(view, view->lineno - view->offset);
2081                 redrawwin(view->win);
2082                 wrefresh(view->win);
2083         }
2085         report("Line %ld matches '%s'", lineno + 1, view->grep);
2086         return TRUE;
2089 static void
2090 find_next(struct view *view, enum request request)
2092         unsigned long lineno = view->lineno;
2093         int direction;
2095         if (!*view->grep) {
2096                 if (!*opt_search)
2097                         report("No previous search");
2098                 else
2099                         search_view(view, request);
2100                 return;
2101         }
2103         switch (request) {
2104         case REQ_SEARCH:
2105         case REQ_FIND_NEXT:
2106                 direction = 1;
2107                 break;
2109         case REQ_SEARCH_BACK:
2110         case REQ_FIND_PREV:
2111                 direction = -1;
2112                 break;
2114         default:
2115                 return;
2116         }
2118         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2119                 lineno += direction;
2121         /* Note, lineno is unsigned long so will wrap around in which case it
2122          * will become bigger than view->lines. */
2123         for (; lineno < view->lines; lineno += direction) {
2124                 struct line *line = &view->line[lineno];
2126                 if (find_next_line(view, lineno, line))
2127                         return;
2128         }
2130         report("No match found for '%s'", view->grep);
2133 static void
2134 search_view(struct view *view, enum request request)
2136         int regex_err;
2138         if (view->regex) {
2139                 regfree(view->regex);
2140                 *view->grep = 0;
2141         } else {
2142                 view->regex = calloc(1, sizeof(*view->regex));
2143                 if (!view->regex)
2144                         return;
2145         }
2147         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2148         if (regex_err != 0) {
2149                 char buf[SIZEOF_STR] = "unknown error";
2151                 regerror(regex_err, view->regex, buf, sizeof(buf));
2152                 report("Search failed: %s", buf);
2153                 return;
2154         }
2156         string_copy(view->grep, opt_search);
2158         find_next(view, request);
2161 /*
2162  * Incremental updating
2163  */
2165 static void
2166 end_update(struct view *view, bool force)
2168         if (!view->pipe)
2169                 return;
2170         while (!view->ops->read(view, NULL))
2171                 if (!force)
2172                         return;
2173         set_nonblocking_input(FALSE);
2174         if (view->pipe == stdin)
2175                 fclose(view->pipe);
2176         else
2177                 pclose(view->pipe);
2178         view->pipe = NULL;
2181 static bool
2182 begin_update(struct view *view)
2184         if (opt_cmd[0]) {
2185                 string_copy(view->cmd, opt_cmd);
2186                 opt_cmd[0] = 0;
2187                 /* When running random commands, initially show the
2188                  * command in the title. However, it maybe later be
2189                  * overwritten if a commit line is selected. */
2190                 if (view == VIEW(REQ_VIEW_PAGER))
2191                         string_copy(view->ref, view->cmd);
2192                 else
2193                         view->ref[0] = 0;
2195         } else if (view == VIEW(REQ_VIEW_TREE)) {
2196                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2197                 char path[SIZEOF_STR];
2199                 if (strcmp(view->vid, view->id))
2200                         opt_path[0] = path[0] = 0;
2201                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2202                         return FALSE;
2204                 if (!string_format(view->cmd, format, view->id, path))
2205                         return FALSE;
2207         } else {
2208                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2209                 const char *id = view->id;
2211                 if (!string_format(view->cmd, format, id, id, id, id, id))
2212                         return FALSE;
2214                 /* Put the current ref_* value to the view title ref
2215                  * member. This is needed by the blob view. Most other
2216                  * views sets it automatically after loading because the
2217                  * first line is a commit line. */
2218                 string_copy_rev(view->ref, view->id);
2219         }
2221         /* Special case for the pager view. */
2222         if (opt_pipe) {
2223                 view->pipe = opt_pipe;
2224                 opt_pipe = NULL;
2225         } else {
2226                 view->pipe = popen(view->cmd, "r");
2227         }
2229         if (!view->pipe)
2230                 return FALSE;
2232         set_nonblocking_input(TRUE);
2234         view->offset = 0;
2235         view->lines  = 0;
2236         view->lineno = 0;
2237         string_copy_rev(view->vid, view->id);
2239         if (view->line) {
2240                 int i;
2242                 for (i = 0; i < view->lines; i++)
2243                         if (view->line[i].data)
2244                                 free(view->line[i].data);
2246                 free(view->line);
2247                 view->line = NULL;
2248         }
2250         view->start_time = time(NULL);
2252         return TRUE;
2255 #define ITEM_CHUNK_SIZE 256
2256 static void *
2257 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2259         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2260         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2262         if (mem == NULL || num_chunks != num_chunks_new) {
2263                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2264                 mem = realloc(mem, *size * item_size);
2265         }
2267         return mem;
2270 static struct line *
2271 realloc_lines(struct view *view, size_t line_size)
2273         size_t alloc = view->line_alloc;
2274         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2275                                          sizeof(*view->line));
2277         if (!tmp)
2278                 return NULL;
2280         view->line = tmp;
2281         view->line_alloc = alloc;
2282         view->line_size = line_size;
2283         return view->line;
2286 static bool
2287 update_view(struct view *view)
2289         char in_buffer[BUFSIZ];
2290         char out_buffer[BUFSIZ * 2];
2291         char *line;
2292         /* The number of lines to read. If too low it will cause too much
2293          * redrawing (and possible flickering), if too high responsiveness
2294          * will suffer. */
2295         unsigned long lines = view->height;
2296         int redraw_from = -1;
2298         if (!view->pipe)
2299                 return TRUE;
2301         /* Only redraw if lines are visible. */
2302         if (view->offset + view->height >= view->lines)
2303                 redraw_from = view->lines - view->offset;
2305         /* FIXME: This is probably not perfect for backgrounded views. */
2306         if (!realloc_lines(view, view->lines + lines))
2307                 goto alloc_error;
2309         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2310                 size_t linelen = strlen(line);
2312                 if (linelen)
2313                         line[linelen - 1] = 0;
2315                 if (opt_iconv != ICONV_NONE) {
2316                         ICONV_CONST char *inbuf = line;
2317                         size_t inlen = linelen;
2319                         char *outbuf = out_buffer;
2320                         size_t outlen = sizeof(out_buffer);
2322                         size_t ret;
2324                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2325                         if (ret != (size_t) -1) {
2326                                 line = out_buffer;
2327                                 linelen = strlen(out_buffer);
2328                         }
2329                 }
2331                 if (!view->ops->read(view, line))
2332                         goto alloc_error;
2334                 if (lines-- == 1)
2335                         break;
2336         }
2338         {
2339                 int digits;
2341                 lines = view->lines;
2342                 for (digits = 0; lines; digits++)
2343                         lines /= 10;
2345                 /* Keep the displayed view in sync with line number scaling. */
2346                 if (digits != view->digits) {
2347                         view->digits = digits;
2348                         redraw_from = 0;
2349                 }
2350         }
2352         if (!view_is_displayed(view))
2353                 goto check_pipe;
2355         if (view == VIEW(REQ_VIEW_TREE)) {
2356                 /* Clear the view and redraw everything since the tree sorting
2357                  * might have rearranged things. */
2358                 redraw_view(view);
2360         } else if (redraw_from >= 0) {
2361                 /* If this is an incremental update, redraw the previous line
2362                  * since for commits some members could have changed when
2363                  * loading the main view. */
2364                 if (redraw_from > 0)
2365                         redraw_from--;
2367                 /* Since revision graph visualization requires knowledge
2368                  * about the parent commit, it causes a further one-off
2369                  * needed to be redrawn for incremental updates. */
2370                 if (redraw_from > 0 && opt_rev_graph)
2371                         redraw_from--;
2373                 /* Incrementally draw avoids flickering. */
2374                 redraw_view_from(view, redraw_from);
2375         }
2377         if (view == VIEW(REQ_VIEW_BLAME))
2378                 redraw_view_dirty(view);
2380         /* Update the title _after_ the redraw so that if the redraw picks up a
2381          * commit reference in view->ref it'll be available here. */
2382         update_view_title(view);
2384 check_pipe:
2385         if (ferror(view->pipe)) {
2386                 report("Failed to read: %s", strerror(errno));
2387                 end_update(view, TRUE);
2389         } else if (feof(view->pipe)) {
2390                 report("");
2391                 end_update(view, FALSE);
2392         }
2394         return TRUE;
2396 alloc_error:
2397         report("Allocation failure");
2398         end_update(view, TRUE);
2399         return FALSE;
2402 static struct line *
2403 add_line_data(struct view *view, void *data, enum line_type type)
2405         struct line *line = &view->line[view->lines++];
2407         memset(line, 0, sizeof(*line));
2408         line->type = type;
2409         line->data = data;
2411         return line;
2414 static struct line *
2415 add_line_text(struct view *view, char *data, enum line_type type)
2417         if (data)
2418                 data = strdup(data);
2420         return data ? add_line_data(view, data, type) : NULL;
2424 /*
2425  * View opening
2426  */
2428 enum open_flags {
2429         OPEN_DEFAULT = 0,       /* Use default view switching. */
2430         OPEN_SPLIT = 1,         /* Split current view. */
2431         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2432         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2433         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2434 };
2436 static void
2437 open_view(struct view *prev, enum request request, enum open_flags flags)
2439         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2440         bool split = !!(flags & OPEN_SPLIT);
2441         bool reload = !!(flags & OPEN_RELOAD);
2442         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2443         struct view *view = VIEW(request);
2444         int nviews = displayed_views();
2445         struct view *base_view = display[0];
2447         if (view == prev && nviews == 1 && !reload) {
2448                 report("Already in %s view", view->name);
2449                 return;
2450         }
2452         if (view->git_dir && !opt_git_dir[0]) {
2453                 report("The %s view is disabled in pager view", view->name);
2454                 return;
2455         }
2457         if (split) {
2458                 display[1] = view;
2459                 if (!backgrounded)
2460                         current_view = 1;
2461         } else if (!nomaximize) {
2462                 /* Maximize the current view. */
2463                 memset(display, 0, sizeof(display));
2464                 current_view = 0;
2465                 display[current_view] = view;
2466         }
2468         /* Resize the view when switching between split- and full-screen,
2469          * or when switching between two different full-screen views. */
2470         if (nviews != displayed_views() ||
2471             (nviews == 1 && base_view != display[0]))
2472                 resize_display();
2474         if (view->pipe)
2475                 end_update(view, TRUE);
2477         if (view->ops->open) {
2478                 if (!view->ops->open(view)) {
2479                         report("Failed to load %s view", view->name);
2480                         return;
2481                 }
2483         } else if ((reload || strcmp(view->vid, view->id)) &&
2484                    !begin_update(view)) {
2485                 report("Failed to load %s view", view->name);
2486                 return;
2487         }
2489         if (split && prev->lineno - prev->offset >= prev->height) {
2490                 /* Take the title line into account. */
2491                 int lines = prev->lineno - prev->offset - prev->height + 1;
2493                 /* Scroll the view that was split if the current line is
2494                  * outside the new limited view. */
2495                 do_scroll_view(prev, lines);
2496         }
2498         if (prev && view != prev) {
2499                 if (split && !backgrounded) {
2500                         /* "Blur" the previous view. */
2501                         update_view_title(prev);
2502                 }
2504                 view->parent = prev;
2505         }
2507         if (view->pipe && view->lines == 0) {
2508                 /* Clear the old view and let the incremental updating refill
2509                  * the screen. */
2510                 werase(view->win);
2511                 report("");
2512         } else {
2513                 redraw_view(view);
2514                 report("");
2515         }
2517         /* If the view is backgrounded the above calls to report()
2518          * won't redraw the view title. */
2519         if (backgrounded)
2520                 update_view_title(view);
2523 static void
2524 open_external_viewer(const char *cmd)
2526         def_prog_mode();           /* save current tty modes */
2527         endwin();                  /* restore original tty modes */
2528         system(cmd);
2529         fprintf(stderr, "Press Enter to continue");
2530         getc(stdin);
2531         reset_prog_mode();
2532         redraw_display();
2535 static void
2536 open_mergetool(const char *file)
2538         char cmd[SIZEOF_STR];
2539         char file_sq[SIZEOF_STR];
2541         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2542             string_format(cmd, "git mergetool %s", file_sq)) {
2543                 open_external_viewer(cmd);
2544         }
2547 static void
2548 open_editor(bool from_root, const char *file)
2550         char cmd[SIZEOF_STR];
2551         char file_sq[SIZEOF_STR];
2552         char *editor;
2553         char *prefix = from_root ? opt_cdup : "";
2555         editor = getenv("GIT_EDITOR");
2556         if (!editor && *opt_editor)
2557                 editor = opt_editor;
2558         if (!editor)
2559                 editor = getenv("VISUAL");
2560         if (!editor)
2561                 editor = getenv("EDITOR");
2562         if (!editor)
2563                 editor = "vi";
2565         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2566             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2567                 open_external_viewer(cmd);
2568         }
2571 static void
2572 open_run_request(enum request request)
2574         struct run_request *req = get_run_request(request);
2575         char buf[SIZEOF_STR * 2];
2576         size_t bufpos;
2577         char *cmd;
2579         if (!req) {
2580                 report("Unknown run request");
2581                 return;
2582         }
2584         bufpos = 0;
2585         cmd = req->cmd;
2587         while (cmd) {
2588                 char *next = strstr(cmd, "%(");
2589                 int len = next - cmd;
2590                 char *value;
2592                 if (!next) {
2593                         len = strlen(cmd);
2594                         value = "";
2596                 } else if (!strncmp(next, "%(head)", 7)) {
2597                         value = ref_head;
2599                 } else if (!strncmp(next, "%(commit)", 9)) {
2600                         value = ref_commit;
2602                 } else if (!strncmp(next, "%(blob)", 7)) {
2603                         value = ref_blob;
2605                 } else {
2606                         report("Unknown replacement in run request: `%s`", req->cmd);
2607                         return;
2608                 }
2610                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2611                         return;
2613                 if (next)
2614                         next = strchr(next, ')') + 1;
2615                 cmd = next;
2616         }
2618         open_external_viewer(buf);
2621 /*
2622  * User request switch noodle
2623  */
2625 static int
2626 view_driver(struct view *view, enum request request)
2628         int i;
2630         if (request == REQ_NONE) {
2631                 doupdate();
2632                 return TRUE;
2633         }
2635         if (request > REQ_NONE) {
2636                 open_run_request(request);
2637                 /* FIXME: When all views can refresh always do this. */
2638                 if (view == VIEW(REQ_VIEW_STATUS) ||
2639                     view == VIEW(REQ_VIEW_STAGE))
2640                         request = REQ_REFRESH;
2641                 else
2642                         return TRUE;
2643         }
2645         if (view && view->lines) {
2646                 request = view->ops->request(view, request, &view->line[view->lineno]);
2647                 if (request == REQ_NONE)
2648                         return TRUE;
2649         }
2651         switch (request) {
2652         case REQ_MOVE_UP:
2653         case REQ_MOVE_DOWN:
2654         case REQ_MOVE_PAGE_UP:
2655         case REQ_MOVE_PAGE_DOWN:
2656         case REQ_MOVE_FIRST_LINE:
2657         case REQ_MOVE_LAST_LINE:
2658                 move_view(view, request);
2659                 break;
2661         case REQ_SCROLL_LINE_DOWN:
2662         case REQ_SCROLL_LINE_UP:
2663         case REQ_SCROLL_PAGE_DOWN:
2664         case REQ_SCROLL_PAGE_UP:
2665                 scroll_view(view, request);
2666                 break;
2668         case REQ_VIEW_BLAME:
2669                 if (!opt_file[0]) {
2670                         report("No file chosen, press %s to open tree view",
2671                                get_key(REQ_VIEW_TREE));
2672                         break;
2673                 }
2674                 open_view(view, request, OPEN_DEFAULT);
2675                 break;
2677         case REQ_VIEW_BLOB:
2678                 if (!ref_blob[0]) {
2679                         report("No file chosen, press %s to open tree view",
2680                                get_key(REQ_VIEW_TREE));
2681                         break;
2682                 }
2683                 open_view(view, request, OPEN_DEFAULT);
2684                 break;
2686         case REQ_VIEW_PAGER:
2687                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2688                         report("No pager content, press %s to run command from prompt",
2689                                get_key(REQ_PROMPT));
2690                         break;
2691                 }
2692                 open_view(view, request, OPEN_DEFAULT);
2693                 break;
2695         case REQ_VIEW_STAGE:
2696                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2697                         report("No stage content, press %s to open the status view and choose file",
2698                                get_key(REQ_VIEW_STATUS));
2699                         break;
2700                 }
2701                 open_view(view, request, OPEN_DEFAULT);
2702                 break;
2704         case REQ_VIEW_STATUS:
2705                 if (opt_is_inside_work_tree == FALSE) {
2706                         report("The status view requires a working tree");
2707                         break;
2708                 }
2709                 open_view(view, request, OPEN_DEFAULT);
2710                 break;
2712         case REQ_VIEW_MAIN:
2713         case REQ_VIEW_DIFF:
2714         case REQ_VIEW_LOG:
2715         case REQ_VIEW_TREE:
2716         case REQ_VIEW_HELP:
2717                 open_view(view, request, OPEN_DEFAULT);
2718                 break;
2720         case REQ_NEXT:
2721         case REQ_PREVIOUS:
2722                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2724                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2725                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2726                    (view == VIEW(REQ_VIEW_DIFF) &&
2727                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2728                    (view == VIEW(REQ_VIEW_STAGE) &&
2729                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2730                    (view == VIEW(REQ_VIEW_BLOB) &&
2731                      view->parent == VIEW(REQ_VIEW_TREE))) {
2732                         int line;
2734                         view = view->parent;
2735                         line = view->lineno;
2736                         move_view(view, request);
2737                         if (view_is_displayed(view))
2738                                 update_view_title(view);
2739                         if (line != view->lineno)
2740                                 view->ops->request(view, REQ_ENTER,
2741                                                    &view->line[view->lineno]);
2743                 } else {
2744                         move_view(view, request);
2745                 }
2746                 break;
2748         case REQ_VIEW_NEXT:
2749         {
2750                 int nviews = displayed_views();
2751                 int next_view = (current_view + 1) % nviews;
2753                 if (next_view == current_view) {
2754                         report("Only one view is displayed");
2755                         break;
2756                 }
2758                 current_view = next_view;
2759                 /* Blur out the title of the previous view. */
2760                 update_view_title(view);
2761                 report("");
2762                 break;
2763         }
2764         case REQ_REFRESH:
2765                 report("Refreshing is not yet supported for the %s view", view->name);
2766                 break;
2768         case REQ_MAXIMIZE:
2769                 if (displayed_views() == 2)
2770                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2771                 break;
2773         case REQ_TOGGLE_LINENO:
2774                 opt_line_number = !opt_line_number;
2775                 redraw_display();
2776                 break;
2778         case REQ_TOGGLE_DATE:
2779                 opt_date = !opt_date;
2780                 redraw_display();
2781                 break;
2783         case REQ_TOGGLE_AUTHOR:
2784                 opt_author = !opt_author;
2785                 redraw_display();
2786                 break;
2788         case REQ_TOGGLE_REV_GRAPH:
2789                 opt_rev_graph = !opt_rev_graph;
2790                 redraw_display();
2791                 break;
2793         case REQ_TOGGLE_REFS:
2794                 opt_show_refs = !opt_show_refs;
2795                 redraw_display();
2796                 break;
2798         case REQ_PROMPT:
2799                 /* Always reload^Wrerun commands from the prompt. */
2800                 open_view(view, opt_request, OPEN_RELOAD);
2801                 break;
2803         case REQ_SEARCH:
2804         case REQ_SEARCH_BACK:
2805                 search_view(view, request);
2806                 break;
2808         case REQ_FIND_NEXT:
2809         case REQ_FIND_PREV:
2810                 find_next(view, request);
2811                 break;
2813         case REQ_STOP_LOADING:
2814                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2815                         view = &views[i];
2816                         if (view->pipe)
2817                                 report("Stopped loading the %s view", view->name),
2818                         end_update(view, TRUE);
2819                 }
2820                 break;
2822         case REQ_SHOW_VERSION:
2823                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2824                 return TRUE;
2826         case REQ_SCREEN_RESIZE:
2827                 resize_display();
2828                 /* Fall-through */
2829         case REQ_SCREEN_REDRAW:
2830                 redraw_display();
2831                 break;
2833         case REQ_EDIT:
2834                 report("Nothing to edit");
2835                 break;
2838         case REQ_ENTER:
2839                 report("Nothing to enter");
2840                 break;
2843         case REQ_VIEW_CLOSE:
2844                 /* XXX: Mark closed views by letting view->parent point to the
2845                  * view itself. Parents to closed view should never be
2846                  * followed. */
2847                 if (view->parent &&
2848                     view->parent->parent != view->parent) {
2849                         memset(display, 0, sizeof(display));
2850                         current_view = 0;
2851                         display[current_view] = view->parent;
2852                         view->parent = view;
2853                         resize_display();
2854                         redraw_display();
2855                         break;
2856                 }
2857                 /* Fall-through */
2858         case REQ_QUIT:
2859                 return FALSE;
2861         default:
2862                 /* An unknown key will show most commonly used commands. */
2863                 report("Unknown key, press 'h' for help");
2864                 return TRUE;
2865         }
2867         return TRUE;
2871 /*
2872  * Pager backend
2873  */
2875 static bool
2876 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2878         char *text = line->data;
2880         if (opt_line_number && draw_lineno(view, lineno))
2881                 return TRUE;
2883         draw_text(view, line->type, text, TRUE);
2884         return TRUE;
2887 static bool
2888 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2890         char refbuf[SIZEOF_STR];
2891         char *ref = NULL;
2892         FILE *pipe;
2894         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2895                 return TRUE;
2897         pipe = popen(refbuf, "r");
2898         if (!pipe)
2899                 return TRUE;
2901         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2902                 ref = chomp_string(ref);
2903         pclose(pipe);
2905         if (!ref || !*ref)
2906                 return TRUE;
2908         /* This is the only fatal call, since it can "corrupt" the buffer. */
2909         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2910                 return FALSE;
2912         return TRUE;
2915 static void
2916 add_pager_refs(struct view *view, struct line *line)
2918         char buf[SIZEOF_STR];
2919         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2920         struct ref **refs;
2921         size_t bufpos = 0, refpos = 0;
2922         const char *sep = "Refs: ";
2923         bool is_tag = FALSE;
2925         assert(line->type == LINE_COMMIT);
2927         refs = get_refs(commit_id);
2928         if (!refs) {
2929                 if (view == VIEW(REQ_VIEW_DIFF))
2930                         goto try_add_describe_ref;
2931                 return;
2932         }
2934         do {
2935                 struct ref *ref = refs[refpos];
2936                 char *fmt = ref->tag    ? "%s[%s]" :
2937                             ref->remote ? "%s<%s>" : "%s%s";
2939                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2940                         return;
2941                 sep = ", ";
2942                 if (ref->tag)
2943                         is_tag = TRUE;
2944         } while (refs[refpos++]->next);
2946         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2947 try_add_describe_ref:
2948                 /* Add <tag>-g<commit_id> "fake" reference. */
2949                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2950                         return;
2951         }
2953         if (bufpos == 0)
2954                 return;
2956         if (!realloc_lines(view, view->line_size + 1))
2957                 return;
2959         add_line_text(view, buf, LINE_PP_REFS);
2962 static bool
2963 pager_read(struct view *view, char *data)
2965         struct line *line;
2967         if (!data)
2968                 return TRUE;
2970         line = add_line_text(view, data, get_line_type(data));
2971         if (!line)
2972                 return FALSE;
2974         if (line->type == LINE_COMMIT &&
2975             (view == VIEW(REQ_VIEW_DIFF) ||
2976              view == VIEW(REQ_VIEW_LOG)))
2977                 add_pager_refs(view, line);
2979         return TRUE;
2982 static enum request
2983 pager_request(struct view *view, enum request request, struct line *line)
2985         int split = 0;
2987         if (request != REQ_ENTER)
2988                 return request;
2990         if (line->type == LINE_COMMIT &&
2991            (view == VIEW(REQ_VIEW_LOG) ||
2992             view == VIEW(REQ_VIEW_PAGER))) {
2993                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2994                 split = 1;
2995         }
2997         /* Always scroll the view even if it was split. That way
2998          * you can use Enter to scroll through the log view and
2999          * split open each commit diff. */
3000         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3002         /* FIXME: A minor workaround. Scrolling the view will call report("")
3003          * but if we are scrolling a non-current view this won't properly
3004          * update the view title. */
3005         if (split)
3006                 update_view_title(view);
3008         return REQ_NONE;
3011 static bool
3012 pager_grep(struct view *view, struct line *line)
3014         regmatch_t pmatch;
3015         char *text = line->data;
3017         if (!*text)
3018                 return FALSE;
3020         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3021                 return FALSE;
3023         return TRUE;
3026 static void
3027 pager_select(struct view *view, struct line *line)
3029         if (line->type == LINE_COMMIT) {
3030                 char *text = (char *)line->data + STRING_SIZE("commit ");
3032                 if (view != VIEW(REQ_VIEW_PAGER))
3033                         string_copy_rev(view->ref, text);
3034                 string_copy_rev(ref_commit, text);
3035         }
3038 static struct view_ops pager_ops = {
3039         "line",
3040         NULL,
3041         pager_read,
3042         pager_draw,
3043         pager_request,
3044         pager_grep,
3045         pager_select,
3046 };
3049 /*
3050  * Help backend
3051  */
3053 static bool
3054 help_open(struct view *view)
3056         char buf[BUFSIZ];
3057         int lines = ARRAY_SIZE(req_info) + 2;
3058         int i;
3060         if (view->lines > 0)
3061                 return TRUE;
3063         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3064                 if (!req_info[i].request)
3065                         lines++;
3067         lines += run_requests + 1;
3069         view->line = calloc(lines, sizeof(*view->line));
3070         if (!view->line)
3071                 return FALSE;
3073         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3075         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3076                 char *key;
3078                 if (req_info[i].request == REQ_NONE)
3079                         continue;
3081                 if (!req_info[i].request) {
3082                         add_line_text(view, "", LINE_DEFAULT);
3083                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3084                         continue;
3085                 }
3087                 key = get_key(req_info[i].request);
3088                 if (!*key)
3089                         key = "(no key defined)";
3091                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3092                         continue;
3094                 add_line_text(view, buf, LINE_DEFAULT);
3095         }
3097         if (run_requests) {
3098                 add_line_text(view, "", LINE_DEFAULT);
3099                 add_line_text(view, "External commands:", LINE_DEFAULT);
3100         }
3102         for (i = 0; i < run_requests; i++) {
3103                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3104                 char *key;
3106                 if (!req)
3107                         continue;
3109                 key = get_key_name(req->key);
3110                 if (!*key)
3111                         key = "(no key defined)";
3113                 if (!string_format(buf, "    %-10s %-14s `%s`",
3114                                    keymap_table[req->keymap].name,
3115                                    key, req->cmd))
3116                         continue;
3118                 add_line_text(view, buf, LINE_DEFAULT);
3119         }
3121         return TRUE;
3124 static struct view_ops help_ops = {
3125         "line",
3126         help_open,
3127         NULL,
3128         pager_draw,
3129         pager_request,
3130         pager_grep,
3131         pager_select,
3132 };
3135 /*
3136  * Tree backend
3137  */
3139 struct tree_stack_entry {
3140         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3141         unsigned long lineno;           /* Line number to restore */
3142         char *name;                     /* Position of name in opt_path */
3143 };
3145 /* The top of the path stack. */
3146 static struct tree_stack_entry *tree_stack = NULL;
3147 unsigned long tree_lineno = 0;
3149 static void
3150 pop_tree_stack_entry(void)
3152         struct tree_stack_entry *entry = tree_stack;
3154         tree_lineno = entry->lineno;
3155         entry->name[0] = 0;
3156         tree_stack = entry->prev;
3157         free(entry);
3160 static void
3161 push_tree_stack_entry(char *name, unsigned long lineno)
3163         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3164         size_t pathlen = strlen(opt_path);
3166         if (!entry)
3167                 return;
3169         entry->prev = tree_stack;
3170         entry->name = opt_path + pathlen;
3171         tree_stack = entry;
3173         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3174                 pop_tree_stack_entry();
3175                 return;
3176         }
3178         /* Move the current line to the first tree entry. */
3179         tree_lineno = 1;
3180         entry->lineno = lineno;
3183 /* Parse output from git-ls-tree(1):
3184  *
3185  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3186  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3187  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3188  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3189  */
3191 #define SIZEOF_TREE_ATTR \
3192         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3194 #define TREE_UP_FORMAT "040000 tree %s\t.."
3196 static int
3197 tree_compare_entry(enum line_type type1, char *name1,
3198                    enum line_type type2, char *name2)
3200         if (type1 != type2) {
3201                 if (type1 == LINE_TREE_DIR)
3202                         return -1;
3203                 return 1;
3204         }
3206         return strcmp(name1, name2);
3209 static char *
3210 tree_path(struct line *line)
3212         char *path = line->data;
3214         return path + SIZEOF_TREE_ATTR;
3217 static bool
3218 tree_read(struct view *view, char *text)
3220         size_t textlen = text ? strlen(text) : 0;
3221         char buf[SIZEOF_STR];
3222         unsigned long pos;
3223         enum line_type type;
3224         bool first_read = view->lines == 0;
3226         if (!text)
3227                 return TRUE;
3228         if (textlen <= SIZEOF_TREE_ATTR)
3229                 return FALSE;
3231         type = text[STRING_SIZE("100644 ")] == 't'
3232              ? LINE_TREE_DIR : LINE_TREE_FILE;
3234         if (first_read) {
3235                 /* Add path info line */
3236                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3237                     !realloc_lines(view, view->line_size + 1) ||
3238                     !add_line_text(view, buf, LINE_DEFAULT))
3239                         return FALSE;
3241                 /* Insert "link" to parent directory. */
3242                 if (*opt_path) {
3243                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3244                             !realloc_lines(view, view->line_size + 1) ||
3245                             !add_line_text(view, buf, LINE_TREE_DIR))
3246                                 return FALSE;
3247                 }
3248         }
3250         /* Strip the path part ... */
3251         if (*opt_path) {
3252                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3253                 size_t striplen = strlen(opt_path);
3254                 char *path = text + SIZEOF_TREE_ATTR;
3256                 if (pathlen > striplen)
3257                         memmove(path, path + striplen,
3258                                 pathlen - striplen + 1);
3259         }
3261         /* Skip "Directory ..." and ".." line. */
3262         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3263                 struct line *line = &view->line[pos];
3264                 char *path1 = tree_path(line);
3265                 char *path2 = text + SIZEOF_TREE_ATTR;
3266                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3268                 if (cmp <= 0)
3269                         continue;
3271                 text = strdup(text);
3272                 if (!text)
3273                         return FALSE;
3275                 if (view->lines > pos)
3276                         memmove(&view->line[pos + 1], &view->line[pos],
3277                                 (view->lines - pos) * sizeof(*line));
3279                 line = &view->line[pos];
3280                 line->data = text;
3281                 line->type = type;
3282                 view->lines++;
3283                 return TRUE;
3284         }
3286         if (!add_line_text(view, text, type))
3287                 return FALSE;
3289         if (tree_lineno > view->lineno) {
3290                 view->lineno = tree_lineno;
3291                 tree_lineno = 0;
3292         }
3294         return TRUE;
3297 static enum request
3298 tree_request(struct view *view, enum request request, struct line *line)
3300         enum open_flags flags;
3302         if (request == REQ_VIEW_BLAME) {
3303                 char *filename = tree_path(line);
3305                 if (line->type == LINE_TREE_DIR) {
3306                         report("Cannot show blame for directory %s", opt_path);
3307                         return REQ_NONE;
3308                 }
3310                 string_copy(opt_ref, view->vid);
3311                 string_format(opt_file, "%s%s", opt_path, filename);
3312                 return request;
3313         }
3314         if (request == REQ_TREE_PARENT) {
3315                 if (*opt_path) {
3316                         /* fake 'cd  ..' */
3317                         request = REQ_ENTER;
3318                         line = &view->line[1];
3319                 } else {
3320                         /* quit view if at top of tree */
3321                         return REQ_VIEW_CLOSE;
3322                 }
3323         }
3324         if (request != REQ_ENTER)
3325                 return request;
3327         /* Cleanup the stack if the tree view is at a different tree. */
3328         while (!*opt_path && tree_stack)
3329                 pop_tree_stack_entry();
3331         switch (line->type) {
3332         case LINE_TREE_DIR:
3333                 /* Depending on whether it is a subdir or parent (updir?) link
3334                  * mangle the path buffer. */
3335                 if (line == &view->line[1] && *opt_path) {
3336                         pop_tree_stack_entry();
3338                 } else {
3339                         char *basename = tree_path(line);
3341                         push_tree_stack_entry(basename, view->lineno);
3342                 }
3344                 /* Trees and subtrees share the same ID, so they are not not
3345                  * unique like blobs. */
3346                 flags = OPEN_RELOAD;
3347                 request = REQ_VIEW_TREE;
3348                 break;
3350         case LINE_TREE_FILE:
3351                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3352                 request = REQ_VIEW_BLOB;
3353                 break;
3355         default:
3356                 return TRUE;
3357         }
3359         open_view(view, request, flags);
3360         if (request == REQ_VIEW_TREE) {
3361                 view->lineno = tree_lineno;
3362         }
3364         return REQ_NONE;
3367 static void
3368 tree_select(struct view *view, struct line *line)
3370         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3372         if (line->type == LINE_TREE_FILE) {
3373                 string_copy_rev(ref_blob, text);
3375         } else if (line->type != LINE_TREE_DIR) {
3376                 return;
3377         }
3379         string_copy_rev(view->ref, text);
3382 static struct view_ops tree_ops = {
3383         "file",
3384         NULL,
3385         tree_read,
3386         pager_draw,
3387         tree_request,
3388         pager_grep,
3389         tree_select,
3390 };
3392 static bool
3393 blob_read(struct view *view, char *line)
3395         if (!line)
3396                 return TRUE;
3397         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3400 static struct view_ops blob_ops = {
3401         "line",
3402         NULL,
3403         blob_read,
3404         pager_draw,
3405         pager_request,
3406         pager_grep,
3407         pager_select,
3408 };
3410 /*
3411  * Blame backend
3412  *
3413  * Loading the blame view is a two phase job:
3414  *
3415  *  1. File content is read either using opt_file from the
3416  *     filesystem or using git-cat-file.
3417  *  2. Then blame information is incrementally added by
3418  *     reading output from git-blame.
3419  */
3421 struct blame_commit {
3422         char id[SIZEOF_REV];            /* SHA1 ID. */
3423         char title[128];                /* First line of the commit message. */
3424         char author[75];                /* Author of the commit. */
3425         struct tm time;                 /* Date from the author ident. */
3426         char filename[128];             /* Name of file. */
3427 };
3429 struct blame {
3430         struct blame_commit *commit;
3431         unsigned int header:1;
3432         char text[1];
3433 };
3435 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3436 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3438 static bool
3439 blame_open(struct view *view)
3441         char path[SIZEOF_STR];
3442         char ref[SIZEOF_STR] = "";
3444         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3445                 return FALSE;
3447         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3448                 return FALSE;
3450         if (*opt_ref) {
3451                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3452                         return FALSE;
3453         } else {
3454                 view->pipe = fopen(opt_file, "r");
3455                 if (!view->pipe &&
3456                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3457                         return FALSE;
3458         }
3460         if (!view->pipe)
3461                 view->pipe = popen(view->cmd, "r");
3462         if (!view->pipe)
3463                 return FALSE;
3465         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3466                 return FALSE;
3468         string_format(view->ref, "%s ...", opt_file);
3469         string_copy_rev(view->vid, opt_file);
3470         set_nonblocking_input(TRUE);
3472         if (view->line) {
3473                 int i;
3475                 for (i = 0; i < view->lines; i++)
3476                         free(view->line[i].data);
3477                 free(view->line);
3478         }
3480         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3481         view->offset = view->lines  = view->lineno = 0;
3482         view->line = NULL;
3483         view->start_time = time(NULL);
3485         return TRUE;
3488 static struct blame_commit *
3489 get_blame_commit(struct view *view, const char *id)
3491         size_t i;
3493         for (i = 0; i < view->lines; i++) {
3494                 struct blame *blame = view->line[i].data;
3496                 if (!blame->commit)
3497                         continue;
3499                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3500                         return blame->commit;
3501         }
3503         {
3504                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3506                 if (commit)
3507                         string_ncopy(commit->id, id, SIZEOF_REV);
3508                 return commit;
3509         }
3512 static bool
3513 parse_number(char **posref, size_t *number, size_t min, size_t max)
3515         char *pos = *posref;
3517         *posref = NULL;
3518         pos = strchr(pos + 1, ' ');
3519         if (!pos || !isdigit(pos[1]))
3520                 return FALSE;
3521         *number = atoi(pos + 1);
3522         if (*number < min || *number > max)
3523                 return FALSE;
3525         *posref = pos;
3526         return TRUE;
3529 static struct blame_commit *
3530 parse_blame_commit(struct view *view, char *text, int *blamed)
3532         struct blame_commit *commit;
3533         struct blame *blame;
3534         char *pos = text + SIZEOF_REV - 1;
3535         size_t lineno;
3536         size_t group;
3538         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3539                 return NULL;
3541         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3542             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3543                 return NULL;
3545         commit = get_blame_commit(view, text);
3546         if (!commit)
3547                 return NULL;
3549         *blamed += group;
3550         while (group--) {
3551                 struct line *line = &view->line[lineno + group - 1];
3553                 blame = line->data;
3554                 blame->commit = commit;
3555                 blame->header = !group;
3556                 line->dirty = 1;
3557         }
3559         return commit;
3562 static bool
3563 blame_read_file(struct view *view, char *line)
3565         if (!line) {
3566                 FILE *pipe = NULL;
3568                 if (view->lines > 0)
3569                         pipe = popen(view->cmd, "r");
3570                 else if (!view->parent)
3571                         die("No blame exist for %s", view->vid);
3572                 view->cmd[0] = 0;
3573                 if (!pipe) {
3574                         report("Failed to load blame data");
3575                         return TRUE;
3576                 }
3578                 fclose(view->pipe);
3579                 view->pipe = pipe;
3580                 return FALSE;
3582         } else {
3583                 size_t linelen = strlen(line);
3584                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3586                 blame->commit = NULL;
3587                 strncpy(blame->text, line, linelen);
3588                 blame->text[linelen] = 0;
3589                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3590         }
3593 static bool
3594 match_blame_header(const char *name, char **line)
3596         size_t namelen = strlen(name);
3597         bool matched = !strncmp(name, *line, namelen);
3599         if (matched)
3600                 *line += namelen;
3602         return matched;
3605 static bool
3606 blame_read(struct view *view, char *line)
3608         static struct blame_commit *commit = NULL;
3609         static int blamed = 0;
3610         static time_t author_time;
3612         if (*view->cmd)
3613                 return blame_read_file(view, line);
3615         if (!line) {
3616                 /* Reset all! */
3617                 commit = NULL;
3618                 blamed = 0;
3619                 string_format(view->ref, "%s", view->vid);
3620                 if (view_is_displayed(view)) {
3621                         update_view_title(view);
3622                         redraw_view_from(view, 0);
3623                 }
3624                 return TRUE;
3625         }
3627         if (!commit) {
3628                 commit = parse_blame_commit(view, line, &blamed);
3629                 string_format(view->ref, "%s %2d%%", view->vid,
3630                               blamed * 100 / view->lines);
3632         } else if (match_blame_header("author ", &line)) {
3633                 string_ncopy(commit->author, line, strlen(line));
3635         } else if (match_blame_header("author-time ", &line)) {
3636                 author_time = (time_t) atol(line);
3638         } else if (match_blame_header("author-tz ", &line)) {
3639                 long tz;
3641                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3642                 tz += ('0' - line[2]) * 60 * 60;
3643                 tz += ('0' - line[3]) * 60;
3644                 tz += ('0' - line[4]) * 60;
3646                 if (line[0] == '-')
3647                         tz = -tz;
3649                 author_time -= tz;
3650                 gmtime_r(&author_time, &commit->time);
3652         } else if (match_blame_header("summary ", &line)) {
3653                 string_ncopy(commit->title, line, strlen(line));
3655         } else if (match_blame_header("filename ", &line)) {
3656                 string_ncopy(commit->filename, line, strlen(line));
3657                 commit = NULL;
3658         }
3660         return TRUE;
3663 static bool
3664 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3666         struct blame *blame = line->data;
3667         struct tm *time = NULL;
3668         char *id = NULL, *author = NULL;
3670         if (blame->commit && *blame->commit->filename) {
3671                 id = blame->commit->id;
3672                 author = blame->commit->author;
3673                 time = &blame->commit->time;
3674         }
3676         if (opt_date && draw_date(view, time))
3677                 return TRUE;
3679         if (opt_author &&
3680             draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3681                 return TRUE;
3683         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3684                 return TRUE;
3686         if (draw_lineno(view, lineno))
3687                 return TRUE;
3689         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3690         return TRUE;
3693 static enum request
3694 blame_request(struct view *view, enum request request, struct line *line)
3696         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3697         struct blame *blame = line->data;
3699         switch (request) {
3700         case REQ_ENTER:
3701                 if (!blame->commit) {
3702                         report("No commit loaded yet");
3703                         break;
3704                 }
3706                 if (!strcmp(blame->commit->id, NULL_ID)) {
3707                         char path[SIZEOF_STR];
3709                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3710                                 break;
3711                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3712                 }
3714                 open_view(view, REQ_VIEW_DIFF, flags);
3715                 break;
3717         default:
3718                 return request;
3719         }
3721         return REQ_NONE;
3724 static bool
3725 blame_grep(struct view *view, struct line *line)
3727         struct blame *blame = line->data;
3728         struct blame_commit *commit = blame->commit;
3729         regmatch_t pmatch;
3731 #define MATCH(text, on)                                                 \
3732         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3734         if (commit) {
3735                 char buf[DATE_COLS + 1];
3737                 if (MATCH(commit->title, 1) ||
3738                     MATCH(commit->author, opt_author) ||
3739                     MATCH(commit->id, opt_date))
3740                         return TRUE;
3742                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3743                     MATCH(buf, 1))
3744                         return TRUE;
3745         }
3747         return MATCH(blame->text, 1);
3749 #undef MATCH
3752 static void
3753 blame_select(struct view *view, struct line *line)
3755         struct blame *blame = line->data;
3756         struct blame_commit *commit = blame->commit;
3758         if (!commit)
3759                 return;
3761         if (!strcmp(commit->id, NULL_ID))
3762                 string_ncopy(ref_commit, "HEAD", 4);
3763         else
3764                 string_copy_rev(ref_commit, commit->id);
3767 static struct view_ops blame_ops = {
3768         "line",
3769         blame_open,
3770         blame_read,
3771         blame_draw,
3772         blame_request,
3773         blame_grep,
3774         blame_select,
3775 };
3777 /*
3778  * Status backend
3779  */
3781 struct status {
3782         char status;
3783         struct {
3784                 mode_t mode;
3785                 char rev[SIZEOF_REV];
3786                 char name[SIZEOF_STR];
3787         } old;
3788         struct {
3789                 mode_t mode;
3790                 char rev[SIZEOF_REV];
3791                 char name[SIZEOF_STR];
3792         } new;
3793 };
3795 static char status_onbranch[SIZEOF_STR];
3796 static struct status stage_status;
3797 static enum line_type stage_line_type;
3798 static size_t stage_chunks;
3799 static int *stage_chunk;
3801 /* Get fields from the diff line:
3802  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3803  */
3804 static inline bool
3805 status_get_diff(struct status *file, char *buf, size_t bufsize)
3807         char *old_mode = buf +  1;
3808         char *new_mode = buf +  8;
3809         char *old_rev  = buf + 15;
3810         char *new_rev  = buf + 56;
3811         char *status   = buf + 97;
3813         if (bufsize < 99 ||
3814             old_mode[-1] != ':' ||
3815             new_mode[-1] != ' ' ||
3816             old_rev[-1]  != ' ' ||
3817             new_rev[-1]  != ' ' ||
3818             status[-1]   != ' ')
3819                 return FALSE;
3821         file->status = *status;
3823         string_copy_rev(file->old.rev, old_rev);
3824         string_copy_rev(file->new.rev, new_rev);
3826         file->old.mode = strtoul(old_mode, NULL, 8);
3827         file->new.mode = strtoul(new_mode, NULL, 8);
3829         file->old.name[0] = file->new.name[0] = 0;
3831         return TRUE;
3834 static bool
3835 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3837         struct status *file = NULL;
3838         struct status *unmerged = NULL;
3839         char buf[SIZEOF_STR * 4];
3840         size_t bufsize = 0;
3841         FILE *pipe;
3843         pipe = popen(cmd, "r");
3844         if (!pipe)
3845                 return FALSE;
3847         add_line_data(view, NULL, type);
3849         while (!feof(pipe) && !ferror(pipe)) {
3850                 char *sep;
3851                 size_t readsize;
3853                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3854                 if (!readsize)
3855                         break;
3856                 bufsize += readsize;
3858                 /* Process while we have NUL chars. */
3859                 while ((sep = memchr(buf, 0, bufsize))) {
3860                         size_t sepsize = sep - buf + 1;
3862                         if (!file) {
3863                                 if (!realloc_lines(view, view->line_size + 1))
3864                                         goto error_out;
3866                                 file = calloc(1, sizeof(*file));
3867                                 if (!file)
3868                                         goto error_out;
3870                                 add_line_data(view, file, type);
3871                         }
3873                         /* Parse diff info part. */
3874                         if (status) {
3875                                 file->status = status;
3876                                 if (status == 'A')
3877                                         string_copy(file->old.rev, NULL_ID);
3879                         } else if (!file->status) {
3880                                 if (!status_get_diff(file, buf, sepsize))
3881                                         goto error_out;
3883                                 bufsize -= sepsize;
3884                                 memmove(buf, sep + 1, bufsize);
3886                                 sep = memchr(buf, 0, bufsize);
3887                                 if (!sep)
3888                                         break;
3889                                 sepsize = sep - buf + 1;
3891                                 /* Collapse all 'M'odified entries that
3892                                  * follow a associated 'U'nmerged entry.
3893                                  */
3894                                 if (file->status == 'U') {
3895                                         unmerged = file;
3897                                 } else if (unmerged) {
3898                                         int collapse = !strcmp(buf, unmerged->new.name);
3900                                         unmerged = NULL;
3901                                         if (collapse) {
3902                                                 free(file);
3903                                                 view->lines--;
3904                                                 continue;
3905                                         }
3906                                 }
3907                         }
3909                         /* Grab the old name for rename/copy. */
3910                         if (!*file->old.name &&
3911                             (file->status == 'R' || file->status == 'C')) {
3912                                 sepsize = sep - buf + 1;
3913                                 string_ncopy(file->old.name, buf, sepsize);
3914                                 bufsize -= sepsize;
3915                                 memmove(buf, sep + 1, bufsize);
3917                                 sep = memchr(buf, 0, bufsize);
3918                                 if (!sep)
3919                                         break;
3920                                 sepsize = sep - buf + 1;
3921                         }
3923                         /* git-ls-files just delivers a NUL separated
3924                          * list of file names similar to the second half
3925                          * of the git-diff-* output. */
3926                         string_ncopy(file->new.name, buf, sepsize);
3927                         if (!*file->old.name)
3928                                 string_copy(file->old.name, file->new.name);
3929                         bufsize -= sepsize;
3930                         memmove(buf, sep + 1, bufsize);
3931                         file = NULL;
3932                 }
3933         }
3935         if (ferror(pipe)) {
3936 error_out:
3937                 pclose(pipe);
3938                 return FALSE;
3939         }
3941         if (!view->line[view->lines - 1].data)
3942                 add_line_data(view, NULL, LINE_STAT_NONE);
3944         pclose(pipe);
3945         return TRUE;
3948 /* Don't show unmerged entries in the staged section. */
3949 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3950 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3951 #define STATUS_LIST_OTHER_CMD \
3952         "git ls-files -z --others --exclude-per-directory=.gitignore"
3953 #define STATUS_LIST_NO_HEAD_CMD \
3954         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3956 #define STATUS_DIFF_INDEX_SHOW_CMD \
3957         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3959 #define STATUS_DIFF_FILES_SHOW_CMD \
3960         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3962 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3963         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3965 /* First parse staged info using git-diff-index(1), then parse unstaged
3966  * info using git-diff-files(1), and finally untracked files using
3967  * git-ls-files(1). */
3968 static bool
3969 status_open(struct view *view)
3971         struct stat statbuf;
3972         char exclude[SIZEOF_STR];
3973         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3974         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3975         unsigned long prev_lineno = view->lineno;
3976         char indexstatus = 0;
3977         size_t i;
3979         for (i = 0; i < view->lines; i++)
3980                 free(view->line[i].data);
3981         free(view->line);
3982         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3983         view->line = NULL;
3985         if (!realloc_lines(view, view->line_size + 7))
3986                 return FALSE;
3988         add_line_data(view, NULL, LINE_STAT_HEAD);
3989         if (opt_no_head)
3990                 string_copy(status_onbranch, "Initial commit");
3991         else if (!*opt_head)
3992                 string_copy(status_onbranch, "Not currently on any branch");
3993         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3994                 return FALSE;
3996         if (opt_no_head) {
3997                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3998                 indexstatus = 'A';
3999         }
4001         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4002                 return FALSE;
4004         if (stat(exclude, &statbuf) >= 0) {
4005                 size_t cmdsize = strlen(othercmd);
4007                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4008                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4009                         return FALSE;
4011                 cmdsize = strlen(indexcmd);
4012                 if (opt_no_head &&
4013                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4014                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4015                         return FALSE;
4016         }
4018         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4020         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4021             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4022             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4023                 return FALSE;
4025         /* If all went well restore the previous line number to stay in
4026          * the context or select a line with something that can be
4027          * updated. */
4028         if (prev_lineno >= view->lines)
4029                 prev_lineno = view->lines - 1;
4030         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4031                 prev_lineno++;
4032         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4033                 prev_lineno--;
4035         /* If the above fails, always skip the "On branch" line. */
4036         if (prev_lineno < view->lines)
4037                 view->lineno = prev_lineno;
4038         else
4039                 view->lineno = 1;
4041         if (view->lineno < view->offset)
4042                 view->offset = view->lineno;
4043         else if (view->offset + view->height <= view->lineno)
4044                 view->offset = view->lineno - view->height + 1;
4046         return TRUE;
4049 static bool
4050 status_draw(struct view *view, struct line *line, unsigned int lineno)
4052         struct status *status = line->data;
4053         enum line_type type;
4054         char *text;
4056         if (!status) {
4057                 switch (line->type) {
4058                 case LINE_STAT_STAGED:
4059                         type = LINE_STAT_SECTION;
4060                         text = "Changes to be committed:";
4061                         break;
4063                 case LINE_STAT_UNSTAGED:
4064                         type = LINE_STAT_SECTION;
4065                         text = "Changed but not updated:";
4066                         break;
4068                 case LINE_STAT_UNTRACKED:
4069                         type = LINE_STAT_SECTION;
4070                         text = "Untracked files:";
4071                         break;
4073                 case LINE_STAT_NONE:
4074                         type = LINE_DEFAULT;
4075                         text = "    (no files)";
4076                         break;
4078                 case LINE_STAT_HEAD:
4079                         type = LINE_STAT_HEAD;
4080                         text = status_onbranch;
4081                         break;
4083                 default:
4084                         return FALSE;
4085                 }
4086         } else {
4087                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4089                 buf[0] = status->status;
4090                 if (draw_text(view, line->type, buf, TRUE))
4091                         return TRUE;
4092                 type = LINE_DEFAULT;
4093                 text = status->new.name;
4094         }
4096         draw_text(view, type, text, TRUE);
4097         return TRUE;
4100 static enum request
4101 status_enter(struct view *view, struct line *line)
4103         struct status *status = line->data;
4104         char oldpath[SIZEOF_STR] = "";
4105         char newpath[SIZEOF_STR] = "";
4106         char *info;
4107         size_t cmdsize = 0;
4108         enum open_flags split;
4110         if (line->type == LINE_STAT_NONE ||
4111             (!status && line[1].type == LINE_STAT_NONE)) {
4112                 report("No file to diff");
4113                 return REQ_NONE;
4114         }
4116         if (status) {
4117                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4118                         return REQ_QUIT;
4119                 /* Diffs for unmerged entries are empty when pasing the
4120                  * new path, so leave it empty. */
4121                 if (status->status != 'U' &&
4122                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4123                         return REQ_QUIT;
4124         }
4126         if (opt_cdup[0] &&
4127             line->type != LINE_STAT_UNTRACKED &&
4128             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4129                 return REQ_QUIT;
4131         switch (line->type) {
4132         case LINE_STAT_STAGED:
4133                 if (opt_no_head) {
4134                         if (!string_format_from(opt_cmd, &cmdsize,
4135                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4136                                                 newpath))
4137                                 return REQ_QUIT;
4138                 } else {
4139                         if (!string_format_from(opt_cmd, &cmdsize,
4140                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4141                                                 oldpath, newpath))
4142                                 return REQ_QUIT;
4143                 }
4145                 if (status)
4146                         info = "Staged changes to %s";
4147                 else
4148                         info = "Staged changes";
4149                 break;
4151         case LINE_STAT_UNSTAGED:
4152                 if (!string_format_from(opt_cmd, &cmdsize,
4153                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4154                         return REQ_QUIT;
4155                 if (status)
4156                         info = "Unstaged changes to %s";
4157                 else
4158                         info = "Unstaged changes";
4159                 break;
4161         case LINE_STAT_UNTRACKED:
4162                 if (opt_pipe)
4163                         return REQ_QUIT;
4165                 if (!status) {
4166                         report("No file to show");
4167                         return REQ_NONE;
4168                 }
4170                 opt_pipe = fopen(status->new.name, "r");
4171                 info = "Untracked file %s";
4172                 break;
4174         case LINE_STAT_HEAD:
4175                 return REQ_NONE;
4177         default:
4178                 die("line type %d not handled in switch", line->type);
4179         }
4181         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4182         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4183         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4184                 if (status) {
4185                         stage_status = *status;
4186                 } else {
4187                         memset(&stage_status, 0, sizeof(stage_status));
4188                 }
4190                 stage_line_type = line->type;
4191                 stage_chunks = 0;
4192                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4193         }
4195         return REQ_NONE;
4198 static bool
4199 status_exists(struct status *status, enum line_type type)
4201         struct view *view = VIEW(REQ_VIEW_STATUS);
4202         struct line *line;
4204         for (line = view->line; line < view->line + view->lines; line++) {
4205                 struct status *pos = line->data;
4207                 if (line->type == type && pos &&
4208                     !strcmp(status->new.name, pos->new.name))
4209                         return TRUE;
4210         }
4212         return FALSE;
4216 static FILE *
4217 status_update_prepare(enum line_type type)
4219         char cmd[SIZEOF_STR];
4220         size_t cmdsize = 0;
4222         if (opt_cdup[0] &&
4223             type != LINE_STAT_UNTRACKED &&
4224             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4225                 return NULL;
4227         switch (type) {
4228         case LINE_STAT_STAGED:
4229                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4230                 break;
4232         case LINE_STAT_UNSTAGED:
4233         case LINE_STAT_UNTRACKED:
4234                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4235                 break;
4237         default:
4238                 die("line type %d not handled in switch", type);
4239         }
4241         return popen(cmd, "w");
4244 static bool
4245 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4247         char buf[SIZEOF_STR];
4248         size_t bufsize = 0;
4249         size_t written = 0;
4251         switch (type) {
4252         case LINE_STAT_STAGED:
4253                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4254                                         status->old.mode,
4255                                         status->old.rev,
4256                                         status->old.name, 0))
4257                         return FALSE;
4258                 break;
4260         case LINE_STAT_UNSTAGED:
4261         case LINE_STAT_UNTRACKED:
4262                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4263                         return FALSE;
4264                 break;
4266         default:
4267                 die("line type %d not handled in switch", type);
4268         }
4270         while (!ferror(pipe) && written < bufsize) {
4271                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4272         }
4274         return written == bufsize;
4277 static bool
4278 status_update_file(struct status *status, enum line_type type)
4280         FILE *pipe = status_update_prepare(type);
4281         bool result;
4283         if (!pipe)
4284                 return FALSE;
4286         result = status_update_write(pipe, status, type);
4287         pclose(pipe);
4288         return result;
4291 static bool
4292 status_update_files(struct view *view, struct line *line)
4294         FILE *pipe = status_update_prepare(line->type);
4295         bool result = TRUE;
4296         struct line *pos = view->line + view->lines;
4297         int files = 0;
4298         int file, done;
4300         if (!pipe)
4301                 return FALSE;
4303         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4304                 files++;
4306         for (file = 0, done = 0; result && file < files; line++, file++) {
4307                 int almost_done = file * 100 / files;
4309                 if (almost_done > done) {
4310                         done = almost_done;
4311                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4312                                       file, files, done);
4313                         update_view_title(view);
4314                 }
4315                 result = status_update_write(pipe, line->data, line->type);
4316         }
4318         pclose(pipe);
4319         return result;
4322 static bool
4323 status_update(struct view *view)
4325         struct line *line = &view->line[view->lineno];
4327         assert(view->lines);
4329         if (!line->data) {
4330                 /* This should work even for the "On branch" line. */
4331                 if (line < view->line + view->lines && !line[1].data) {
4332                         report("Nothing to update");
4333                         return FALSE;
4334                 }
4336                 if (!status_update_files(view, line + 1)) {
4337                         report("Failed to update file status");
4338                         return FALSE;
4339                 }
4341         } else if (!status_update_file(line->data, line->type)) {
4342                 report("Failed to update file status");
4343                 return FALSE;
4344         }
4346         return TRUE;
4349 static enum request
4350 status_request(struct view *view, enum request request, struct line *line)
4352         struct status *status = line->data;
4354         switch (request) {
4355         case REQ_STATUS_UPDATE:
4356                 if (!status_update(view))
4357                         return REQ_NONE;
4358                 break;
4360         case REQ_STATUS_MERGE:
4361                 if (!status || status->status != 'U') {
4362                         report("Merging only possible for files with unmerged status ('U').");
4363                         return REQ_NONE;
4364                 }
4365                 open_mergetool(status->new.name);
4366                 break;
4368         case REQ_EDIT:
4369                 if (!status)
4370                         return request;
4372                 open_editor(status->status != '?', status->new.name);
4373                 break;
4375         case REQ_VIEW_BLAME:
4376                 if (status) {
4377                         string_copy(opt_file, status->new.name);
4378                         opt_ref[0] = 0;
4379                 }
4380                 return request;
4382         case REQ_ENTER:
4383                 /* After returning the status view has been split to
4384                  * show the stage view. No further reloading is
4385                  * necessary. */
4386                 status_enter(view, line);
4387                 return REQ_NONE;
4389         case REQ_REFRESH:
4390                 /* Simply reload the view. */
4391                 break;
4393         default:
4394                 return request;
4395         }
4397         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4399         return REQ_NONE;
4402 static void
4403 status_select(struct view *view, struct line *line)
4405         struct status *status = line->data;
4406         char file[SIZEOF_STR] = "all files";
4407         char *text;
4408         char *key;
4410         if (status && !string_format(file, "'%s'", status->new.name))
4411                 return;
4413         if (!status && line[1].type == LINE_STAT_NONE)
4414                 line++;
4416         switch (line->type) {
4417         case LINE_STAT_STAGED:
4418                 text = "Press %s to unstage %s for commit";
4419                 break;
4421         case LINE_STAT_UNSTAGED:
4422                 text = "Press %s to stage %s for commit";
4423                 break;
4425         case LINE_STAT_UNTRACKED:
4426                 text = "Press %s to stage %s for addition";
4427                 break;
4429         case LINE_STAT_HEAD:
4430         case LINE_STAT_NONE:
4431                 text = "Nothing to update";
4432                 break;
4434         default:
4435                 die("line type %d not handled in switch", line->type);
4436         }
4438         if (status && status->status == 'U') {
4439                 text = "Press %s to resolve conflict in %s";
4440                 key = get_key(REQ_STATUS_MERGE);
4442         } else {
4443                 key = get_key(REQ_STATUS_UPDATE);
4444         }
4446         string_format(view->ref, text, key, file);
4449 static bool
4450 status_grep(struct view *view, struct line *line)
4452         struct status *status = line->data;
4453         enum { S_STATUS, S_NAME, S_END } state;
4454         char buf[2] = "?";
4455         regmatch_t pmatch;
4457         if (!status)
4458                 return FALSE;
4460         for (state = S_STATUS; state < S_END; state++) {
4461                 char *text;
4463                 switch (state) {
4464                 case S_NAME:    text = status->new.name;        break;
4465                 case S_STATUS:
4466                         buf[0] = status->status;
4467                         text = buf;
4468                         break;
4470                 default:
4471                         return FALSE;
4472                 }
4474                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4475                         return TRUE;
4476         }
4478         return FALSE;
4481 static struct view_ops status_ops = {
4482         "file",
4483         status_open,
4484         NULL,
4485         status_draw,
4486         status_request,
4487         status_grep,
4488         status_select,
4489 };
4492 static bool
4493 stage_diff_line(FILE *pipe, struct line *line)
4495         char *buf = line->data;
4496         size_t bufsize = strlen(buf);
4497         size_t written = 0;
4499         while (!ferror(pipe) && written < bufsize) {
4500                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4501         }
4503         fputc('\n', pipe);
4505         return written == bufsize;
4508 static bool
4509 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4511         while (line < end) {
4512                 if (!stage_diff_line(pipe, line++))
4513                         return FALSE;
4514                 if (line->type == LINE_DIFF_CHUNK ||
4515                     line->type == LINE_DIFF_HEADER)
4516                         break;
4517         }
4519         return TRUE;
4522 static struct line *
4523 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4525         for (; view->line < line; line--)
4526                 if (line->type == type)
4527                         return line;
4529         return NULL;
4532 static bool
4533 stage_update_chunk(struct view *view, struct line *chunk)
4535         char cmd[SIZEOF_STR];
4536         size_t cmdsize = 0;
4537         struct line *diff_hdr;
4538         FILE *pipe;
4540         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4541         if (!diff_hdr)
4542                 return FALSE;
4544         if (opt_cdup[0] &&
4545             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4546                 return FALSE;
4548         if (!string_format_from(cmd, &cmdsize,
4549                                 "git apply --whitespace=nowarn --cached %s - && "
4550                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4551                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4552                 return FALSE;
4554         pipe = popen(cmd, "w");
4555         if (!pipe)
4556                 return FALSE;
4558         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4559             !stage_diff_write(pipe, chunk, view->line + view->lines))
4560                 chunk = NULL;
4562         pclose(pipe);
4564         return chunk ? TRUE : FALSE;
4567 static bool
4568 stage_update(struct view *view, struct line *line)
4570         struct line *chunk = NULL;
4572         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4573                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4575         if (chunk) {
4576                 if (!stage_update_chunk(view, chunk)) {
4577                         report("Failed to apply chunk");
4578                         return FALSE;
4579                 }
4581         } else if (!stage_status.status) {
4582                 view = VIEW(REQ_VIEW_STATUS);
4584                 for (line = view->line; line < view->line + view->lines; line++)
4585                         if (line->type == stage_line_type)
4586                                 break;
4588                 if (!status_update_files(view, line + 1)) {
4589                         report("Failed to update files");
4590                         return FALSE;
4591                 }
4593         } else if (!status_update_file(&stage_status, stage_line_type)) {
4594                 report("Failed to update file");
4595                 return FALSE;
4596         }
4598         return TRUE;
4601 static void
4602 stage_next(struct view *view, struct line *line)
4604         int i;
4606         if (!stage_chunks) {
4607                 static size_t alloc = 0;
4608                 int *tmp;
4610                 for (line = view->line; line < view->line + view->lines; line++) {
4611                         if (line->type != LINE_DIFF_CHUNK)
4612                                 continue;
4614                         tmp = realloc_items(stage_chunk, &alloc,
4615                                             stage_chunks, sizeof(*tmp));
4616                         if (!tmp) {
4617                                 report("Allocation failure");
4618                                 return;
4619                         }
4621                         stage_chunk = tmp;
4622                         stage_chunk[stage_chunks++] = line - view->line;
4623                 }
4624         }
4626         for (i = 0; i < stage_chunks; i++) {
4627                 if (stage_chunk[i] > view->lineno) {
4628                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4629                         report("Chunk %d of %d", i + 1, stage_chunks);
4630                         return;
4631                 }
4632         }
4634         report("No next chunk found");
4637 static enum request
4638 stage_request(struct view *view, enum request request, struct line *line)
4640         switch (request) {
4641         case REQ_STATUS_UPDATE:
4642                 if (!stage_update(view, line))
4643                         return REQ_NONE;
4644                 break;
4646         case REQ_STAGE_NEXT:
4647                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4648                         report("File is untracked; press %s to add",
4649                                get_key(REQ_STATUS_UPDATE));
4650                         return REQ_NONE;
4651                 }
4652                 stage_next(view, line);
4653                 return REQ_NONE;
4655         case REQ_EDIT:
4656                 if (!stage_status.new.name[0])
4657                         return request;
4659                 open_editor(stage_status.status != '?', stage_status.new.name);
4660                 break;
4662         case REQ_REFRESH:
4663                 /* Reload everything ... */
4664                 break;
4666         case REQ_VIEW_BLAME:
4667                 if (stage_status.new.name[0]) {
4668                         string_copy(opt_file, stage_status.new.name);
4669                         opt_ref[0] = 0;
4670                 }
4671                 return request;
4673         case REQ_ENTER:
4674                 return pager_request(view, request, line);
4676         default:
4677                 return request;
4678         }
4680         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4682         /* Check whether the staged entry still exists, and close the
4683          * stage view if it doesn't. */
4684         if (!status_exists(&stage_status, stage_line_type))
4685                 return REQ_VIEW_CLOSE;
4687         if (stage_line_type == LINE_STAT_UNTRACKED)
4688                 opt_pipe = fopen(stage_status.new.name, "r");
4689         else
4690                 string_copy(opt_cmd, view->cmd);
4691         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4693         return REQ_NONE;
4696 static struct view_ops stage_ops = {
4697         "line",
4698         NULL,
4699         pager_read,
4700         pager_draw,
4701         stage_request,
4702         pager_grep,
4703         pager_select,
4704 };
4707 /*
4708  * Revision graph
4709  */
4711 struct commit {
4712         char id[SIZEOF_REV];            /* SHA1 ID. */
4713         char title[128];                /* First line of the commit message. */
4714         char author[75];                /* Author of the commit. */
4715         struct tm time;                 /* Date from the author ident. */
4716         struct ref **refs;              /* Repository references. */
4717         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4718         size_t graph_size;              /* The width of the graph array. */
4719         bool has_parents;               /* Rewritten --parents seen. */
4720 };
4722 /* Size of rev graph with no  "padding" columns */
4723 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4725 struct rev_graph {
4726         struct rev_graph *prev, *next, *parents;
4727         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4728         size_t size;
4729         struct commit *commit;
4730         size_t pos;
4731         unsigned int boundary:1;
4732 };
4734 /* Parents of the commit being visualized. */
4735 static struct rev_graph graph_parents[4];
4737 /* The current stack of revisions on the graph. */
4738 static struct rev_graph graph_stacks[4] = {
4739         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4740         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4741         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4742         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4743 };
4745 static inline bool
4746 graph_parent_is_merge(struct rev_graph *graph)
4748         return graph->parents->size > 1;
4751 static inline void
4752 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4754         struct commit *commit = graph->commit;
4756         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4757                 commit->graph[commit->graph_size++] = symbol;
4760 static void
4761 done_rev_graph(struct rev_graph *graph)
4763         if (graph_parent_is_merge(graph) &&
4764             graph->pos < graph->size - 1 &&
4765             graph->next->size == graph->size + graph->parents->size - 1) {
4766                 size_t i = graph->pos + graph->parents->size - 1;
4768                 graph->commit->graph_size = i * 2;
4769                 while (i < graph->next->size - 1) {
4770                         append_to_rev_graph(graph, ' ');
4771                         append_to_rev_graph(graph, '\\');
4772                         i++;
4773                 }
4774         }
4776         graph->size = graph->pos = 0;
4777         graph->commit = NULL;
4778         memset(graph->parents, 0, sizeof(*graph->parents));
4781 static void
4782 push_rev_graph(struct rev_graph *graph, char *parent)
4784         int i;
4786         /* "Collapse" duplicate parents lines.
4787          *
4788          * FIXME: This needs to also update update the drawn graph but
4789          * for now it just serves as a method for pruning graph lines. */
4790         for (i = 0; i < graph->size; i++)
4791                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4792                         return;
4794         if (graph->size < SIZEOF_REVITEMS) {
4795                 string_copy_rev(graph->rev[graph->size++], parent);
4796         }
4799 static chtype
4800 get_rev_graph_symbol(struct rev_graph *graph)
4802         chtype symbol;
4804         if (graph->boundary)
4805                 symbol = REVGRAPH_BOUND;
4806         else if (graph->parents->size == 0)
4807                 symbol = REVGRAPH_INIT;
4808         else if (graph_parent_is_merge(graph))
4809                 symbol = REVGRAPH_MERGE;
4810         else if (graph->pos >= graph->size)
4811                 symbol = REVGRAPH_BRANCH;
4812         else
4813                 symbol = REVGRAPH_COMMIT;
4815         return symbol;
4818 static void
4819 draw_rev_graph(struct rev_graph *graph)
4821         struct rev_filler {
4822                 chtype separator, line;
4823         };
4824         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4825         static struct rev_filler fillers[] = {
4826                 { ' ',  '|' },
4827                 { '`',  '.' },
4828                 { '\'', ' ' },
4829                 { '/',  ' ' },
4830         };
4831         chtype symbol = get_rev_graph_symbol(graph);
4832         struct rev_filler *filler;
4833         size_t i;
4835         if (opt_line_graphics)
4836                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4838         filler = &fillers[DEFAULT];
4840         for (i = 0; i < graph->pos; i++) {
4841                 append_to_rev_graph(graph, filler->line);
4842                 if (graph_parent_is_merge(graph->prev) &&
4843                     graph->prev->pos == i)
4844                         filler = &fillers[RSHARP];
4846                 append_to_rev_graph(graph, filler->separator);
4847         }
4849         /* Place the symbol for this revision. */
4850         append_to_rev_graph(graph, symbol);
4852         if (graph->prev->size > graph->size)
4853                 filler = &fillers[RDIAG];
4854         else
4855                 filler = &fillers[DEFAULT];
4857         i++;
4859         for (; i < graph->size; i++) {
4860                 append_to_rev_graph(graph, filler->separator);
4861                 append_to_rev_graph(graph, filler->line);
4862                 if (graph_parent_is_merge(graph->prev) &&
4863                     i < graph->prev->pos + graph->parents->size)
4864                         filler = &fillers[RSHARP];
4865                 if (graph->prev->size > graph->size)
4866                         filler = &fillers[LDIAG];
4867         }
4869         if (graph->prev->size > graph->size) {
4870                 append_to_rev_graph(graph, filler->separator);
4871                 if (filler->line != ' ')
4872                         append_to_rev_graph(graph, filler->line);
4873         }
4876 /* Prepare the next rev graph */
4877 static void
4878 prepare_rev_graph(struct rev_graph *graph)
4880         size_t i;
4882         /* First, traverse all lines of revisions up to the active one. */
4883         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4884                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4885                         break;
4887                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4888         }
4890         /* Interleave the new revision parent(s). */
4891         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4892                 push_rev_graph(graph->next, graph->parents->rev[i]);
4894         /* Lastly, put any remaining revisions. */
4895         for (i = graph->pos + 1; i < graph->size; i++)
4896                 push_rev_graph(graph->next, graph->rev[i]);
4899 static void
4900 update_rev_graph(struct rev_graph *graph)
4902         /* If this is the finalizing update ... */
4903         if (graph->commit)
4904                 prepare_rev_graph(graph);
4906         /* Graph visualization needs a one rev look-ahead,
4907          * so the first update doesn't visualize anything. */
4908         if (!graph->prev->commit)
4909                 return;
4911         draw_rev_graph(graph->prev);
4912         done_rev_graph(graph->prev->prev);
4916 /*
4917  * Main view backend
4918  */
4920 static bool
4921 main_draw(struct view *view, struct line *line, unsigned int lineno)
4923         struct commit *commit = line->data;
4925         if (!*commit->author)
4926                 return FALSE;
4928         if (opt_date && draw_date(view, &commit->time))
4929                 return TRUE;
4931         if (opt_author &&
4932             draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4933                 return TRUE;
4935         if (opt_rev_graph && commit->graph_size &&
4936             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4937                 return TRUE;
4939         if (opt_show_refs && commit->refs) {
4940                 size_t i = 0;
4942                 do {
4943                         enum line_type type;
4945                         if (commit->refs[i]->head)
4946                                 type = LINE_MAIN_HEAD;
4947                         else if (commit->refs[i]->ltag)
4948                                 type = LINE_MAIN_LOCAL_TAG;
4949                         else if (commit->refs[i]->tag)
4950                                 type = LINE_MAIN_TAG;
4951                         else if (commit->refs[i]->tracked)
4952                                 type = LINE_MAIN_TRACKED;
4953                         else if (commit->refs[i]->remote)
4954                                 type = LINE_MAIN_REMOTE;
4955                         else
4956                                 type = LINE_MAIN_REF;
4958                         if (draw_text(view, type, "[", TRUE) ||
4959                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
4960                             draw_text(view, type, "]", TRUE))
4961                                 return TRUE;
4963                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4964                                 return TRUE;
4965                 } while (commit->refs[i++]->next);
4966         }
4968         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4969         return TRUE;
4972 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4973 static bool
4974 main_read(struct view *view, char *line)
4976         static struct rev_graph *graph = graph_stacks;
4977         enum line_type type;
4978         struct commit *commit;
4980         if (!line) {
4981                 if (!view->lines && !view->parent)
4982                         die("No revisions match the given arguments.");
4983                 update_rev_graph(graph);
4984                 return TRUE;
4985         }
4987         type = get_line_type(line);
4988         if (type == LINE_COMMIT) {
4989                 commit = calloc(1, sizeof(struct commit));
4990                 if (!commit)
4991                         return FALSE;
4993                 line += STRING_SIZE("commit ");
4994                 if (*line == '-') {
4995                         graph->boundary = 1;
4996                         line++;
4997                 }
4999                 string_copy_rev(commit->id, line);
5000                 commit->refs = get_refs(commit->id);
5001                 graph->commit = commit;
5002                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5004                 while ((line = strchr(line, ' '))) {
5005                         line++;
5006                         push_rev_graph(graph->parents, line);
5007                         commit->has_parents = TRUE;
5008                 }
5009                 return TRUE;
5010         }
5012         if (!view->lines)
5013                 return TRUE;
5014         commit = view->line[view->lines - 1].data;
5016         switch (type) {
5017         case LINE_PARENT:
5018                 if (commit->has_parents)
5019                         break;
5020                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5021                 break;
5023         case LINE_AUTHOR:
5024         {
5025                 /* Parse author lines where the name may be empty:
5026                  *      author  <email@address.tld> 1138474660 +0100
5027                  */
5028                 char *ident = line + STRING_SIZE("author ");
5029                 char *nameend = strchr(ident, '<');
5030                 char *emailend = strchr(ident, '>');
5032                 if (!nameend || !emailend)
5033                         break;
5035                 update_rev_graph(graph);
5036                 graph = graph->next;
5038                 *nameend = *emailend = 0;
5039                 ident = chomp_string(ident);
5040                 if (!*ident) {
5041                         ident = chomp_string(nameend + 1);
5042                         if (!*ident)
5043                                 ident = "Unknown";
5044                 }
5046                 string_ncopy(commit->author, ident, strlen(ident));
5048                 /* Parse epoch and timezone */
5049                 if (emailend[1] == ' ') {
5050                         char *secs = emailend + 2;
5051                         char *zone = strchr(secs, ' ');
5052                         time_t time = (time_t) atol(secs);
5054                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5055                                 long tz;
5057                                 zone++;
5058                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5059                                 tz += ('0' - zone[2]) * 60 * 60;
5060                                 tz += ('0' - zone[3]) * 60;
5061                                 tz += ('0' - zone[4]) * 60;
5063                                 if (zone[0] == '-')
5064                                         tz = -tz;
5066                                 time -= tz;
5067                         }
5069                         gmtime_r(&time, &commit->time);
5070                 }
5071                 break;
5072         }
5073         default:
5074                 /* Fill in the commit title if it has not already been set. */
5075                 if (commit->title[0])
5076                         break;
5078                 /* Require titles to start with a non-space character at the
5079                  * offset used by git log. */
5080                 if (strncmp(line, "    ", 4))
5081                         break;
5082                 line += 4;
5083                 /* Well, if the title starts with a whitespace character,
5084                  * try to be forgiving.  Otherwise we end up with no title. */
5085                 while (isspace(*line))
5086                         line++;
5087                 if (*line == '\0')
5088                         break;
5089                 /* FIXME: More graceful handling of titles; append "..." to
5090                  * shortened titles, etc. */
5092                 string_ncopy(commit->title, line, strlen(line));
5093         }
5095         return TRUE;
5098 static enum request
5099 main_request(struct view *view, enum request request, struct line *line)
5101         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5103         if (request == REQ_ENTER)
5104                 open_view(view, REQ_VIEW_DIFF, flags);
5105         else
5106                 return request;
5108         return REQ_NONE;
5111 static bool
5112 grep_refs(struct ref **refs, regex_t *regex)
5114         regmatch_t pmatch;
5115         size_t i = 0;
5117         if (!refs)
5118                 return FALSE;
5119         do {
5120                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5121                         return TRUE;
5122         } while (refs[i++]->next);
5124         return FALSE;
5127 static bool
5128 main_grep(struct view *view, struct line *line)
5130         struct commit *commit = line->data;
5131         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5132         char buf[DATE_COLS + 1];
5133         regmatch_t pmatch;
5135         for (state = S_TITLE; state < S_END; state++) {
5136                 char *text;
5138                 switch (state) {
5139                 case S_TITLE:   text = commit->title;   break;
5140                 case S_AUTHOR:
5141                         if (!opt_author)
5142                                 continue;
5143                         text = commit->author;
5144                         break;
5145                 case S_DATE:
5146                         if (!opt_date)
5147                                 continue;
5148                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5149                                 continue;
5150                         text = buf;
5151                         break;
5152                 case S_REFS:
5153                         if (!opt_show_refs)
5154                                 continue;
5155                         if (grep_refs(commit->refs, view->regex) == TRUE)
5156                                 return TRUE;
5157                         continue;
5158                 default:
5159                         return FALSE;
5160                 }
5162                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5163                         return TRUE;
5164         }
5166         return FALSE;
5169 static void
5170 main_select(struct view *view, struct line *line)
5172         struct commit *commit = line->data;
5174         string_copy_rev(view->ref, commit->id);
5175         string_copy_rev(ref_commit, view->ref);
5178 static struct view_ops main_ops = {
5179         "commit",
5180         NULL,
5181         main_read,
5182         main_draw,
5183         main_request,
5184         main_grep,
5185         main_select,
5186 };
5189 /*
5190  * Unicode / UTF-8 handling
5191  *
5192  * NOTE: Much of the following code for dealing with unicode is derived from
5193  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5194  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5195  */
5197 /* I've (over)annotated a lot of code snippets because I am not entirely
5198  * confident that the approach taken by this small UTF-8 interface is correct.
5199  * --jonas */
5201 static inline int
5202 unicode_width(unsigned long c)
5204         if (c >= 0x1100 &&
5205            (c <= 0x115f                         /* Hangul Jamo */
5206             || c == 0x2329
5207             || c == 0x232a
5208             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5209                                                 /* CJK ... Yi */
5210             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5211             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5212             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5213             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5214             || (c >= 0xffe0  && c <= 0xffe6)
5215             || (c >= 0x20000 && c <= 0x2fffd)
5216             || (c >= 0x30000 && c <= 0x3fffd)))
5217                 return 2;
5219         if (c == '\t')
5220                 return opt_tab_size;
5222         return 1;
5225 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5226  * Illegal bytes are set one. */
5227 static const unsigned char utf8_bytes[256] = {
5228         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,
5229         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,
5230         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,
5231         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,
5232         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,
5233         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,
5234         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,
5235         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,
5236 };
5238 /* Decode UTF-8 multi-byte representation into a unicode character. */
5239 static inline unsigned long
5240 utf8_to_unicode(const char *string, size_t length)
5242         unsigned long unicode;
5244         switch (length) {
5245         case 1:
5246                 unicode  =   string[0];
5247                 break;
5248         case 2:
5249                 unicode  =  (string[0] & 0x1f) << 6;
5250                 unicode +=  (string[1] & 0x3f);
5251                 break;
5252         case 3:
5253                 unicode  =  (string[0] & 0x0f) << 12;
5254                 unicode += ((string[1] & 0x3f) << 6);
5255                 unicode +=  (string[2] & 0x3f);
5256                 break;
5257         case 4:
5258                 unicode  =  (string[0] & 0x0f) << 18;
5259                 unicode += ((string[1] & 0x3f) << 12);
5260                 unicode += ((string[2] & 0x3f) << 6);
5261                 unicode +=  (string[3] & 0x3f);
5262                 break;
5263         case 5:
5264                 unicode  =  (string[0] & 0x0f) << 24;
5265                 unicode += ((string[1] & 0x3f) << 18);
5266                 unicode += ((string[2] & 0x3f) << 12);
5267                 unicode += ((string[3] & 0x3f) << 6);
5268                 unicode +=  (string[4] & 0x3f);
5269                 break;
5270         case 6:
5271                 unicode  =  (string[0] & 0x01) << 30;
5272                 unicode += ((string[1] & 0x3f) << 24);
5273                 unicode += ((string[2] & 0x3f) << 18);
5274                 unicode += ((string[3] & 0x3f) << 12);
5275                 unicode += ((string[4] & 0x3f) << 6);
5276                 unicode +=  (string[5] & 0x3f);
5277                 break;
5278         default:
5279                 die("Invalid unicode length");
5280         }
5282         /* Invalid characters could return the special 0xfffd value but NUL
5283          * should be just as good. */
5284         return unicode > 0xffff ? 0 : unicode;
5287 /* Calculates how much of string can be shown within the given maximum width
5288  * and sets trimmed parameter to non-zero value if all of string could not be
5289  * shown. If the reserve flag is TRUE, it will reserve at least one
5290  * trailing character, which can be useful when drawing a delimiter.
5291  *
5292  * Returns the number of bytes to output from string to satisfy max_width. */
5293 static size_t
5294 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5296         const char *start = string;
5297         const char *end = strchr(string, '\0');
5298         unsigned char last_bytes = 0;
5299         size_t last_ucwidth = 0;
5301         *width = 0;
5302         *trimmed = 0;
5304         while (string < end) {
5305                 int c = *(unsigned char *) string;
5306                 unsigned char bytes = utf8_bytes[c];
5307                 size_t ucwidth;
5308                 unsigned long unicode;
5310                 if (string + bytes > end)
5311                         break;
5313                 /* Change representation to figure out whether
5314                  * it is a single- or double-width character. */
5316                 unicode = utf8_to_unicode(string, bytes);
5317                 /* FIXME: Graceful handling of invalid unicode character. */
5318                 if (!unicode)
5319                         break;
5321                 ucwidth = unicode_width(unicode);
5322                 *width  += ucwidth;
5323                 if (*width > max_width) {
5324                         *trimmed = 1;
5325                         *width -= ucwidth;
5326                         if (reserve && *width == max_width) {
5327                                 string -= last_bytes;
5328                                 *width -= last_ucwidth;
5329                         }
5330                         break;
5331                 }
5333                 string  += bytes;
5334                 last_bytes = bytes;
5335                 last_ucwidth = ucwidth;
5336         }
5338         return string - start;
5342 /*
5343  * Status management
5344  */
5346 /* Whether or not the curses interface has been initialized. */
5347 static bool cursed = FALSE;
5349 /* The status window is used for polling keystrokes. */
5350 static WINDOW *status_win;
5352 static bool status_empty = TRUE;
5354 /* Update status and title window. */
5355 static void
5356 report(const char *msg, ...)
5358         struct view *view = display[current_view];
5360         if (input_mode)
5361                 return;
5363         if (!view) {
5364                 char buf[SIZEOF_STR];
5365                 va_list args;
5367                 va_start(args, msg);
5368                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5369                         buf[sizeof(buf) - 1] = 0;
5370                         buf[sizeof(buf) - 2] = '.';
5371                         buf[sizeof(buf) - 3] = '.';
5372                         buf[sizeof(buf) - 4] = '.';
5373                 }
5374                 va_end(args);
5375                 die("%s", buf);
5376         }
5378         if (!status_empty || *msg) {
5379                 va_list args;
5381                 va_start(args, msg);
5383                 wmove(status_win, 0, 0);
5384                 if (*msg) {
5385                         vwprintw(status_win, msg, args);
5386                         status_empty = FALSE;
5387                 } else {
5388                         status_empty = TRUE;
5389                 }
5390                 wclrtoeol(status_win);
5391                 wrefresh(status_win);
5393                 va_end(args);
5394         }
5396         update_view_title(view);
5397         update_display_cursor(view);
5400 /* Controls when nodelay should be in effect when polling user input. */
5401 static void
5402 set_nonblocking_input(bool loading)
5404         static unsigned int loading_views;
5406         if ((loading == FALSE && loading_views-- == 1) ||
5407             (loading == TRUE  && loading_views++ == 0))
5408                 nodelay(status_win, loading);
5411 static void
5412 init_display(void)
5414         int x, y;
5416         /* Initialize the curses library */
5417         if (isatty(STDIN_FILENO)) {
5418                 cursed = !!initscr();
5419         } else {
5420                 /* Leave stdin and stdout alone when acting as a pager. */
5421                 FILE *io = fopen("/dev/tty", "r+");
5423                 if (!io)
5424                         die("Failed to open /dev/tty");
5425                 cursed = !!newterm(NULL, io, io);
5426         }
5428         if (!cursed)
5429                 die("Failed to initialize curses");
5431         nonl();         /* Tell curses not to do NL->CR/NL on output */
5432         cbreak();       /* Take input chars one at a time, no wait for \n */
5433         noecho();       /* Don't echo input */
5434         leaveok(stdscr, TRUE);
5436         if (has_colors())
5437                 init_colors();
5439         getmaxyx(stdscr, y, x);
5440         status_win = newwin(1, 0, y - 1, 0);
5441         if (!status_win)
5442                 die("Failed to create status window");
5444         /* Enable keyboard mapping */
5445         keypad(status_win, TRUE);
5446         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5448         TABSIZE = opt_tab_size;
5449         if (opt_line_graphics) {
5450                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5451         }
5454 static char *
5455 read_prompt(const char *prompt)
5457         enum { READING, STOP, CANCEL } status = READING;
5458         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5459         int pos = 0;
5461         while (status == READING) {
5462                 struct view *view;
5463                 int i, key;
5465                 input_mode = TRUE;
5467                 foreach_view (view, i)
5468                         update_view(view);
5470                 input_mode = FALSE;
5472                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5473                 wclrtoeol(status_win);
5475                 /* Refresh, accept single keystroke of input */
5476                 key = wgetch(status_win);
5477                 switch (key) {
5478                 case KEY_RETURN:
5479                 case KEY_ENTER:
5480                 case '\n':
5481                         status = pos ? STOP : CANCEL;
5482                         break;
5484                 case KEY_BACKSPACE:
5485                         if (pos > 0)
5486                                 pos--;
5487                         else
5488                                 status = CANCEL;
5489                         break;
5491                 case KEY_ESC:
5492                         status = CANCEL;
5493                         break;
5495                 case ERR:
5496                         break;
5498                 default:
5499                         if (pos >= sizeof(buf)) {
5500                                 report("Input string too long");
5501                                 return NULL;
5502                         }
5504                         if (isprint(key))
5505                                 buf[pos++] = (char) key;
5506                 }
5507         }
5509         /* Clear the status window */
5510         status_empty = FALSE;
5511         report("");
5513         if (status == CANCEL)
5514                 return NULL;
5516         buf[pos++] = 0;
5518         return buf;
5521 /*
5522  * Repository references
5523  */
5525 static struct ref *refs = NULL;
5526 static size_t refs_alloc = 0;
5527 static size_t refs_size = 0;
5529 /* Id <-> ref store */
5530 static struct ref ***id_refs = NULL;
5531 static size_t id_refs_alloc = 0;
5532 static size_t id_refs_size = 0;
5534 static struct ref **
5535 get_refs(char *id)
5537         struct ref ***tmp_id_refs;
5538         struct ref **ref_list = NULL;
5539         size_t ref_list_alloc = 0;
5540         size_t ref_list_size = 0;
5541         size_t i;
5543         for (i = 0; i < id_refs_size; i++)
5544                 if (!strcmp(id, id_refs[i][0]->id))
5545                         return id_refs[i];
5547         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5548                                     sizeof(*id_refs));
5549         if (!tmp_id_refs)
5550                 return NULL;
5552         id_refs = tmp_id_refs;
5554         for (i = 0; i < refs_size; i++) {
5555                 struct ref **tmp;
5557                 if (strcmp(id, refs[i].id))
5558                         continue;
5560                 tmp = realloc_items(ref_list, &ref_list_alloc,
5561                                     ref_list_size + 1, sizeof(*ref_list));
5562                 if (!tmp) {
5563                         if (ref_list)
5564                                 free(ref_list);
5565                         return NULL;
5566                 }
5568                 ref_list = tmp;
5569                 if (ref_list_size > 0)
5570                         ref_list[ref_list_size - 1]->next = 1;
5571                 ref_list[ref_list_size] = &refs[i];
5573                 /* XXX: The properties of the commit chains ensures that we can
5574                  * safely modify the shared ref. The repo references will
5575                  * always be similar for the same id. */
5576                 ref_list[ref_list_size]->next = 0;
5577                 ref_list_size++;
5578         }
5580         if (ref_list)
5581                 id_refs[id_refs_size++] = ref_list;
5583         return ref_list;
5586 static int
5587 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5589         struct ref *ref;
5590         bool tag = FALSE;
5591         bool ltag = FALSE;
5592         bool remote = FALSE;
5593         bool tracked = FALSE;
5594         bool check_replace = FALSE;
5595         bool head = FALSE;
5597         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5598                 if (!strcmp(name + namelen - 3, "^{}")) {
5599                         namelen -= 3;
5600                         name[namelen] = 0;
5601                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5602                                 check_replace = TRUE;
5603                 } else {
5604                         ltag = TRUE;
5605                 }
5607                 tag = TRUE;
5608                 namelen -= STRING_SIZE("refs/tags/");
5609                 name    += STRING_SIZE("refs/tags/");
5611         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5612                 remote = TRUE;
5613                 namelen -= STRING_SIZE("refs/remotes/");
5614                 name    += STRING_SIZE("refs/remotes/");
5615                 tracked  = !strcmp(opt_remote, name);
5617         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5618                 namelen -= STRING_SIZE("refs/heads/");
5619                 name    += STRING_SIZE("refs/heads/");
5620                 head     = !strncmp(opt_head, name, namelen);
5622         } else if (!strcmp(name, "HEAD")) {
5623                 opt_no_head = FALSE;
5624                 return OK;
5625         }
5627         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5628                 /* it's an annotated tag, replace the previous sha1 with the
5629                  * resolved commit id; relies on the fact git-ls-remote lists
5630                  * the commit id of an annotated tag right beofre the commit id
5631                  * it points to. */
5632                 refs[refs_size - 1].ltag = ltag;
5633                 string_copy_rev(refs[refs_size - 1].id, id);
5635                 return OK;
5636         }
5637         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5638         if (!refs)
5639                 return ERR;
5641         ref = &refs[refs_size++];
5642         ref->name = malloc(namelen + 1);
5643         if (!ref->name)
5644                 return ERR;
5646         strncpy(ref->name, name, namelen);
5647         ref->name[namelen] = 0;
5648         ref->head = head;
5649         ref->tag = tag;
5650         ref->ltag = ltag;
5651         ref->remote = remote;
5652         ref->tracked = tracked;
5653         string_copy_rev(ref->id, id);
5655         return OK;
5658 static int
5659 load_refs(void)
5661         const char *cmd_env = getenv("TIG_LS_REMOTE");
5662         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5664         return read_properties(popen(cmd, "r"), "\t", read_ref);
5667 static int
5668 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5670         if (!strcmp(name, "i18n.commitencoding"))
5671                 string_ncopy(opt_encoding, value, valuelen);
5673         if (!strcmp(name, "core.editor"))
5674                 string_ncopy(opt_editor, value, valuelen);
5676         /* branch.<head>.remote */
5677         if (*opt_head &&
5678             !strncmp(name, "branch.", 7) &&
5679             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5680             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5681                 string_ncopy(opt_remote, value, valuelen);
5683         if (*opt_head && *opt_remote &&
5684             !strncmp(name, "branch.", 7) &&
5685             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5686             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5687                 size_t from = strlen(opt_remote);
5689                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5690                         value += STRING_SIZE("refs/heads/");
5691                         valuelen -= STRING_SIZE("refs/heads/");
5692                 }
5694                 if (!string_format_from(opt_remote, &from, "/%s", value))
5695                         opt_remote[0] = 0;
5696         }
5698         return OK;
5701 static int
5702 load_git_config(void)
5704         return read_properties(popen(GIT_CONFIG " --list", "r"),
5705                                "=", read_repo_config_option);
5708 static int
5709 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5711         if (!opt_git_dir[0]) {
5712                 string_ncopy(opt_git_dir, name, namelen);
5714         } else if (opt_is_inside_work_tree == -1) {
5715                 /* This can be 3 different values depending on the
5716                  * version of git being used. If git-rev-parse does not
5717                  * understand --is-inside-work-tree it will simply echo
5718                  * the option else either "true" or "false" is printed.
5719                  * Default to true for the unknown case. */
5720                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5722         } else if (opt_cdup[0] == ' ') {
5723                 string_ncopy(opt_cdup, name, namelen);
5724         } else {
5725                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5726                         namelen -= STRING_SIZE("refs/heads/");
5727                         name    += STRING_SIZE("refs/heads/");
5728                         string_ncopy(opt_head, name, namelen);
5729                 }
5730         }
5732         return OK;
5735 static int
5736 load_repo_info(void)
5738         int result;
5739         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5740                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5742         /* XXX: The line outputted by "--show-cdup" can be empty so
5743          * initialize it to something invalid to make it possible to
5744          * detect whether it has been set or not. */
5745         opt_cdup[0] = ' ';
5747         result = read_properties(pipe, "=", read_repo_info);
5748         if (opt_cdup[0] == ' ')
5749                 opt_cdup[0] = 0;
5751         return result;
5754 static int
5755 read_properties(FILE *pipe, const char *separators,
5756                 int (*read_property)(char *, size_t, char *, size_t))
5758         char buffer[BUFSIZ];
5759         char *name;
5760         int state = OK;
5762         if (!pipe)
5763                 return ERR;
5765         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5766                 char *value;
5767                 size_t namelen;
5768                 size_t valuelen;
5770                 name = chomp_string(name);
5771                 namelen = strcspn(name, separators);
5773                 if (name[namelen]) {
5774                         name[namelen] = 0;
5775                         value = chomp_string(name + namelen + 1);
5776                         valuelen = strlen(value);
5778                 } else {
5779                         value = "";
5780                         valuelen = 0;
5781                 }
5783                 state = read_property(name, namelen, value, valuelen);
5784         }
5786         if (state != ERR && ferror(pipe))
5787                 state = ERR;
5789         pclose(pipe);
5791         return state;
5795 /*
5796  * Main
5797  */
5799 static void __NORETURN
5800 quit(int sig)
5802         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5803         if (cursed)
5804                 endwin();
5805         exit(0);
5808 static void __NORETURN
5809 die(const char *err, ...)
5811         va_list args;
5813         endwin();
5815         va_start(args, err);
5816         fputs("tig: ", stderr);
5817         vfprintf(stderr, err, args);
5818         fputs("\n", stderr);
5819         va_end(args);
5821         exit(1);
5824 static void
5825 warn(const char *msg, ...)
5827         va_list args;
5829         va_start(args, msg);
5830         fputs("tig warning: ", stderr);
5831         vfprintf(stderr, msg, args);
5832         fputs("\n", stderr);
5833         va_end(args);
5836 int
5837 main(int argc, char *argv[])
5839         struct view *view;
5840         enum request request;
5841         size_t i;
5843         signal(SIGINT, quit);
5845         if (setlocale(LC_ALL, "")) {
5846                 char *codeset = nl_langinfo(CODESET);
5848                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5849         }
5851         if (load_repo_info() == ERR)
5852                 die("Failed to load repo info.");
5854         if (load_options() == ERR)
5855                 die("Failed to load user config.");
5857         if (load_git_config() == ERR)
5858                 die("Failed to load repo config.");
5860         if (!parse_options(argc, argv))
5861                 return 0;
5863         /* Require a git repository unless when running in pager mode. */
5864         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5865                 die("Not a git repository");
5867         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5868                 opt_utf8 = FALSE;
5870         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5871                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5872                 if (opt_iconv == ICONV_NONE)
5873                         die("Failed to initialize character set conversion");
5874         }
5876         if (*opt_git_dir && load_refs() == ERR)
5877                 die("Failed to load refs.");
5879         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5880                 view->cmd_env = getenv(view->cmd_env);
5882         request = opt_request;
5884         init_display();
5886         while (view_driver(display[current_view], request)) {
5887                 int key;
5888                 int i;
5890                 foreach_view (view, i)
5891                         update_view(view);
5893                 /* Refresh, accept single keystroke of input */
5894                 key = wgetch(status_win);
5896                 /* wgetch() with nodelay() enabled returns ERR when there's no
5897                  * input. */
5898                 if (key == ERR) {
5899                         request = REQ_NONE;
5900                         continue;
5901                 }
5903                 request = get_keybinding(display[current_view]->keymap, key);
5905                 /* Some low-level request handling. This keeps access to
5906                  * status_win restricted. */
5907                 switch (request) {
5908                 case REQ_PROMPT:
5909                 {
5910                         char *cmd = read_prompt(":");
5912                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5913                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5914                                         opt_request = REQ_VIEW_DIFF;
5915                                 } else {
5916                                         opt_request = REQ_VIEW_PAGER;
5917                                 }
5918                                 break;
5919                         }
5921                         request = REQ_NONE;
5922                         break;
5923                 }
5924                 case REQ_SEARCH:
5925                 case REQ_SEARCH_BACK:
5926                 {
5927                         const char *prompt = request == REQ_SEARCH
5928                                            ? "/" : "?";
5929                         char *search = read_prompt(prompt);
5931                         if (search)
5932                                 string_ncopy(opt_search, search, strlen(search));
5933                         else
5934                                 request = REQ_NONE;
5935                         break;
5936                 }
5937                 case REQ_SCREEN_RESIZE:
5938                 {
5939                         int height, width;
5941                         getmaxyx(stdscr, height, width);
5943                         /* Resize the status view and let the view driver take
5944                          * care of resizing the displayed views. */
5945                         wresize(status_win, 1, width);
5946                         mvwin(status_win, height - 1, 0);
5947                         wrefresh(status_win);
5948                         break;
5949                 }
5950                 default:
5951                         break;
5952                 }
5953         }
5955         quit(0);
5957         return 0;