Code

14fbf35a9f834eef1d392848a314944a37a3516c
[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 (view->pipe)
2185                 end_update(view, TRUE);
2187         if (opt_cmd[0]) {
2188                 string_copy(view->cmd, opt_cmd);
2189                 opt_cmd[0] = 0;
2190                 /* When running random commands, initially show the
2191                  * command in the title. However, it maybe later be
2192                  * overwritten if a commit line is selected. */
2193                 if (view == VIEW(REQ_VIEW_PAGER))
2194                         string_copy(view->ref, view->cmd);
2195                 else
2196                         view->ref[0] = 0;
2198         } else if (view == VIEW(REQ_VIEW_TREE)) {
2199                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2200                 char path[SIZEOF_STR];
2202                 if (strcmp(view->vid, view->id))
2203                         opt_path[0] = path[0] = 0;
2204                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2205                         return FALSE;
2207                 if (!string_format(view->cmd, format, view->id, path))
2208                         return FALSE;
2210         } else {
2211                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2212                 const char *id = view->id;
2214                 if (!string_format(view->cmd, format, id, id, id, id, id))
2215                         return FALSE;
2217                 /* Put the current ref_* value to the view title ref
2218                  * member. This is needed by the blob view. Most other
2219                  * views sets it automatically after loading because the
2220                  * first line is a commit line. */
2221                 string_copy_rev(view->ref, view->id);
2222         }
2224         /* Special case for the pager view. */
2225         if (opt_pipe) {
2226                 view->pipe = opt_pipe;
2227                 opt_pipe = NULL;
2228         } else {
2229                 view->pipe = popen(view->cmd, "r");
2230         }
2232         if (!view->pipe)
2233                 return FALSE;
2235         set_nonblocking_input(TRUE);
2237         view->offset = 0;
2238         view->lines  = 0;
2239         view->lineno = 0;
2240         string_copy_rev(view->vid, view->id);
2242         if (view->line) {
2243                 int i;
2245                 for (i = 0; i < view->lines; i++)
2246                         if (view->line[i].data)
2247                                 free(view->line[i].data);
2249                 free(view->line);
2250                 view->line = NULL;
2251         }
2253         view->start_time = time(NULL);
2255         return TRUE;
2258 #define ITEM_CHUNK_SIZE 256
2259 static void *
2260 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2262         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2263         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2265         if (mem == NULL || num_chunks != num_chunks_new) {
2266                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2267                 mem = realloc(mem, *size * item_size);
2268         }
2270         return mem;
2273 static struct line *
2274 realloc_lines(struct view *view, size_t line_size)
2276         size_t alloc = view->line_alloc;
2277         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2278                                          sizeof(*view->line));
2280         if (!tmp)
2281                 return NULL;
2283         view->line = tmp;
2284         view->line_alloc = alloc;
2285         view->line_size = line_size;
2286         return view->line;
2289 static bool
2290 update_view(struct view *view)
2292         char in_buffer[BUFSIZ];
2293         char out_buffer[BUFSIZ * 2];
2294         char *line;
2295         /* The number of lines to read. If too low it will cause too much
2296          * redrawing (and possible flickering), if too high responsiveness
2297          * will suffer. */
2298         unsigned long lines = view->height;
2299         int redraw_from = -1;
2301         if (!view->pipe)
2302                 return TRUE;
2304         /* Only redraw if lines are visible. */
2305         if (view->offset + view->height >= view->lines)
2306                 redraw_from = view->lines - view->offset;
2308         /* FIXME: This is probably not perfect for backgrounded views. */
2309         if (!realloc_lines(view, view->lines + lines))
2310                 goto alloc_error;
2312         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2313                 size_t linelen = strlen(line);
2315                 if (linelen)
2316                         line[linelen - 1] = 0;
2318                 if (opt_iconv != ICONV_NONE) {
2319                         ICONV_CONST char *inbuf = line;
2320                         size_t inlen = linelen;
2322                         char *outbuf = out_buffer;
2323                         size_t outlen = sizeof(out_buffer);
2325                         size_t ret;
2327                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2328                         if (ret != (size_t) -1) {
2329                                 line = out_buffer;
2330                                 linelen = strlen(out_buffer);
2331                         }
2332                 }
2334                 if (!view->ops->read(view, line))
2335                         goto alloc_error;
2337                 if (lines-- == 1)
2338                         break;
2339         }
2341         {
2342                 int digits;
2344                 lines = view->lines;
2345                 for (digits = 0; lines; digits++)
2346                         lines /= 10;
2348                 /* Keep the displayed view in sync with line number scaling. */
2349                 if (digits != view->digits) {
2350                         view->digits = digits;
2351                         redraw_from = 0;
2352                 }
2353         }
2355         if (!view_is_displayed(view))
2356                 goto check_pipe;
2358         if (view == VIEW(REQ_VIEW_TREE)) {
2359                 /* Clear the view and redraw everything since the tree sorting
2360                  * might have rearranged things. */
2361                 redraw_view(view);
2363         } else if (redraw_from >= 0) {
2364                 /* If this is an incremental update, redraw the previous line
2365                  * since for commits some members could have changed when
2366                  * loading the main view. */
2367                 if (redraw_from > 0)
2368                         redraw_from--;
2370                 /* Since revision graph visualization requires knowledge
2371                  * about the parent commit, it causes a further one-off
2372                  * needed to be redrawn for incremental updates. */
2373                 if (redraw_from > 0 && opt_rev_graph)
2374                         redraw_from--;
2376                 /* Incrementally draw avoids flickering. */
2377                 redraw_view_from(view, redraw_from);
2378         }
2380         if (view == VIEW(REQ_VIEW_BLAME))
2381                 redraw_view_dirty(view);
2383         /* Update the title _after_ the redraw so that if the redraw picks up a
2384          * commit reference in view->ref it'll be available here. */
2385         update_view_title(view);
2387 check_pipe:
2388         if (ferror(view->pipe)) {
2389                 report("Failed to read: %s", strerror(errno));
2390                 end_update(view, TRUE);
2392         } else if (feof(view->pipe)) {
2393                 report("");
2394                 end_update(view, FALSE);
2395         }
2397         return TRUE;
2399 alloc_error:
2400         report("Allocation failure");
2401         end_update(view, TRUE);
2402         return FALSE;
2405 static struct line *
2406 add_line_data(struct view *view, void *data, enum line_type type)
2408         struct line *line = &view->line[view->lines++];
2410         memset(line, 0, sizeof(*line));
2411         line->type = type;
2412         line->data = data;
2414         return line;
2417 static struct line *
2418 add_line_text(struct view *view, char *data, enum line_type type)
2420         if (data)
2421                 data = strdup(data);
2423         return data ? add_line_data(view, data, type) : NULL;
2427 /*
2428  * View opening
2429  */
2431 enum open_flags {
2432         OPEN_DEFAULT = 0,       /* Use default view switching. */
2433         OPEN_SPLIT = 1,         /* Split current view. */
2434         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2435         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2436         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2437 };
2439 static void
2440 open_view(struct view *prev, enum request request, enum open_flags flags)
2442         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2443         bool split = !!(flags & OPEN_SPLIT);
2444         bool reload = !!(flags & OPEN_RELOAD);
2445         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2446         struct view *view = VIEW(request);
2447         int nviews = displayed_views();
2448         struct view *base_view = display[0];
2450         if (view == prev && nviews == 1 && !reload) {
2451                 report("Already in %s view", view->name);
2452                 return;
2453         }
2455         if (view->git_dir && !opt_git_dir[0]) {
2456                 report("The %s view is disabled in pager view", view->name);
2457                 return;
2458         }
2460         if (split) {
2461                 display[1] = view;
2462                 if (!backgrounded)
2463                         current_view = 1;
2464         } else if (!nomaximize) {
2465                 /* Maximize the current view. */
2466                 memset(display, 0, sizeof(display));
2467                 current_view = 0;
2468                 display[current_view] = view;
2469         }
2471         /* Resize the view when switching between split- and full-screen,
2472          * or when switching between two different full-screen views. */
2473         if (nviews != displayed_views() ||
2474             (nviews == 1 && base_view != display[0]))
2475                 resize_display();
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                 if (!line)
3587                         return FALSE;
3589                 blame->commit = NULL;
3590                 strncpy(blame->text, line, linelen);
3591                 blame->text[linelen] = 0;
3592                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3593         }
3596 static bool
3597 match_blame_header(const char *name, char **line)
3599         size_t namelen = strlen(name);
3600         bool matched = !strncmp(name, *line, namelen);
3602         if (matched)
3603                 *line += namelen;
3605         return matched;
3608 static bool
3609 blame_read(struct view *view, char *line)
3611         static struct blame_commit *commit = NULL;
3612         static int blamed = 0;
3613         static time_t author_time;
3615         if (*view->cmd)
3616                 return blame_read_file(view, line);
3618         if (!line) {
3619                 /* Reset all! */
3620                 commit = NULL;
3621                 blamed = 0;
3622                 string_format(view->ref, "%s", view->vid);
3623                 if (view_is_displayed(view)) {
3624                         update_view_title(view);
3625                         redraw_view_from(view, 0);
3626                 }
3627                 return TRUE;
3628         }
3630         if (!commit) {
3631                 commit = parse_blame_commit(view, line, &blamed);
3632                 string_format(view->ref, "%s %2d%%", view->vid,
3633                               blamed * 100 / view->lines);
3635         } else if (match_blame_header("author ", &line)) {
3636                 string_ncopy(commit->author, line, strlen(line));
3638         } else if (match_blame_header("author-time ", &line)) {
3639                 author_time = (time_t) atol(line);
3641         } else if (match_blame_header("author-tz ", &line)) {
3642                 long tz;
3644                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3645                 tz += ('0' - line[2]) * 60 * 60;
3646                 tz += ('0' - line[3]) * 60;
3647                 tz += ('0' - line[4]) * 60;
3649                 if (line[0] == '-')
3650                         tz = -tz;
3652                 author_time -= tz;
3653                 gmtime_r(&author_time, &commit->time);
3655         } else if (match_blame_header("summary ", &line)) {
3656                 string_ncopy(commit->title, line, strlen(line));
3658         } else if (match_blame_header("filename ", &line)) {
3659                 string_ncopy(commit->filename, line, strlen(line));
3660                 commit = NULL;
3661         }
3663         return TRUE;
3666 static bool
3667 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3669         struct blame *blame = line->data;
3670         struct tm *time = NULL;
3671         char *id = NULL, *author = NULL;
3673         if (blame->commit && *blame->commit->filename) {
3674                 id = blame->commit->id;
3675                 author = blame->commit->author;
3676                 time = &blame->commit->time;
3677         }
3679         if (opt_date && draw_date(view, time))
3680                 return TRUE;
3682         if (opt_author &&
3683             draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3684                 return TRUE;
3686         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3687                 return TRUE;
3689         if (draw_lineno(view, lineno))
3690                 return TRUE;
3692         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3693         return TRUE;
3696 static enum request
3697 blame_request(struct view *view, enum request request, struct line *line)
3699         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3700         struct blame *blame = line->data;
3702         switch (request) {
3703         case REQ_ENTER:
3704                 if (!blame->commit) {
3705                         report("No commit loaded yet");
3706                         break;
3707                 }
3709                 if (!strcmp(blame->commit->id, NULL_ID)) {
3710                         char path[SIZEOF_STR];
3712                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3713                                 break;
3714                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3715                 }
3717                 open_view(view, REQ_VIEW_DIFF, flags);
3718                 break;
3720         default:
3721                 return request;
3722         }
3724         return REQ_NONE;
3727 static bool
3728 blame_grep(struct view *view, struct line *line)
3730         struct blame *blame = line->data;
3731         struct blame_commit *commit = blame->commit;
3732         regmatch_t pmatch;
3734 #define MATCH(text, on)                                                 \
3735         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3737         if (commit) {
3738                 char buf[DATE_COLS + 1];
3740                 if (MATCH(commit->title, 1) ||
3741                     MATCH(commit->author, opt_author) ||
3742                     MATCH(commit->id, opt_date))
3743                         return TRUE;
3745                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3746                     MATCH(buf, 1))
3747                         return TRUE;
3748         }
3750         return MATCH(blame->text, 1);
3752 #undef MATCH
3755 static void
3756 blame_select(struct view *view, struct line *line)
3758         struct blame *blame = line->data;
3759         struct blame_commit *commit = blame->commit;
3761         if (!commit)
3762                 return;
3764         if (!strcmp(commit->id, NULL_ID))
3765                 string_ncopy(ref_commit, "HEAD", 4);
3766         else
3767                 string_copy_rev(ref_commit, commit->id);
3770 static struct view_ops blame_ops = {
3771         "line",
3772         blame_open,
3773         blame_read,
3774         blame_draw,
3775         blame_request,
3776         blame_grep,
3777         blame_select,
3778 };
3780 /*
3781  * Status backend
3782  */
3784 struct status {
3785         char status;
3786         struct {
3787                 mode_t mode;
3788                 char rev[SIZEOF_REV];
3789                 char name[SIZEOF_STR];
3790         } old;
3791         struct {
3792                 mode_t mode;
3793                 char rev[SIZEOF_REV];
3794                 char name[SIZEOF_STR];
3795         } new;
3796 };
3798 static char status_onbranch[SIZEOF_STR];
3799 static struct status stage_status;
3800 static enum line_type stage_line_type;
3801 static size_t stage_chunks;
3802 static int *stage_chunk;
3804 /* Get fields from the diff line:
3805  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3806  */
3807 static inline bool
3808 status_get_diff(struct status *file, char *buf, size_t bufsize)
3810         char *old_mode = buf +  1;
3811         char *new_mode = buf +  8;
3812         char *old_rev  = buf + 15;
3813         char *new_rev  = buf + 56;
3814         char *status   = buf + 97;
3816         if (bufsize < 99 ||
3817             old_mode[-1] != ':' ||
3818             new_mode[-1] != ' ' ||
3819             old_rev[-1]  != ' ' ||
3820             new_rev[-1]  != ' ' ||
3821             status[-1]   != ' ')
3822                 return FALSE;
3824         file->status = *status;
3826         string_copy_rev(file->old.rev, old_rev);
3827         string_copy_rev(file->new.rev, new_rev);
3829         file->old.mode = strtoul(old_mode, NULL, 8);
3830         file->new.mode = strtoul(new_mode, NULL, 8);
3832         file->old.name[0] = file->new.name[0] = 0;
3834         return TRUE;
3837 static bool
3838 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3840         struct status *file = NULL;
3841         struct status *unmerged = NULL;
3842         char buf[SIZEOF_STR * 4];
3843         size_t bufsize = 0;
3844         FILE *pipe;
3846         pipe = popen(cmd, "r");
3847         if (!pipe)
3848                 return FALSE;
3850         add_line_data(view, NULL, type);
3852         while (!feof(pipe) && !ferror(pipe)) {
3853                 char *sep;
3854                 size_t readsize;
3856                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3857                 if (!readsize)
3858                         break;
3859                 bufsize += readsize;
3861                 /* Process while we have NUL chars. */
3862                 while ((sep = memchr(buf, 0, bufsize))) {
3863                         size_t sepsize = sep - buf + 1;
3865                         if (!file) {
3866                                 if (!realloc_lines(view, view->line_size + 1))
3867                                         goto error_out;
3869                                 file = calloc(1, sizeof(*file));
3870                                 if (!file)
3871                                         goto error_out;
3873                                 add_line_data(view, file, type);
3874                         }
3876                         /* Parse diff info part. */
3877                         if (status) {
3878                                 file->status = status;
3879                                 if (status == 'A')
3880                                         string_copy(file->old.rev, NULL_ID);
3882                         } else if (!file->status) {
3883                                 if (!status_get_diff(file, buf, sepsize))
3884                                         goto error_out;
3886                                 bufsize -= sepsize;
3887                                 memmove(buf, sep + 1, bufsize);
3889                                 sep = memchr(buf, 0, bufsize);
3890                                 if (!sep)
3891                                         break;
3892                                 sepsize = sep - buf + 1;
3894                                 /* Collapse all 'M'odified entries that
3895                                  * follow a associated 'U'nmerged entry.
3896                                  */
3897                                 if (file->status == 'U') {
3898                                         unmerged = file;
3900                                 } else if (unmerged) {
3901                                         int collapse = !strcmp(buf, unmerged->new.name);
3903                                         unmerged = NULL;
3904                                         if (collapse) {
3905                                                 free(file);
3906                                                 view->lines--;
3907                                                 continue;
3908                                         }
3909                                 }
3910                         }
3912                         /* Grab the old name for rename/copy. */
3913                         if (!*file->old.name &&
3914                             (file->status == 'R' || file->status == 'C')) {
3915                                 sepsize = sep - buf + 1;
3916                                 string_ncopy(file->old.name, buf, sepsize);
3917                                 bufsize -= sepsize;
3918                                 memmove(buf, sep + 1, bufsize);
3920                                 sep = memchr(buf, 0, bufsize);
3921                                 if (!sep)
3922                                         break;
3923                                 sepsize = sep - buf + 1;
3924                         }
3926                         /* git-ls-files just delivers a NUL separated
3927                          * list of file names similar to the second half
3928                          * of the git-diff-* output. */
3929                         string_ncopy(file->new.name, buf, sepsize);
3930                         if (!*file->old.name)
3931                                 string_copy(file->old.name, file->new.name);
3932                         bufsize -= sepsize;
3933                         memmove(buf, sep + 1, bufsize);
3934                         file = NULL;
3935                 }
3936         }
3938         if (ferror(pipe)) {
3939 error_out:
3940                 pclose(pipe);
3941                 return FALSE;
3942         }
3944         if (!view->line[view->lines - 1].data)
3945                 add_line_data(view, NULL, LINE_STAT_NONE);
3947         pclose(pipe);
3948         return TRUE;
3951 /* Don't show unmerged entries in the staged section. */
3952 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3953 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3954 #define STATUS_LIST_OTHER_CMD \
3955         "git ls-files -z --others --exclude-per-directory=.gitignore"
3956 #define STATUS_LIST_NO_HEAD_CMD \
3957         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3959 #define STATUS_DIFF_INDEX_SHOW_CMD \
3960         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3962 #define STATUS_DIFF_FILES_SHOW_CMD \
3963         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3965 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3966         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3968 /* First parse staged info using git-diff-index(1), then parse unstaged
3969  * info using git-diff-files(1), and finally untracked files using
3970  * git-ls-files(1). */
3971 static bool
3972 status_open(struct view *view)
3974         struct stat statbuf;
3975         char exclude[SIZEOF_STR];
3976         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3977         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3978         unsigned long prev_lineno = view->lineno;
3979         char indexstatus = 0;
3980         size_t i;
3982         for (i = 0; i < view->lines; i++)
3983                 free(view->line[i].data);
3984         free(view->line);
3985         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3986         view->line = NULL;
3988         if (!realloc_lines(view, view->line_size + 7))
3989                 return FALSE;
3991         add_line_data(view, NULL, LINE_STAT_HEAD);
3992         if (opt_no_head)
3993                 string_copy(status_onbranch, "Initial commit");
3994         else if (!*opt_head)
3995                 string_copy(status_onbranch, "Not currently on any branch");
3996         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3997                 return FALSE;
3999         if (opt_no_head) {
4000                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
4001                 indexstatus = 'A';
4002         }
4004         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4005                 return FALSE;
4007         if (stat(exclude, &statbuf) >= 0) {
4008                 size_t cmdsize = strlen(othercmd);
4010                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4011                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4012                         return FALSE;
4014                 cmdsize = strlen(indexcmd);
4015                 if (opt_no_head &&
4016                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4017                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4018                         return FALSE;
4019         }
4021         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4023         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4024             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4025             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4026                 return FALSE;
4028         /* If all went well restore the previous line number to stay in
4029          * the context or select a line with something that can be
4030          * updated. */
4031         if (prev_lineno >= view->lines)
4032                 prev_lineno = view->lines - 1;
4033         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4034                 prev_lineno++;
4035         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4036                 prev_lineno--;
4038         /* If the above fails, always skip the "On branch" line. */
4039         if (prev_lineno < view->lines)
4040                 view->lineno = prev_lineno;
4041         else
4042                 view->lineno = 1;
4044         if (view->lineno < view->offset)
4045                 view->offset = view->lineno;
4046         else if (view->offset + view->height <= view->lineno)
4047                 view->offset = view->lineno - view->height + 1;
4049         return TRUE;
4052 static bool
4053 status_draw(struct view *view, struct line *line, unsigned int lineno)
4055         struct status *status = line->data;
4056         enum line_type type;
4057         char *text;
4059         if (!status) {
4060                 switch (line->type) {
4061                 case LINE_STAT_STAGED:
4062                         type = LINE_STAT_SECTION;
4063                         text = "Changes to be committed:";
4064                         break;
4066                 case LINE_STAT_UNSTAGED:
4067                         type = LINE_STAT_SECTION;
4068                         text = "Changed but not updated:";
4069                         break;
4071                 case LINE_STAT_UNTRACKED:
4072                         type = LINE_STAT_SECTION;
4073                         text = "Untracked files:";
4074                         break;
4076                 case LINE_STAT_NONE:
4077                         type = LINE_DEFAULT;
4078                         text = "    (no files)";
4079                         break;
4081                 case LINE_STAT_HEAD:
4082                         type = LINE_STAT_HEAD;
4083                         text = status_onbranch;
4084                         break;
4086                 default:
4087                         return FALSE;
4088                 }
4089         } else {
4090                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4092                 buf[0] = status->status;
4093                 if (draw_text(view, line->type, buf, TRUE))
4094                         return TRUE;
4095                 type = LINE_DEFAULT;
4096                 text = status->new.name;
4097         }
4099         draw_text(view, type, text, TRUE);
4100         return TRUE;
4103 static enum request
4104 status_enter(struct view *view, struct line *line)
4106         struct status *status = line->data;
4107         char oldpath[SIZEOF_STR] = "";
4108         char newpath[SIZEOF_STR] = "";
4109         char *info;
4110         size_t cmdsize = 0;
4111         enum open_flags split;
4113         if (line->type == LINE_STAT_NONE ||
4114             (!status && line[1].type == LINE_STAT_NONE)) {
4115                 report("No file to diff");
4116                 return REQ_NONE;
4117         }
4119         if (status) {
4120                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4121                         return REQ_QUIT;
4122                 /* Diffs for unmerged entries are empty when pasing the
4123                  * new path, so leave it empty. */
4124                 if (status->status != 'U' &&
4125                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4126                         return REQ_QUIT;
4127         }
4129         if (opt_cdup[0] &&
4130             line->type != LINE_STAT_UNTRACKED &&
4131             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4132                 return REQ_QUIT;
4134         switch (line->type) {
4135         case LINE_STAT_STAGED:
4136                 if (opt_no_head) {
4137                         if (!string_format_from(opt_cmd, &cmdsize,
4138                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4139                                                 newpath))
4140                                 return REQ_QUIT;
4141                 } else {
4142                         if (!string_format_from(opt_cmd, &cmdsize,
4143                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4144                                                 oldpath, newpath))
4145                                 return REQ_QUIT;
4146                 }
4148                 if (status)
4149                         info = "Staged changes to %s";
4150                 else
4151                         info = "Staged changes";
4152                 break;
4154         case LINE_STAT_UNSTAGED:
4155                 if (!string_format_from(opt_cmd, &cmdsize,
4156                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4157                         return REQ_QUIT;
4158                 if (status)
4159                         info = "Unstaged changes to %s";
4160                 else
4161                         info = "Unstaged changes";
4162                 break;
4164         case LINE_STAT_UNTRACKED:
4165                 if (opt_pipe)
4166                         return REQ_QUIT;
4168                 if (!status) {
4169                         report("No file to show");
4170                         return REQ_NONE;
4171                 }
4173                 opt_pipe = fopen(status->new.name, "r");
4174                 info = "Untracked file %s";
4175                 break;
4177         case LINE_STAT_HEAD:
4178                 return REQ_NONE;
4180         default:
4181                 die("line type %d not handled in switch", line->type);
4182         }
4184         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4185         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4186         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4187                 if (status) {
4188                         stage_status = *status;
4189                 } else {
4190                         memset(&stage_status, 0, sizeof(stage_status));
4191                 }
4193                 stage_line_type = line->type;
4194                 stage_chunks = 0;
4195                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4196         }
4198         return REQ_NONE;
4201 static bool
4202 status_exists(struct status *status, enum line_type type)
4204         struct view *view = VIEW(REQ_VIEW_STATUS);
4205         struct line *line;
4207         for (line = view->line; line < view->line + view->lines; line++) {
4208                 struct status *pos = line->data;
4210                 if (line->type == type && pos &&
4211                     !strcmp(status->new.name, pos->new.name))
4212                         return TRUE;
4213         }
4215         return FALSE;
4219 static FILE *
4220 status_update_prepare(enum line_type type)
4222         char cmd[SIZEOF_STR];
4223         size_t cmdsize = 0;
4225         if (opt_cdup[0] &&
4226             type != LINE_STAT_UNTRACKED &&
4227             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4228                 return NULL;
4230         switch (type) {
4231         case LINE_STAT_STAGED:
4232                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4233                 break;
4235         case LINE_STAT_UNSTAGED:
4236         case LINE_STAT_UNTRACKED:
4237                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4238                 break;
4240         default:
4241                 die("line type %d not handled in switch", type);
4242         }
4244         return popen(cmd, "w");
4247 static bool
4248 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4250         char buf[SIZEOF_STR];
4251         size_t bufsize = 0;
4252         size_t written = 0;
4254         switch (type) {
4255         case LINE_STAT_STAGED:
4256                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4257                                         status->old.mode,
4258                                         status->old.rev,
4259                                         status->old.name, 0))
4260                         return FALSE;
4261                 break;
4263         case LINE_STAT_UNSTAGED:
4264         case LINE_STAT_UNTRACKED:
4265                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4266                         return FALSE;
4267                 break;
4269         default:
4270                 die("line type %d not handled in switch", type);
4271         }
4273         while (!ferror(pipe) && written < bufsize) {
4274                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4275         }
4277         return written == bufsize;
4280 static bool
4281 status_update_file(struct status *status, enum line_type type)
4283         FILE *pipe = status_update_prepare(type);
4284         bool result;
4286         if (!pipe)
4287                 return FALSE;
4289         result = status_update_write(pipe, status, type);
4290         pclose(pipe);
4291         return result;
4294 static bool
4295 status_update_files(struct view *view, struct line *line)
4297         FILE *pipe = status_update_prepare(line->type);
4298         bool result = TRUE;
4299         struct line *pos = view->line + view->lines;
4300         int files = 0;
4301         int file, done;
4303         if (!pipe)
4304                 return FALSE;
4306         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4307                 files++;
4309         for (file = 0, done = 0; result && file < files; line++, file++) {
4310                 int almost_done = file * 100 / files;
4312                 if (almost_done > done) {
4313                         done = almost_done;
4314                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4315                                       file, files, done);
4316                         update_view_title(view);
4317                 }
4318                 result = status_update_write(pipe, line->data, line->type);
4319         }
4321         pclose(pipe);
4322         return result;
4325 static bool
4326 status_update(struct view *view)
4328         struct line *line = &view->line[view->lineno];
4330         assert(view->lines);
4332         if (!line->data) {
4333                 /* This should work even for the "On branch" line. */
4334                 if (line < view->line + view->lines && !line[1].data) {
4335                         report("Nothing to update");
4336                         return FALSE;
4337                 }
4339                 if (!status_update_files(view, line + 1)) {
4340                         report("Failed to update file status");
4341                         return FALSE;
4342                 }
4344         } else if (!status_update_file(line->data, line->type)) {
4345                 report("Failed to update file status");
4346                 return FALSE;
4347         }
4349         return TRUE;
4352 static enum request
4353 status_request(struct view *view, enum request request, struct line *line)
4355         struct status *status = line->data;
4357         switch (request) {
4358         case REQ_STATUS_UPDATE:
4359                 if (!status_update(view))
4360                         return REQ_NONE;
4361                 break;
4363         case REQ_STATUS_MERGE:
4364                 if (!status || status->status != 'U') {
4365                         report("Merging only possible for files with unmerged status ('U').");
4366                         return REQ_NONE;
4367                 }
4368                 open_mergetool(status->new.name);
4369                 break;
4371         case REQ_EDIT:
4372                 if (!status)
4373                         return request;
4375                 open_editor(status->status != '?', status->new.name);
4376                 break;
4378         case REQ_VIEW_BLAME:
4379                 if (status) {
4380                         string_copy(opt_file, status->new.name);
4381                         opt_ref[0] = 0;
4382                 }
4383                 return request;
4385         case REQ_ENTER:
4386                 /* After returning the status view has been split to
4387                  * show the stage view. No further reloading is
4388                  * necessary. */
4389                 status_enter(view, line);
4390                 return REQ_NONE;
4392         case REQ_REFRESH:
4393                 /* Simply reload the view. */
4394                 break;
4396         default:
4397                 return request;
4398         }
4400         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4402         return REQ_NONE;
4405 static void
4406 status_select(struct view *view, struct line *line)
4408         struct status *status = line->data;
4409         char file[SIZEOF_STR] = "all files";
4410         char *text;
4411         char *key;
4413         if (status && !string_format(file, "'%s'", status->new.name))
4414                 return;
4416         if (!status && line[1].type == LINE_STAT_NONE)
4417                 line++;
4419         switch (line->type) {
4420         case LINE_STAT_STAGED:
4421                 text = "Press %s to unstage %s for commit";
4422                 break;
4424         case LINE_STAT_UNSTAGED:
4425                 text = "Press %s to stage %s for commit";
4426                 break;
4428         case LINE_STAT_UNTRACKED:
4429                 text = "Press %s to stage %s for addition";
4430                 break;
4432         case LINE_STAT_HEAD:
4433         case LINE_STAT_NONE:
4434                 text = "Nothing to update";
4435                 break;
4437         default:
4438                 die("line type %d not handled in switch", line->type);
4439         }
4441         if (status && status->status == 'U') {
4442                 text = "Press %s to resolve conflict in %s";
4443                 key = get_key(REQ_STATUS_MERGE);
4445         } else {
4446                 key = get_key(REQ_STATUS_UPDATE);
4447         }
4449         string_format(view->ref, text, key, file);
4452 static bool
4453 status_grep(struct view *view, struct line *line)
4455         struct status *status = line->data;
4456         enum { S_STATUS, S_NAME, S_END } state;
4457         char buf[2] = "?";
4458         regmatch_t pmatch;
4460         if (!status)
4461                 return FALSE;
4463         for (state = S_STATUS; state < S_END; state++) {
4464                 char *text;
4466                 switch (state) {
4467                 case S_NAME:    text = status->new.name;        break;
4468                 case S_STATUS:
4469                         buf[0] = status->status;
4470                         text = buf;
4471                         break;
4473                 default:
4474                         return FALSE;
4475                 }
4477                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4478                         return TRUE;
4479         }
4481         return FALSE;
4484 static struct view_ops status_ops = {
4485         "file",
4486         status_open,
4487         NULL,
4488         status_draw,
4489         status_request,
4490         status_grep,
4491         status_select,
4492 };
4495 static bool
4496 stage_diff_line(FILE *pipe, struct line *line)
4498         char *buf = line->data;
4499         size_t bufsize = strlen(buf);
4500         size_t written = 0;
4502         while (!ferror(pipe) && written < bufsize) {
4503                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4504         }
4506         fputc('\n', pipe);
4508         return written == bufsize;
4511 static bool
4512 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4514         while (line < end) {
4515                 if (!stage_diff_line(pipe, line++))
4516                         return FALSE;
4517                 if (line->type == LINE_DIFF_CHUNK ||
4518                     line->type == LINE_DIFF_HEADER)
4519                         break;
4520         }
4522         return TRUE;
4525 static struct line *
4526 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4528         for (; view->line < line; line--)
4529                 if (line->type == type)
4530                         return line;
4532         return NULL;
4535 static bool
4536 stage_update_chunk(struct view *view, struct line *chunk)
4538         char cmd[SIZEOF_STR];
4539         size_t cmdsize = 0;
4540         struct line *diff_hdr;
4541         FILE *pipe;
4543         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4544         if (!diff_hdr)
4545                 return FALSE;
4547         if (opt_cdup[0] &&
4548             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4549                 return FALSE;
4551         if (!string_format_from(cmd, &cmdsize,
4552                                 "git apply --whitespace=nowarn --cached %s - && "
4553                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4554                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4555                 return FALSE;
4557         pipe = popen(cmd, "w");
4558         if (!pipe)
4559                 return FALSE;
4561         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4562             !stage_diff_write(pipe, chunk, view->line + view->lines))
4563                 chunk = NULL;
4565         pclose(pipe);
4567         return chunk ? TRUE : FALSE;
4570 static bool
4571 stage_update(struct view *view, struct line *line)
4573         struct line *chunk = NULL;
4575         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4576                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4578         if (chunk) {
4579                 if (!stage_update_chunk(view, chunk)) {
4580                         report("Failed to apply chunk");
4581                         return FALSE;
4582                 }
4584         } else if (!stage_status.status) {
4585                 view = VIEW(REQ_VIEW_STATUS);
4587                 for (line = view->line; line < view->line + view->lines; line++)
4588                         if (line->type == stage_line_type)
4589                                 break;
4591                 if (!status_update_files(view, line + 1)) {
4592                         report("Failed to update files");
4593                         return FALSE;
4594                 }
4596         } else if (!status_update_file(&stage_status, stage_line_type)) {
4597                 report("Failed to update file");
4598                 return FALSE;
4599         }
4601         return TRUE;
4604 static void
4605 stage_next(struct view *view, struct line *line)
4607         int i;
4609         if (!stage_chunks) {
4610                 static size_t alloc = 0;
4611                 int *tmp;
4613                 for (line = view->line; line < view->line + view->lines; line++) {
4614                         if (line->type != LINE_DIFF_CHUNK)
4615                                 continue;
4617                         tmp = realloc_items(stage_chunk, &alloc,
4618                                             stage_chunks, sizeof(*tmp));
4619                         if (!tmp) {
4620                                 report("Allocation failure");
4621                                 return;
4622                         }
4624                         stage_chunk = tmp;
4625                         stage_chunk[stage_chunks++] = line - view->line;
4626                 }
4627         }
4629         for (i = 0; i < stage_chunks; i++) {
4630                 if (stage_chunk[i] > view->lineno) {
4631                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4632                         report("Chunk %d of %d", i + 1, stage_chunks);
4633                         return;
4634                 }
4635         }
4637         report("No next chunk found");
4640 static enum request
4641 stage_request(struct view *view, enum request request, struct line *line)
4643         switch (request) {
4644         case REQ_STATUS_UPDATE:
4645                 if (!stage_update(view, line))
4646                         return REQ_NONE;
4647                 break;
4649         case REQ_STAGE_NEXT:
4650                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4651                         report("File is untracked; press %s to add",
4652                                get_key(REQ_STATUS_UPDATE));
4653                         return REQ_NONE;
4654                 }
4655                 stage_next(view, line);
4656                 return REQ_NONE;
4658         case REQ_EDIT:
4659                 if (!stage_status.new.name[0])
4660                         return request;
4662                 open_editor(stage_status.status != '?', stage_status.new.name);
4663                 break;
4665         case REQ_REFRESH:
4666                 /* Reload everything ... */
4667                 break;
4669         case REQ_VIEW_BLAME:
4670                 if (stage_status.new.name[0]) {
4671                         string_copy(opt_file, stage_status.new.name);
4672                         opt_ref[0] = 0;
4673                 }
4674                 return request;
4676         case REQ_ENTER:
4677                 return pager_request(view, request, line);
4679         default:
4680                 return request;
4681         }
4683         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4685         /* Check whether the staged entry still exists, and close the
4686          * stage view if it doesn't. */
4687         if (!status_exists(&stage_status, stage_line_type))
4688                 return REQ_VIEW_CLOSE;
4690         if (stage_line_type == LINE_STAT_UNTRACKED)
4691                 opt_pipe = fopen(stage_status.new.name, "r");
4692         else
4693                 string_copy(opt_cmd, view->cmd);
4694         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4696         return REQ_NONE;
4699 static struct view_ops stage_ops = {
4700         "line",
4701         NULL,
4702         pager_read,
4703         pager_draw,
4704         stage_request,
4705         pager_grep,
4706         pager_select,
4707 };
4710 /*
4711  * Revision graph
4712  */
4714 struct commit {
4715         char id[SIZEOF_REV];            /* SHA1 ID. */
4716         char title[128];                /* First line of the commit message. */
4717         char author[75];                /* Author of the commit. */
4718         struct tm time;                 /* Date from the author ident. */
4719         struct ref **refs;              /* Repository references. */
4720         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4721         size_t graph_size;              /* The width of the graph array. */
4722         bool has_parents;               /* Rewritten --parents seen. */
4723 };
4725 /* Size of rev graph with no  "padding" columns */
4726 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4728 struct rev_graph {
4729         struct rev_graph *prev, *next, *parents;
4730         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4731         size_t size;
4732         struct commit *commit;
4733         size_t pos;
4734         unsigned int boundary:1;
4735 };
4737 /* Parents of the commit being visualized. */
4738 static struct rev_graph graph_parents[4];
4740 /* The current stack of revisions on the graph. */
4741 static struct rev_graph graph_stacks[4] = {
4742         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4743         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4744         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4745         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4746 };
4748 static inline bool
4749 graph_parent_is_merge(struct rev_graph *graph)
4751         return graph->parents->size > 1;
4754 static inline void
4755 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4757         struct commit *commit = graph->commit;
4759         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4760                 commit->graph[commit->graph_size++] = symbol;
4763 static void
4764 done_rev_graph(struct rev_graph *graph)
4766         if (graph_parent_is_merge(graph) &&
4767             graph->pos < graph->size - 1 &&
4768             graph->next->size == graph->size + graph->parents->size - 1) {
4769                 size_t i = graph->pos + graph->parents->size - 1;
4771                 graph->commit->graph_size = i * 2;
4772                 while (i < graph->next->size - 1) {
4773                         append_to_rev_graph(graph, ' ');
4774                         append_to_rev_graph(graph, '\\');
4775                         i++;
4776                 }
4777         }
4779         graph->size = graph->pos = 0;
4780         graph->commit = NULL;
4781         memset(graph->parents, 0, sizeof(*graph->parents));
4784 static void
4785 push_rev_graph(struct rev_graph *graph, char *parent)
4787         int i;
4789         /* "Collapse" duplicate parents lines.
4790          *
4791          * FIXME: This needs to also update update the drawn graph but
4792          * for now it just serves as a method for pruning graph lines. */
4793         for (i = 0; i < graph->size; i++)
4794                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4795                         return;
4797         if (graph->size < SIZEOF_REVITEMS) {
4798                 string_copy_rev(graph->rev[graph->size++], parent);
4799         }
4802 static chtype
4803 get_rev_graph_symbol(struct rev_graph *graph)
4805         chtype symbol;
4807         if (graph->boundary)
4808                 symbol = REVGRAPH_BOUND;
4809         else if (graph->parents->size == 0)
4810                 symbol = REVGRAPH_INIT;
4811         else if (graph_parent_is_merge(graph))
4812                 symbol = REVGRAPH_MERGE;
4813         else if (graph->pos >= graph->size)
4814                 symbol = REVGRAPH_BRANCH;
4815         else
4816                 symbol = REVGRAPH_COMMIT;
4818         return symbol;
4821 static void
4822 draw_rev_graph(struct rev_graph *graph)
4824         struct rev_filler {
4825                 chtype separator, line;
4826         };
4827         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4828         static struct rev_filler fillers[] = {
4829                 { ' ',  '|' },
4830                 { '`',  '.' },
4831                 { '\'', ' ' },
4832                 { '/',  ' ' },
4833         };
4834         chtype symbol = get_rev_graph_symbol(graph);
4835         struct rev_filler *filler;
4836         size_t i;
4838         if (opt_line_graphics)
4839                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4841         filler = &fillers[DEFAULT];
4843         for (i = 0; i < graph->pos; i++) {
4844                 append_to_rev_graph(graph, filler->line);
4845                 if (graph_parent_is_merge(graph->prev) &&
4846                     graph->prev->pos == i)
4847                         filler = &fillers[RSHARP];
4849                 append_to_rev_graph(graph, filler->separator);
4850         }
4852         /* Place the symbol for this revision. */
4853         append_to_rev_graph(graph, symbol);
4855         if (graph->prev->size > graph->size)
4856                 filler = &fillers[RDIAG];
4857         else
4858                 filler = &fillers[DEFAULT];
4860         i++;
4862         for (; i < graph->size; i++) {
4863                 append_to_rev_graph(graph, filler->separator);
4864                 append_to_rev_graph(graph, filler->line);
4865                 if (graph_parent_is_merge(graph->prev) &&
4866                     i < graph->prev->pos + graph->parents->size)
4867                         filler = &fillers[RSHARP];
4868                 if (graph->prev->size > graph->size)
4869                         filler = &fillers[LDIAG];
4870         }
4872         if (graph->prev->size > graph->size) {
4873                 append_to_rev_graph(graph, filler->separator);
4874                 if (filler->line != ' ')
4875                         append_to_rev_graph(graph, filler->line);
4876         }
4879 /* Prepare the next rev graph */
4880 static void
4881 prepare_rev_graph(struct rev_graph *graph)
4883         size_t i;
4885         /* First, traverse all lines of revisions up to the active one. */
4886         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4887                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4888                         break;
4890                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4891         }
4893         /* Interleave the new revision parent(s). */
4894         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4895                 push_rev_graph(graph->next, graph->parents->rev[i]);
4897         /* Lastly, put any remaining revisions. */
4898         for (i = graph->pos + 1; i < graph->size; i++)
4899                 push_rev_graph(graph->next, graph->rev[i]);
4902 static void
4903 update_rev_graph(struct rev_graph *graph)
4905         /* If this is the finalizing update ... */
4906         if (graph->commit)
4907                 prepare_rev_graph(graph);
4909         /* Graph visualization needs a one rev look-ahead,
4910          * so the first update doesn't visualize anything. */
4911         if (!graph->prev->commit)
4912                 return;
4914         draw_rev_graph(graph->prev);
4915         done_rev_graph(graph->prev->prev);
4919 /*
4920  * Main view backend
4921  */
4923 static bool
4924 main_draw(struct view *view, struct line *line, unsigned int lineno)
4926         struct commit *commit = line->data;
4928         if (!*commit->author)
4929                 return FALSE;
4931         if (opt_date && draw_date(view, &commit->time))
4932                 return TRUE;
4934         if (opt_author &&
4935             draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4936                 return TRUE;
4938         if (opt_rev_graph && commit->graph_size &&
4939             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4940                 return TRUE;
4942         if (opt_show_refs && commit->refs) {
4943                 size_t i = 0;
4945                 do {
4946                         enum line_type type;
4948                         if (commit->refs[i]->head)
4949                                 type = LINE_MAIN_HEAD;
4950                         else if (commit->refs[i]->ltag)
4951                                 type = LINE_MAIN_LOCAL_TAG;
4952                         else if (commit->refs[i]->tag)
4953                                 type = LINE_MAIN_TAG;
4954                         else if (commit->refs[i]->tracked)
4955                                 type = LINE_MAIN_TRACKED;
4956                         else if (commit->refs[i]->remote)
4957                                 type = LINE_MAIN_REMOTE;
4958                         else
4959                                 type = LINE_MAIN_REF;
4961                         if (draw_text(view, type, "[", TRUE) ||
4962                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
4963                             draw_text(view, type, "]", TRUE))
4964                                 return TRUE;
4966                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4967                                 return TRUE;
4968                 } while (commit->refs[i++]->next);
4969         }
4971         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4972         return TRUE;
4975 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4976 static bool
4977 main_read(struct view *view, char *line)
4979         static struct rev_graph *graph = graph_stacks;
4980         enum line_type type;
4981         struct commit *commit;
4983         if (!line) {
4984                 if (!view->lines && !view->parent)
4985                         die("No revisions match the given arguments.");
4986                 update_rev_graph(graph);
4987                 return TRUE;
4988         }
4990         type = get_line_type(line);
4991         if (type == LINE_COMMIT) {
4992                 commit = calloc(1, sizeof(struct commit));
4993                 if (!commit)
4994                         return FALSE;
4996                 line += STRING_SIZE("commit ");
4997                 if (*line == '-') {
4998                         graph->boundary = 1;
4999                         line++;
5000                 }
5002                 string_copy_rev(commit->id, line);
5003                 commit->refs = get_refs(commit->id);
5004                 graph->commit = commit;
5005                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5007                 while ((line = strchr(line, ' '))) {
5008                         line++;
5009                         push_rev_graph(graph->parents, line);
5010                         commit->has_parents = TRUE;
5011                 }
5012                 return TRUE;
5013         }
5015         if (!view->lines)
5016                 return TRUE;
5017         commit = view->line[view->lines - 1].data;
5019         switch (type) {
5020         case LINE_PARENT:
5021                 if (commit->has_parents)
5022                         break;
5023                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5024                 break;
5026         case LINE_AUTHOR:
5027         {
5028                 /* Parse author lines where the name may be empty:
5029                  *      author  <email@address.tld> 1138474660 +0100
5030                  */
5031                 char *ident = line + STRING_SIZE("author ");
5032                 char *nameend = strchr(ident, '<');
5033                 char *emailend = strchr(ident, '>');
5035                 if (!nameend || !emailend)
5036                         break;
5038                 update_rev_graph(graph);
5039                 graph = graph->next;
5041                 *nameend = *emailend = 0;
5042                 ident = chomp_string(ident);
5043                 if (!*ident) {
5044                         ident = chomp_string(nameend + 1);
5045                         if (!*ident)
5046                                 ident = "Unknown";
5047                 }
5049                 string_ncopy(commit->author, ident, strlen(ident));
5051                 /* Parse epoch and timezone */
5052                 if (emailend[1] == ' ') {
5053                         char *secs = emailend + 2;
5054                         char *zone = strchr(secs, ' ');
5055                         time_t time = (time_t) atol(secs);
5057                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5058                                 long tz;
5060                                 zone++;
5061                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5062                                 tz += ('0' - zone[2]) * 60 * 60;
5063                                 tz += ('0' - zone[3]) * 60;
5064                                 tz += ('0' - zone[4]) * 60;
5066                                 if (zone[0] == '-')
5067                                         tz = -tz;
5069                                 time -= tz;
5070                         }
5072                         gmtime_r(&time, &commit->time);
5073                 }
5074                 break;
5075         }
5076         default:
5077                 /* Fill in the commit title if it has not already been set. */
5078                 if (commit->title[0])
5079                         break;
5081                 /* Require titles to start with a non-space character at the
5082                  * offset used by git log. */
5083                 if (strncmp(line, "    ", 4))
5084                         break;
5085                 line += 4;
5086                 /* Well, if the title starts with a whitespace character,
5087                  * try to be forgiving.  Otherwise we end up with no title. */
5088                 while (isspace(*line))
5089                         line++;
5090                 if (*line == '\0')
5091                         break;
5092                 /* FIXME: More graceful handling of titles; append "..." to
5093                  * shortened titles, etc. */
5095                 string_ncopy(commit->title, line, strlen(line));
5096         }
5098         return TRUE;
5101 static enum request
5102 main_request(struct view *view, enum request request, struct line *line)
5104         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5106         if (request == REQ_ENTER)
5107                 open_view(view, REQ_VIEW_DIFF, flags);
5108         else
5109                 return request;
5111         return REQ_NONE;
5114 static bool
5115 grep_refs(struct ref **refs, regex_t *regex)
5117         regmatch_t pmatch;
5118         size_t i = 0;
5120         if (!refs)
5121                 return FALSE;
5122         do {
5123                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5124                         return TRUE;
5125         } while (refs[i++]->next);
5127         return FALSE;
5130 static bool
5131 main_grep(struct view *view, struct line *line)
5133         struct commit *commit = line->data;
5134         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5135         char buf[DATE_COLS + 1];
5136         regmatch_t pmatch;
5138         for (state = S_TITLE; state < S_END; state++) {
5139                 char *text;
5141                 switch (state) {
5142                 case S_TITLE:   text = commit->title;   break;
5143                 case S_AUTHOR:
5144                         if (!opt_author)
5145                                 continue;
5146                         text = commit->author;
5147                         break;
5148                 case S_DATE:
5149                         if (!opt_date)
5150                                 continue;
5151                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5152                                 continue;
5153                         text = buf;
5154                         break;
5155                 case S_REFS:
5156                         if (!opt_show_refs)
5157                                 continue;
5158                         if (grep_refs(commit->refs, view->regex) == TRUE)
5159                                 return TRUE;
5160                         continue;
5161                 default:
5162                         return FALSE;
5163                 }
5165                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5166                         return TRUE;
5167         }
5169         return FALSE;
5172 static void
5173 main_select(struct view *view, struct line *line)
5175         struct commit *commit = line->data;
5177         string_copy_rev(view->ref, commit->id);
5178         string_copy_rev(ref_commit, view->ref);
5181 static struct view_ops main_ops = {
5182         "commit",
5183         NULL,
5184         main_read,
5185         main_draw,
5186         main_request,
5187         main_grep,
5188         main_select,
5189 };
5192 /*
5193  * Unicode / UTF-8 handling
5194  *
5195  * NOTE: Much of the following code for dealing with unicode is derived from
5196  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5197  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5198  */
5200 /* I've (over)annotated a lot of code snippets because I am not entirely
5201  * confident that the approach taken by this small UTF-8 interface is correct.
5202  * --jonas */
5204 static inline int
5205 unicode_width(unsigned long c)
5207         if (c >= 0x1100 &&
5208            (c <= 0x115f                         /* Hangul Jamo */
5209             || c == 0x2329
5210             || c == 0x232a
5211             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5212                                                 /* CJK ... Yi */
5213             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5214             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5215             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5216             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5217             || (c >= 0xffe0  && c <= 0xffe6)
5218             || (c >= 0x20000 && c <= 0x2fffd)
5219             || (c >= 0x30000 && c <= 0x3fffd)))
5220                 return 2;
5222         if (c == '\t')
5223                 return opt_tab_size;
5225         return 1;
5228 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5229  * Illegal bytes are set one. */
5230 static const unsigned char utf8_bytes[256] = {
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         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,
5235         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,
5236         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,
5237         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,
5238         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,
5239 };
5241 /* Decode UTF-8 multi-byte representation into a unicode character. */
5242 static inline unsigned long
5243 utf8_to_unicode(const char *string, size_t length)
5245         unsigned long unicode;
5247         switch (length) {
5248         case 1:
5249                 unicode  =   string[0];
5250                 break;
5251         case 2:
5252                 unicode  =  (string[0] & 0x1f) << 6;
5253                 unicode +=  (string[1] & 0x3f);
5254                 break;
5255         case 3:
5256                 unicode  =  (string[0] & 0x0f) << 12;
5257                 unicode += ((string[1] & 0x3f) << 6);
5258                 unicode +=  (string[2] & 0x3f);
5259                 break;
5260         case 4:
5261                 unicode  =  (string[0] & 0x0f) << 18;
5262                 unicode += ((string[1] & 0x3f) << 12);
5263                 unicode += ((string[2] & 0x3f) << 6);
5264                 unicode +=  (string[3] & 0x3f);
5265                 break;
5266         case 5:
5267                 unicode  =  (string[0] & 0x0f) << 24;
5268                 unicode += ((string[1] & 0x3f) << 18);
5269                 unicode += ((string[2] & 0x3f) << 12);
5270                 unicode += ((string[3] & 0x3f) << 6);
5271                 unicode +=  (string[4] & 0x3f);
5272                 break;
5273         case 6:
5274                 unicode  =  (string[0] & 0x01) << 30;
5275                 unicode += ((string[1] & 0x3f) << 24);
5276                 unicode += ((string[2] & 0x3f) << 18);
5277                 unicode += ((string[3] & 0x3f) << 12);
5278                 unicode += ((string[4] & 0x3f) << 6);
5279                 unicode +=  (string[5] & 0x3f);
5280                 break;
5281         default:
5282                 die("Invalid unicode length");
5283         }
5285         /* Invalid characters could return the special 0xfffd value but NUL
5286          * should be just as good. */
5287         return unicode > 0xffff ? 0 : unicode;
5290 /* Calculates how much of string can be shown within the given maximum width
5291  * and sets trimmed parameter to non-zero value if all of string could not be
5292  * shown. If the reserve flag is TRUE, it will reserve at least one
5293  * trailing character, which can be useful when drawing a delimiter.
5294  *
5295  * Returns the number of bytes to output from string to satisfy max_width. */
5296 static size_t
5297 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5299         const char *start = string;
5300         const char *end = strchr(string, '\0');
5301         unsigned char last_bytes = 0;
5302         size_t last_ucwidth = 0;
5304         *width = 0;
5305         *trimmed = 0;
5307         while (string < end) {
5308                 int c = *(unsigned char *) string;
5309                 unsigned char bytes = utf8_bytes[c];
5310                 size_t ucwidth;
5311                 unsigned long unicode;
5313                 if (string + bytes > end)
5314                         break;
5316                 /* Change representation to figure out whether
5317                  * it is a single- or double-width character. */
5319                 unicode = utf8_to_unicode(string, bytes);
5320                 /* FIXME: Graceful handling of invalid unicode character. */
5321                 if (!unicode)
5322                         break;
5324                 ucwidth = unicode_width(unicode);
5325                 *width  += ucwidth;
5326                 if (*width > max_width) {
5327                         *trimmed = 1;
5328                         *width -= ucwidth;
5329                         if (reserve && *width == max_width) {
5330                                 string -= last_bytes;
5331                                 *width -= last_ucwidth;
5332                         }
5333                         break;
5334                 }
5336                 string  += bytes;
5337                 last_bytes = bytes;
5338                 last_ucwidth = ucwidth;
5339         }
5341         return string - start;
5345 /*
5346  * Status management
5347  */
5349 /* Whether or not the curses interface has been initialized. */
5350 static bool cursed = FALSE;
5352 /* The status window is used for polling keystrokes. */
5353 static WINDOW *status_win;
5355 static bool status_empty = TRUE;
5357 /* Update status and title window. */
5358 static void
5359 report(const char *msg, ...)
5361         struct view *view = display[current_view];
5363         if (input_mode)
5364                 return;
5366         if (!view) {
5367                 char buf[SIZEOF_STR];
5368                 va_list args;
5370                 va_start(args, msg);
5371                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5372                         buf[sizeof(buf) - 1] = 0;
5373                         buf[sizeof(buf) - 2] = '.';
5374                         buf[sizeof(buf) - 3] = '.';
5375                         buf[sizeof(buf) - 4] = '.';
5376                 }
5377                 va_end(args);
5378                 die("%s", buf);
5379         }
5381         if (!status_empty || *msg) {
5382                 va_list args;
5384                 va_start(args, msg);
5386                 wmove(status_win, 0, 0);
5387                 if (*msg) {
5388                         vwprintw(status_win, msg, args);
5389                         status_empty = FALSE;
5390                 } else {
5391                         status_empty = TRUE;
5392                 }
5393                 wclrtoeol(status_win);
5394                 wrefresh(status_win);
5396                 va_end(args);
5397         }
5399         update_view_title(view);
5400         update_display_cursor(view);
5403 /* Controls when nodelay should be in effect when polling user input. */
5404 static void
5405 set_nonblocking_input(bool loading)
5407         static unsigned int loading_views;
5409         if ((loading == FALSE && loading_views-- == 1) ||
5410             (loading == TRUE  && loading_views++ == 0))
5411                 nodelay(status_win, loading);
5414 static void
5415 init_display(void)
5417         int x, y;
5419         /* Initialize the curses library */
5420         if (isatty(STDIN_FILENO)) {
5421                 cursed = !!initscr();
5422         } else {
5423                 /* Leave stdin and stdout alone when acting as a pager. */
5424                 FILE *io = fopen("/dev/tty", "r+");
5426                 if (!io)
5427                         die("Failed to open /dev/tty");
5428                 cursed = !!newterm(NULL, io, io);
5429         }
5431         if (!cursed)
5432                 die("Failed to initialize curses");
5434         nonl();         /* Tell curses not to do NL->CR/NL on output */
5435         cbreak();       /* Take input chars one at a time, no wait for \n */
5436         noecho();       /* Don't echo input */
5437         leaveok(stdscr, TRUE);
5439         if (has_colors())
5440                 init_colors();
5442         getmaxyx(stdscr, y, x);
5443         status_win = newwin(1, 0, y - 1, 0);
5444         if (!status_win)
5445                 die("Failed to create status window");
5447         /* Enable keyboard mapping */
5448         keypad(status_win, TRUE);
5449         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5451         TABSIZE = opt_tab_size;
5452         if (opt_line_graphics) {
5453                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5454         }
5457 static char *
5458 read_prompt(const char *prompt)
5460         enum { READING, STOP, CANCEL } status = READING;
5461         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5462         int pos = 0;
5464         while (status == READING) {
5465                 struct view *view;
5466                 int i, key;
5468                 input_mode = TRUE;
5470                 foreach_view (view, i)
5471                         update_view(view);
5473                 input_mode = FALSE;
5475                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5476                 wclrtoeol(status_win);
5478                 /* Refresh, accept single keystroke of input */
5479                 key = wgetch(status_win);
5480                 switch (key) {
5481                 case KEY_RETURN:
5482                 case KEY_ENTER:
5483                 case '\n':
5484                         status = pos ? STOP : CANCEL;
5485                         break;
5487                 case KEY_BACKSPACE:
5488                         if (pos > 0)
5489                                 pos--;
5490                         else
5491                                 status = CANCEL;
5492                         break;
5494                 case KEY_ESC:
5495                         status = CANCEL;
5496                         break;
5498                 case ERR:
5499                         break;
5501                 default:
5502                         if (pos >= sizeof(buf)) {
5503                                 report("Input string too long");
5504                                 return NULL;
5505                         }
5507                         if (isprint(key))
5508                                 buf[pos++] = (char) key;
5509                 }
5510         }
5512         /* Clear the status window */
5513         status_empty = FALSE;
5514         report("");
5516         if (status == CANCEL)
5517                 return NULL;
5519         buf[pos++] = 0;
5521         return buf;
5524 /*
5525  * Repository references
5526  */
5528 static struct ref *refs = NULL;
5529 static size_t refs_alloc = 0;
5530 static size_t refs_size = 0;
5532 /* Id <-> ref store */
5533 static struct ref ***id_refs = NULL;
5534 static size_t id_refs_alloc = 0;
5535 static size_t id_refs_size = 0;
5537 static struct ref **
5538 get_refs(char *id)
5540         struct ref ***tmp_id_refs;
5541         struct ref **ref_list = NULL;
5542         size_t ref_list_alloc = 0;
5543         size_t ref_list_size = 0;
5544         size_t i;
5546         for (i = 0; i < id_refs_size; i++)
5547                 if (!strcmp(id, id_refs[i][0]->id))
5548                         return id_refs[i];
5550         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5551                                     sizeof(*id_refs));
5552         if (!tmp_id_refs)
5553                 return NULL;
5555         id_refs = tmp_id_refs;
5557         for (i = 0; i < refs_size; i++) {
5558                 struct ref **tmp;
5560                 if (strcmp(id, refs[i].id))
5561                         continue;
5563                 tmp = realloc_items(ref_list, &ref_list_alloc,
5564                                     ref_list_size + 1, sizeof(*ref_list));
5565                 if (!tmp) {
5566                         if (ref_list)
5567                                 free(ref_list);
5568                         return NULL;
5569                 }
5571                 ref_list = tmp;
5572                 if (ref_list_size > 0)
5573                         ref_list[ref_list_size - 1]->next = 1;
5574                 ref_list[ref_list_size] = &refs[i];
5576                 /* XXX: The properties of the commit chains ensures that we can
5577                  * safely modify the shared ref. The repo references will
5578                  * always be similar for the same id. */
5579                 ref_list[ref_list_size]->next = 0;
5580                 ref_list_size++;
5581         }
5583         if (ref_list)
5584                 id_refs[id_refs_size++] = ref_list;
5586         return ref_list;
5589 static int
5590 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5592         struct ref *ref;
5593         bool tag = FALSE;
5594         bool ltag = FALSE;
5595         bool remote = FALSE;
5596         bool tracked = FALSE;
5597         bool check_replace = FALSE;
5598         bool head = FALSE;
5600         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5601                 if (!strcmp(name + namelen - 3, "^{}")) {
5602                         namelen -= 3;
5603                         name[namelen] = 0;
5604                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5605                                 check_replace = TRUE;
5606                 } else {
5607                         ltag = TRUE;
5608                 }
5610                 tag = TRUE;
5611                 namelen -= STRING_SIZE("refs/tags/");
5612                 name    += STRING_SIZE("refs/tags/");
5614         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5615                 remote = TRUE;
5616                 namelen -= STRING_SIZE("refs/remotes/");
5617                 name    += STRING_SIZE("refs/remotes/");
5618                 tracked  = !strcmp(opt_remote, name);
5620         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5621                 namelen -= STRING_SIZE("refs/heads/");
5622                 name    += STRING_SIZE("refs/heads/");
5623                 head     = !strncmp(opt_head, name, namelen);
5625         } else if (!strcmp(name, "HEAD")) {
5626                 opt_no_head = FALSE;
5627                 return OK;
5628         }
5630         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5631                 /* it's an annotated tag, replace the previous sha1 with the
5632                  * resolved commit id; relies on the fact git-ls-remote lists
5633                  * the commit id of an annotated tag right beofre the commit id
5634                  * it points to. */
5635                 refs[refs_size - 1].ltag = ltag;
5636                 string_copy_rev(refs[refs_size - 1].id, id);
5638                 return OK;
5639         }
5640         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5641         if (!refs)
5642                 return ERR;
5644         ref = &refs[refs_size++];
5645         ref->name = malloc(namelen + 1);
5646         if (!ref->name)
5647                 return ERR;
5649         strncpy(ref->name, name, namelen);
5650         ref->name[namelen] = 0;
5651         ref->head = head;
5652         ref->tag = tag;
5653         ref->ltag = ltag;
5654         ref->remote = remote;
5655         ref->tracked = tracked;
5656         string_copy_rev(ref->id, id);
5658         return OK;
5661 static int
5662 load_refs(void)
5664         const char *cmd_env = getenv("TIG_LS_REMOTE");
5665         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5667         return read_properties(popen(cmd, "r"), "\t", read_ref);
5670 static int
5671 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5673         if (!strcmp(name, "i18n.commitencoding"))
5674                 string_ncopy(opt_encoding, value, valuelen);
5676         if (!strcmp(name, "core.editor"))
5677                 string_ncopy(opt_editor, value, valuelen);
5679         /* branch.<head>.remote */
5680         if (*opt_head &&
5681             !strncmp(name, "branch.", 7) &&
5682             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5683             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5684                 string_ncopy(opt_remote, value, valuelen);
5686         if (*opt_head && *opt_remote &&
5687             !strncmp(name, "branch.", 7) &&
5688             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5689             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5690                 size_t from = strlen(opt_remote);
5692                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5693                         value += STRING_SIZE("refs/heads/");
5694                         valuelen -= STRING_SIZE("refs/heads/");
5695                 }
5697                 if (!string_format_from(opt_remote, &from, "/%s", value))
5698                         opt_remote[0] = 0;
5699         }
5701         return OK;
5704 static int
5705 load_git_config(void)
5707         return read_properties(popen(GIT_CONFIG " --list", "r"),
5708                                "=", read_repo_config_option);
5711 static int
5712 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5714         if (!opt_git_dir[0]) {
5715                 string_ncopy(opt_git_dir, name, namelen);
5717         } else if (opt_is_inside_work_tree == -1) {
5718                 /* This can be 3 different values depending on the
5719                  * version of git being used. If git-rev-parse does not
5720                  * understand --is-inside-work-tree it will simply echo
5721                  * the option else either "true" or "false" is printed.
5722                  * Default to true for the unknown case. */
5723                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5725         } else if (opt_cdup[0] == ' ') {
5726                 string_ncopy(opt_cdup, name, namelen);
5727         } else {
5728                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5729                         namelen -= STRING_SIZE("refs/heads/");
5730                         name    += STRING_SIZE("refs/heads/");
5731                         string_ncopy(opt_head, name, namelen);
5732                 }
5733         }
5735         return OK;
5738 static int
5739 load_repo_info(void)
5741         int result;
5742         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5743                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5745         /* XXX: The line outputted by "--show-cdup" can be empty so
5746          * initialize it to something invalid to make it possible to
5747          * detect whether it has been set or not. */
5748         opt_cdup[0] = ' ';
5750         result = read_properties(pipe, "=", read_repo_info);
5751         if (opt_cdup[0] == ' ')
5752                 opt_cdup[0] = 0;
5754         return result;
5757 static int
5758 read_properties(FILE *pipe, const char *separators,
5759                 int (*read_property)(char *, size_t, char *, size_t))
5761         char buffer[BUFSIZ];
5762         char *name;
5763         int state = OK;
5765         if (!pipe)
5766                 return ERR;
5768         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5769                 char *value;
5770                 size_t namelen;
5771                 size_t valuelen;
5773                 name = chomp_string(name);
5774                 namelen = strcspn(name, separators);
5776                 if (name[namelen]) {
5777                         name[namelen] = 0;
5778                         value = chomp_string(name + namelen + 1);
5779                         valuelen = strlen(value);
5781                 } else {
5782                         value = "";
5783                         valuelen = 0;
5784                 }
5786                 state = read_property(name, namelen, value, valuelen);
5787         }
5789         if (state != ERR && ferror(pipe))
5790                 state = ERR;
5792         pclose(pipe);
5794         return state;
5798 /*
5799  * Main
5800  */
5802 static void __NORETURN
5803 quit(int sig)
5805         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5806         if (cursed)
5807                 endwin();
5808         exit(0);
5811 static void __NORETURN
5812 die(const char *err, ...)
5814         va_list args;
5816         endwin();
5818         va_start(args, err);
5819         fputs("tig: ", stderr);
5820         vfprintf(stderr, err, args);
5821         fputs("\n", stderr);
5822         va_end(args);
5824         exit(1);
5827 static void
5828 warn(const char *msg, ...)
5830         va_list args;
5832         va_start(args, msg);
5833         fputs("tig warning: ", stderr);
5834         vfprintf(stderr, msg, args);
5835         fputs("\n", stderr);
5836         va_end(args);
5839 int
5840 main(int argc, char *argv[])
5842         struct view *view;
5843         enum request request;
5844         size_t i;
5846         signal(SIGINT, quit);
5848         if (setlocale(LC_ALL, "")) {
5849                 char *codeset = nl_langinfo(CODESET);
5851                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5852         }
5854         if (load_repo_info() == ERR)
5855                 die("Failed to load repo info.");
5857         if (load_options() == ERR)
5858                 die("Failed to load user config.");
5860         if (load_git_config() == ERR)
5861                 die("Failed to load repo config.");
5863         if (!parse_options(argc, argv))
5864                 return 0;
5866         /* Require a git repository unless when running in pager mode. */
5867         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5868                 die("Not a git repository");
5870         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5871                 opt_utf8 = FALSE;
5873         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5874                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5875                 if (opt_iconv == ICONV_NONE)
5876                         die("Failed to initialize character set conversion");
5877         }
5879         if (*opt_git_dir && load_refs() == ERR)
5880                 die("Failed to load refs.");
5882         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5883                 view->cmd_env = getenv(view->cmd_env);
5885         request = opt_request;
5887         init_display();
5889         while (view_driver(display[current_view], request)) {
5890                 int key;
5891                 int i;
5893                 foreach_view (view, i)
5894                         update_view(view);
5896                 /* Refresh, accept single keystroke of input */
5897                 key = wgetch(status_win);
5899                 /* wgetch() with nodelay() enabled returns ERR when there's no
5900                  * input. */
5901                 if (key == ERR) {
5902                         request = REQ_NONE;
5903                         continue;
5904                 }
5906                 request = get_keybinding(display[current_view]->keymap, key);
5908                 /* Some low-level request handling. This keeps access to
5909                  * status_win restricted. */
5910                 switch (request) {
5911                 case REQ_PROMPT:
5912                 {
5913                         char *cmd = read_prompt(":");
5915                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5916                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5917                                         opt_request = REQ_VIEW_DIFF;
5918                                 } else {
5919                                         opt_request = REQ_VIEW_PAGER;
5920                                 }
5921                                 break;
5922                         }
5924                         request = REQ_NONE;
5925                         break;
5926                 }
5927                 case REQ_SEARCH:
5928                 case REQ_SEARCH_BACK:
5929                 {
5930                         const char *prompt = request == REQ_SEARCH
5931                                            ? "/" : "?";
5932                         char *search = read_prompt(prompt);
5934                         if (search)
5935                                 string_ncopy(opt_search, search, strlen(search));
5936                         else
5937                                 request = REQ_NONE;
5938                         break;
5939                 }
5940                 case REQ_SCREEN_RESIZE:
5941                 {
5942                         int height, width;
5944                         getmaxyx(stdscr, height, width);
5946                         /* Resize the status view and let the view driver take
5947                          * care of resizing the displayed views. */
5948                         wresize(status_win, 1, width);
5949                         mvwin(status_win, height - 1, 0);
5950                         wrefresh(status_win);
5951                         break;
5952                 }
5953                 default:
5954                         break;
5955                 }
5956         }
5958         quit(0);
5960         return 0;