Code

8e06c1ac5031bbb19cb3e0275e91e46c71652976
[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_(TREE_PARENT,       "Switch to parent directory in tree view"), \
380         REQ_(EDIT,              "Open in editor"), \
381         REQ_(NONE,              "Do nothing")
384 /* User action requests. */
385 enum request {
386 #define REQ_GROUP(help)
387 #define REQ_(req, help) REQ_##req
389         /* Offset all requests to avoid conflicts with ncurses getch values. */
390         REQ_OFFSET = KEY_MAX + 1,
391         REQ_INFO
393 #undef  REQ_GROUP
394 #undef  REQ_
395 };
397 struct request_info {
398         enum request request;
399         char *name;
400         int namelen;
401         char *help;
402 };
404 static struct request_info req_info[] = {
405 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
406 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
407         REQ_INFO
408 #undef  REQ_GROUP
409 #undef  REQ_
410 };
412 static enum request
413 get_request(const char *name)
415         int namelen = strlen(name);
416         int i;
418         for (i = 0; i < ARRAY_SIZE(req_info); i++)
419                 if (req_info[i].namelen == namelen &&
420                     !string_enum_compare(req_info[i].name, name, namelen))
421                         return req_info[i].request;
423         return REQ_NONE;
427 /*
428  * Options
429  */
431 static const char usage[] =
432 "tig " TIG_VERSION " (" __DATE__ ")\n"
433 "\n"
434 "Usage: tig        [options] [revs] [--] [paths]\n"
435 "   or: tig show   [options] [revs] [--] [paths]\n"
436 "   or: tig blame  [rev] path\n"
437 "   or: tig status\n"
438 "   or: tig <      [git command output]\n"
439 "\n"
440 "Options:\n"
441 "  -v, --version   Show version and exit\n"
442 "  -h, --help      Show help message and exit";
444 /* Option and state variables. */
445 static bool opt_date                    = TRUE;
446 static bool opt_author                  = TRUE;
447 static bool opt_line_number             = FALSE;
448 static bool opt_line_graphics           = TRUE;
449 static bool opt_rev_graph               = FALSE;
450 static bool opt_show_refs               = TRUE;
451 static int opt_num_interval             = NUMBER_INTERVAL;
452 static int opt_tab_size                 = TAB_SIZE;
453 static enum request opt_request         = REQ_VIEW_MAIN;
454 static char opt_cmd[SIZEOF_STR]         = "";
455 static char opt_path[SIZEOF_STR]        = "";
456 static char opt_file[SIZEOF_STR]        = "";
457 static char opt_ref[SIZEOF_REF]         = "";
458 static char opt_head[SIZEOF_REF]        = "";
459 static char opt_remote[SIZEOF_REF]      = "";
460 static bool opt_no_head                 = TRUE;
461 static FILE *opt_pipe                   = NULL;
462 static char opt_encoding[20]            = "UTF-8";
463 static bool opt_utf8                    = TRUE;
464 static char opt_codeset[20]             = "UTF-8";
465 static iconv_t opt_iconv                = ICONV_NONE;
466 static char opt_search[SIZEOF_STR]      = "";
467 static char opt_cdup[SIZEOF_STR]        = "";
468 static char opt_git_dir[SIZEOF_STR]     = "";
469 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
470 static char opt_editor[SIZEOF_STR]      = "";
472 static bool
473 parse_options(int argc, char *argv[])
475         size_t buf_size;
476         char *subcommand;
477         bool seen_dashdash = FALSE;
478         int i;
480         if (!isatty(STDIN_FILENO)) {
481                 opt_request = REQ_VIEW_PAGER;
482                 opt_pipe = stdin;
483                 return TRUE;
484         }
486         if (argc <= 1)
487                 return TRUE;
489         subcommand = argv[1];
490         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
491                 opt_request = REQ_VIEW_STATUS;
492                 if (!strcmp(subcommand, "-S"))
493                         warn("`-S' has been deprecated; use `tig status' instead");
494                 if (argc > 2)
495                         warn("ignoring arguments after `%s'", subcommand);
496                 return TRUE;
498         } else if (!strcmp(subcommand, "blame")) {
499                 opt_request = REQ_VIEW_BLAME;
500                 if (argc <= 2 || argc > 4)
501                         die("invalid number of options to blame\n\n%s", usage);
503                 i = 2;
504                 if (argc == 4) {
505                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
506                         i++;
507                 }
509                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
510                 return TRUE;
512         } else if (!strcmp(subcommand, "show")) {
513                 opt_request = REQ_VIEW_DIFF;
515         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
516                 opt_request = subcommand[0] == 'l'
517                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
518                 warn("`tig %s' has been deprecated", subcommand);
520         } else {
521                 subcommand = NULL;
522         }
524         if (!subcommand)
525                 /* XXX: This is vulnerable to the user overriding
526                  * options required for the main view parser. */
527                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
528         else
529                 string_format(opt_cmd, "git %s", subcommand);
531         buf_size = strlen(opt_cmd);
533         for (i = 1 + !!subcommand; i < argc; i++) {
534                 char *opt = argv[i];
536                 if (seen_dashdash || !strcmp(opt, "--")) {
537                         seen_dashdash = TRUE;
539                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
540                         printf("tig version %s\n", TIG_VERSION);
541                         return FALSE;
543                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
544                         printf("%s\n", usage);
545                         return FALSE;
546                 }
548                 opt_cmd[buf_size++] = ' ';
549                 buf_size = sq_quote(opt_cmd, buf_size, opt);
550                 if (buf_size >= sizeof(opt_cmd))
551                         die("command too long");
552         }
554         opt_cmd[buf_size] = 0;
556         return TRUE;
560 /*
561  * Line-oriented content detection.
562  */
564 #define LINE_INFO \
565 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
566 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
568 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
569 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
570 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
571 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
572 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
573 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
574 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
575 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
576 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
577 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
578 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
579 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
580 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
581 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
582 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
583 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
586 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
587 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
588 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
592 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
593 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
594 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
595 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
596 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
598 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
599 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
600 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
601 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
602 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
603 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
604 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
606 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
607 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
608 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
609 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
611 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
612 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
613 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
615 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
616 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
617 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
618 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
620 enum line_type {
621 #define LINE(type, line, fg, bg, attr) \
622         LINE_##type
623         LINE_INFO,
624         LINE_NONE
625 #undef  LINE
626 };
628 struct line_info {
629         const char *name;       /* Option name. */
630         int namelen;            /* Size of option name. */
631         const char *line;       /* The start of line to match. */
632         int linelen;            /* Size of string to match. */
633         int fg, bg, attr;       /* Color and text attributes for the lines. */
634 };
636 static struct line_info line_info[] = {
637 #define LINE(type, line, fg, bg, attr) \
638         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
639         LINE_INFO
640 #undef  LINE
641 };
643 static enum line_type
644 get_line_type(char *line)
646         int linelen = strlen(line);
647         enum line_type type;
649         for (type = 0; type < ARRAY_SIZE(line_info); type++)
650                 /* Case insensitive search matches Signed-off-by lines better. */
651                 if (linelen >= line_info[type].linelen &&
652                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
653                         return type;
655         return LINE_DEFAULT;
658 static inline int
659 get_line_attr(enum line_type type)
661         assert(type < ARRAY_SIZE(line_info));
662         return COLOR_PAIR(type) | line_info[type].attr;
665 static struct line_info *
666 get_line_info(char *name)
668         size_t namelen = strlen(name);
669         enum line_type type;
671         for (type = 0; type < ARRAY_SIZE(line_info); type++)
672                 if (namelen == line_info[type].namelen &&
673                     !string_enum_compare(line_info[type].name, name, namelen))
674                         return &line_info[type];
676         return NULL;
679 static void
680 init_colors(void)
682         int default_bg = line_info[LINE_DEFAULT].bg;
683         int default_fg = line_info[LINE_DEFAULT].fg;
684         enum line_type type;
686         start_color();
688         if (assume_default_colors(default_fg, default_bg) == ERR) {
689                 default_bg = COLOR_BLACK;
690                 default_fg = COLOR_WHITE;
691         }
693         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
694                 struct line_info *info = &line_info[type];
695                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
696                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
698                 init_pair(type, fg, bg);
699         }
702 struct line {
703         enum line_type type;
705         /* State flags */
706         unsigned int selected:1;
707         unsigned int dirty:1;
709         void *data;             /* User data */
710 };
713 /*
714  * Keys
715  */
717 struct keybinding {
718         int alias;
719         enum request request;
720         struct keybinding *next;
721 };
723 static struct keybinding default_keybindings[] = {
724         /* View switching */
725         { 'm',          REQ_VIEW_MAIN },
726         { 'd',          REQ_VIEW_DIFF },
727         { 'l',          REQ_VIEW_LOG },
728         { 't',          REQ_VIEW_TREE },
729         { 'f',          REQ_VIEW_BLOB },
730         { 'B',          REQ_VIEW_BLAME },
731         { 'p',          REQ_VIEW_PAGER },
732         { 'h',          REQ_VIEW_HELP },
733         { 'S',          REQ_VIEW_STATUS },
734         { 'c',          REQ_VIEW_STAGE },
736         /* View manipulation */
737         { 'q',          REQ_VIEW_CLOSE },
738         { KEY_TAB,      REQ_VIEW_NEXT },
739         { KEY_RETURN,   REQ_ENTER },
740         { KEY_UP,       REQ_PREVIOUS },
741         { KEY_DOWN,     REQ_NEXT },
742         { 'R',          REQ_REFRESH },
743         { KEY_F(5),     REQ_REFRESH },
744         { 'O',          REQ_MAXIMIZE },
746         /* Cursor navigation */
747         { 'k',          REQ_MOVE_UP },
748         { 'j',          REQ_MOVE_DOWN },
749         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
750         { KEY_END,      REQ_MOVE_LAST_LINE },
751         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
752         { ' ',          REQ_MOVE_PAGE_DOWN },
753         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
754         { 'b',          REQ_MOVE_PAGE_UP },
755         { '-',          REQ_MOVE_PAGE_UP },
757         /* Scrolling */
758         { KEY_IC,       REQ_SCROLL_LINE_UP },
759         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
760         { 'w',          REQ_SCROLL_PAGE_UP },
761         { 's',          REQ_SCROLL_PAGE_DOWN },
763         /* Searching */
764         { '/',          REQ_SEARCH },
765         { '?',          REQ_SEARCH_BACK },
766         { 'n',          REQ_FIND_NEXT },
767         { 'N',          REQ_FIND_PREV },
769         /* Misc */
770         { 'Q',          REQ_QUIT },
771         { 'z',          REQ_STOP_LOADING },
772         { 'v',          REQ_SHOW_VERSION },
773         { 'r',          REQ_SCREEN_REDRAW },
774         { '.',          REQ_TOGGLE_LINENO },
775         { 'D',          REQ_TOGGLE_DATE },
776         { 'A',          REQ_TOGGLE_AUTHOR },
777         { 'g',          REQ_TOGGLE_REV_GRAPH },
778         { 'F',          REQ_TOGGLE_REFS },
779         { ':',          REQ_PROMPT },
780         { 'u',          REQ_STATUS_UPDATE },
781         { 'M',          REQ_STATUS_MERGE },
782         { ',',          REQ_TREE_PARENT },
783         { 'e',          REQ_EDIT },
785         /* Using the ncurses SIGWINCH handler. */
786         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
787 };
789 #define KEYMAP_INFO \
790         KEYMAP_(GENERIC), \
791         KEYMAP_(MAIN), \
792         KEYMAP_(DIFF), \
793         KEYMAP_(LOG), \
794         KEYMAP_(TREE), \
795         KEYMAP_(BLOB), \
796         KEYMAP_(BLAME), \
797         KEYMAP_(PAGER), \
798         KEYMAP_(HELP), \
799         KEYMAP_(STATUS), \
800         KEYMAP_(STAGE)
802 enum keymap {
803 #define KEYMAP_(name) KEYMAP_##name
804         KEYMAP_INFO
805 #undef  KEYMAP_
806 };
808 static struct int_map keymap_table[] = {
809 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
810         KEYMAP_INFO
811 #undef  KEYMAP_
812 };
814 #define set_keymap(map, name) \
815         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
817 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
819 static void
820 add_keybinding(enum keymap keymap, enum request request, int key)
822         struct keybinding *keybinding;
824         keybinding = calloc(1, sizeof(*keybinding));
825         if (!keybinding)
826                 die("Failed to allocate keybinding");
828         keybinding->alias = key;
829         keybinding->request = request;
830         keybinding->next = keybindings[keymap];
831         keybindings[keymap] = keybinding;
834 /* Looks for a key binding first in the given map, then in the generic map, and
835  * lastly in the default keybindings. */
836 static enum request
837 get_keybinding(enum keymap keymap, int key)
839         struct keybinding *kbd;
840         int i;
842         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
843                 if (kbd->alias == key)
844                         return kbd->request;
846         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
847                 if (kbd->alias == key)
848                         return kbd->request;
850         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
851                 if (default_keybindings[i].alias == key)
852                         return default_keybindings[i].request;
854         return (enum request) key;
858 struct key {
859         char *name;
860         int value;
861 };
863 static struct key key_table[] = {
864         { "Enter",      KEY_RETURN },
865         { "Space",      ' ' },
866         { "Backspace",  KEY_BACKSPACE },
867         { "Tab",        KEY_TAB },
868         { "Escape",     KEY_ESC },
869         { "Left",       KEY_LEFT },
870         { "Right",      KEY_RIGHT },
871         { "Up",         KEY_UP },
872         { "Down",       KEY_DOWN },
873         { "Insert",     KEY_IC },
874         { "Delete",     KEY_DC },
875         { "Hash",       '#' },
876         { "Home",       KEY_HOME },
877         { "End",        KEY_END },
878         { "PageUp",     KEY_PPAGE },
879         { "PageDown",   KEY_NPAGE },
880         { "F1",         KEY_F(1) },
881         { "F2",         KEY_F(2) },
882         { "F3",         KEY_F(3) },
883         { "F4",         KEY_F(4) },
884         { "F5",         KEY_F(5) },
885         { "F6",         KEY_F(6) },
886         { "F7",         KEY_F(7) },
887         { "F8",         KEY_F(8) },
888         { "F9",         KEY_F(9) },
889         { "F10",        KEY_F(10) },
890         { "F11",        KEY_F(11) },
891         { "F12",        KEY_F(12) },
892 };
894 static int
895 get_key_value(const char *name)
897         int i;
899         for (i = 0; i < ARRAY_SIZE(key_table); i++)
900                 if (!strcasecmp(key_table[i].name, name))
901                         return key_table[i].value;
903         if (strlen(name) == 1 && isprint(*name))
904                 return (int) *name;
906         return ERR;
909 static char *
910 get_key_name(int key_value)
912         static char key_char[] = "'X'";
913         char *seq = NULL;
914         int key;
916         for (key = 0; key < ARRAY_SIZE(key_table); key++)
917                 if (key_table[key].value == key_value)
918                         seq = key_table[key].name;
920         if (seq == NULL &&
921             key_value < 127 &&
922             isprint(key_value)) {
923                 key_char[1] = (char) key_value;
924                 seq = key_char;
925         }
927         return seq ? seq : "'?'";
930 static char *
931 get_key(enum request request)
933         static char buf[BUFSIZ];
934         size_t pos = 0;
935         char *sep = "";
936         int i;
938         buf[pos] = 0;
940         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
941                 struct keybinding *keybinding = &default_keybindings[i];
943                 if (keybinding->request != request)
944                         continue;
946                 if (!string_format_from(buf, &pos, "%s%s", sep,
947                                         get_key_name(keybinding->alias)))
948                         return "Too many keybindings!";
949                 sep = ", ";
950         }
952         return buf;
955 struct run_request {
956         enum keymap keymap;
957         int key;
958         char cmd[SIZEOF_STR];
959 };
961 static struct run_request *run_request;
962 static size_t run_requests;
964 static enum request
965 add_run_request(enum keymap keymap, int key, int argc, char **argv)
967         struct run_request *req;
968         char cmd[SIZEOF_STR];
969         size_t bufpos;
971         for (bufpos = 0; argc > 0; argc--, argv++)
972                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
973                         return REQ_NONE;
975         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
976         if (!req)
977                 return REQ_NONE;
979         run_request = req;
980         req = &run_request[run_requests++];
981         string_copy(req->cmd, cmd);
982         req->keymap = keymap;
983         req->key = key;
985         return REQ_NONE + run_requests;
988 static struct run_request *
989 get_run_request(enum request request)
991         if (request <= REQ_NONE)
992                 return NULL;
993         return &run_request[request - REQ_NONE - 1];
996 static void
997 add_builtin_run_requests(void)
999         struct {
1000                 enum keymap keymap;
1001                 int key;
1002                 char *argv[1];
1003         } reqs[] = {
1004                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1005                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1006         };
1007         int i;
1009         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1010                 enum request req;
1012                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1013                 if (req != REQ_NONE)
1014                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1015         }
1018 /*
1019  * User config file handling.
1020  */
1022 static struct int_map color_map[] = {
1023 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1024         COLOR_MAP(DEFAULT),
1025         COLOR_MAP(BLACK),
1026         COLOR_MAP(BLUE),
1027         COLOR_MAP(CYAN),
1028         COLOR_MAP(GREEN),
1029         COLOR_MAP(MAGENTA),
1030         COLOR_MAP(RED),
1031         COLOR_MAP(WHITE),
1032         COLOR_MAP(YELLOW),
1033 };
1035 #define set_color(color, name) \
1036         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1038 static struct int_map attr_map[] = {
1039 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1040         ATTR_MAP(NORMAL),
1041         ATTR_MAP(BLINK),
1042         ATTR_MAP(BOLD),
1043         ATTR_MAP(DIM),
1044         ATTR_MAP(REVERSE),
1045         ATTR_MAP(STANDOUT),
1046         ATTR_MAP(UNDERLINE),
1047 };
1049 #define set_attribute(attr, name) \
1050         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1052 static int   config_lineno;
1053 static bool  config_errors;
1054 static char *config_msg;
1056 /* Wants: object fgcolor bgcolor [attr] */
1057 static int
1058 option_color_command(int argc, char *argv[])
1060         struct line_info *info;
1062         if (argc != 3 && argc != 4) {
1063                 config_msg = "Wrong number of arguments given to color command";
1064                 return ERR;
1065         }
1067         info = get_line_info(argv[0]);
1068         if (!info) {
1069                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1070                         info = get_line_info("delimiter");
1072                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1073                         info = get_line_info("date");
1075                 } else {
1076                         config_msg = "Unknown color name";
1077                         return ERR;
1078                 }
1079         }
1081         if (set_color(&info->fg, argv[1]) == ERR ||
1082             set_color(&info->bg, argv[2]) == ERR) {
1083                 config_msg = "Unknown color";
1084                 return ERR;
1085         }
1087         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1088                 config_msg = "Unknown attribute";
1089                 return ERR;
1090         }
1092         return OK;
1095 static bool parse_bool(const char *s)
1097         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1098                 !strcmp(s, "yes")) ? TRUE : FALSE;
1101 /* Wants: name = value */
1102 static int
1103 option_set_command(int argc, char *argv[])
1105         if (argc != 3) {
1106                 config_msg = "Wrong number of arguments given to set command";
1107                 return ERR;
1108         }
1110         if (strcmp(argv[1], "=")) {
1111                 config_msg = "No value assigned";
1112                 return ERR;
1113         }
1115         if (!strcmp(argv[0], "show-author")) {
1116                 opt_author = parse_bool(argv[2]);
1117                 return OK;
1118         }
1120         if (!strcmp(argv[0], "show-date")) {
1121                 opt_date = parse_bool(argv[2]);
1122                 return OK;
1123         }
1125         if (!strcmp(argv[0], "show-rev-graph")) {
1126                 opt_rev_graph = parse_bool(argv[2]);
1127                 return OK;
1128         }
1130         if (!strcmp(argv[0], "show-refs")) {
1131                 opt_show_refs = parse_bool(argv[2]);
1132                 return OK;
1133         }
1135         if (!strcmp(argv[0], "show-line-numbers")) {
1136                 opt_line_number = parse_bool(argv[2]);
1137                 return OK;
1138         }
1140         if (!strcmp(argv[0], "line-graphics")) {
1141                 opt_line_graphics = parse_bool(argv[2]);
1142                 return OK;
1143         }
1145         if (!strcmp(argv[0], "line-number-interval")) {
1146                 opt_num_interval = atoi(argv[2]);
1147                 return OK;
1148         }
1150         if (!strcmp(argv[0], "tab-size")) {
1151                 opt_tab_size = atoi(argv[2]);
1152                 return OK;
1153         }
1155         if (!strcmp(argv[0], "commit-encoding")) {
1156                 char *arg = argv[2];
1157                 int delimiter = *arg;
1158                 int i;
1160                 switch (delimiter) {
1161                 case '"':
1162                 case '\'':
1163                         for (arg++, i = 0; arg[i]; i++)
1164                                 if (arg[i] == delimiter) {
1165                                         arg[i] = 0;
1166                                         break;
1167                                 }
1168                 default:
1169                         string_ncopy(opt_encoding, arg, strlen(arg));
1170                         return OK;
1171                 }
1172         }
1174         config_msg = "Unknown variable name";
1175         return ERR;
1178 /* Wants: mode request key */
1179 static int
1180 option_bind_command(int argc, char *argv[])
1182         enum request request;
1183         int keymap;
1184         int key;
1186         if (argc < 3) {
1187                 config_msg = "Wrong number of arguments given to bind command";
1188                 return ERR;
1189         }
1191         if (set_keymap(&keymap, argv[0]) == ERR) {
1192                 config_msg = "Unknown key map";
1193                 return ERR;
1194         }
1196         key = get_key_value(argv[1]);
1197         if (key == ERR) {
1198                 config_msg = "Unknown key";
1199                 return ERR;
1200         }
1202         request = get_request(argv[2]);
1203         if (request == REQ_NONE) {
1204                 const char *obsolete[] = { "cherry-pick" };
1205                 size_t namelen = strlen(argv[2]);
1206                 int i;
1208                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1209                         if (namelen == strlen(obsolete[i]) &&
1210                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1211                                 config_msg = "Obsolete request name";
1212                                 return ERR;
1213                         }
1214                 }
1215         }
1216         if (request == REQ_NONE && *argv[2]++ == '!')
1217                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1218         if (request == REQ_NONE) {
1219                 config_msg = "Unknown request name";
1220                 return ERR;
1221         }
1223         add_keybinding(keymap, request, key);
1225         return OK;
1228 static int
1229 set_option(char *opt, char *value)
1231         char *argv[16];
1232         int valuelen;
1233         int argc = 0;
1235         /* Tokenize */
1236         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1237                 argv[argc++] = value;
1238                 value += valuelen;
1240                 /* Nothing more to tokenize or last available token. */
1241                 if (!*value || argc >= ARRAY_SIZE(argv))
1242                         break;
1244                 *value++ = 0;
1245                 while (isspace(*value))
1246                         value++;
1247         }
1249         if (!strcmp(opt, "color"))
1250                 return option_color_command(argc, argv);
1252         if (!strcmp(opt, "set"))
1253                 return option_set_command(argc, argv);
1255         if (!strcmp(opt, "bind"))
1256                 return option_bind_command(argc, argv);
1258         config_msg = "Unknown option command";
1259         return ERR;
1262 static int
1263 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1265         int status = OK;
1267         config_lineno++;
1268         config_msg = "Internal error";
1270         /* Check for comment markers, since read_properties() will
1271          * only ensure opt and value are split at first " \t". */
1272         optlen = strcspn(opt, "#");
1273         if (optlen == 0)
1274                 return OK;
1276         if (opt[optlen] != 0) {
1277                 config_msg = "No option value";
1278                 status = ERR;
1280         }  else {
1281                 /* Look for comment endings in the value. */
1282                 size_t len = strcspn(value, "#");
1284                 if (len < valuelen) {
1285                         valuelen = len;
1286                         value[valuelen] = 0;
1287                 }
1289                 status = set_option(opt, value);
1290         }
1292         if (status == ERR) {
1293                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1294                         config_lineno, (int) optlen, opt, config_msg);
1295                 config_errors = TRUE;
1296         }
1298         /* Always keep going if errors are encountered. */
1299         return OK;
1302 static void
1303 load_option_file(const char *path)
1305         FILE *file;
1307         /* It's ok that the file doesn't exist. */
1308         file = fopen(path, "r");
1309         if (!file)
1310                 return;
1312         config_lineno = 0;
1313         config_errors = FALSE;
1315         if (read_properties(file, " \t", read_option) == ERR ||
1316             config_errors == TRUE)
1317                 fprintf(stderr, "Errors while loading %s.\n", path);
1320 static int
1321 load_options(void)
1323         char *home = getenv("HOME");
1324         char *tigrc_user = getenv("TIGRC_USER");
1325         char *tigrc_system = getenv("TIGRC_SYSTEM");
1326         char buf[SIZEOF_STR];
1328         add_builtin_run_requests();
1330         if (!tigrc_system) {
1331                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1332                         return ERR;
1333                 tigrc_system = buf;
1334         }
1335         load_option_file(tigrc_system);
1337         if (!tigrc_user) {
1338                 if (!home || !string_format(buf, "%s/.tigrc", home))
1339                         return ERR;
1340                 tigrc_user = buf;
1341         }
1342         load_option_file(tigrc_user);
1344         return OK;
1348 /*
1349  * The viewer
1350  */
1352 struct view;
1353 struct view_ops;
1355 /* The display array of active views and the index of the current view. */
1356 static struct view *display[2];
1357 static unsigned int current_view;
1359 /* Reading from the prompt? */
1360 static bool input_mode = FALSE;
1362 #define foreach_displayed_view(view, i) \
1363         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1365 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1367 /* Current head and commit ID */
1368 static char ref_blob[SIZEOF_REF]        = "";
1369 static char ref_commit[SIZEOF_REF]      = "HEAD";
1370 static char ref_head[SIZEOF_REF]        = "HEAD";
1372 struct view {
1373         const char *name;       /* View name */
1374         const char *cmd_fmt;    /* Default command line format */
1375         const char *cmd_env;    /* Command line set via environment */
1376         const char *id;         /* Points to either of ref_{head,commit,blob} */
1378         struct view_ops *ops;   /* View operations */
1380         enum keymap keymap;     /* What keymap does this view have */
1381         bool git_dir;           /* Whether the view requires a git directory. */
1383         char cmd[SIZEOF_STR];   /* Command buffer */
1384         char ref[SIZEOF_REF];   /* Hovered commit reference */
1385         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1387         int height, width;      /* The width and height of the main window */
1388         WINDOW *win;            /* The main window */
1389         WINDOW *title;          /* The title window living below the main window */
1391         /* Navigation */
1392         unsigned long offset;   /* Offset of the window top */
1393         unsigned long lineno;   /* Current line number */
1395         /* Searching */
1396         char grep[SIZEOF_STR];  /* Search string */
1397         regex_t *regex;         /* Pre-compiled regex */
1399         /* If non-NULL, points to the view that opened this view. If this view
1400          * is closed tig will switch back to the parent view. */
1401         struct view *parent;
1403         /* Buffering */
1404         size_t lines;           /* Total number of lines */
1405         struct line *line;      /* Line index */
1406         size_t line_alloc;      /* Total number of allocated lines */
1407         size_t line_size;       /* Total number of used lines */
1408         unsigned int digits;    /* Number of digits in the lines member. */
1410         /* Drawing */
1411         struct line *curline;   /* Line currently being drawn. */
1412         enum line_type curtype; /* Attribute currently used for drawing. */
1413         unsigned long col;      /* Column when drawing. */
1415         /* Loading */
1416         FILE *pipe;
1417         time_t start_time;
1418 };
1420 struct view_ops {
1421         /* What type of content being displayed. Used in the title bar. */
1422         const char *type;
1423         /* Open and reads in all view content. */
1424         bool (*open)(struct view *view);
1425         /* Read one line; updates view->line. */
1426         bool (*read)(struct view *view, char *data);
1427         /* Draw one line; @lineno must be < view->height. */
1428         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1429         /* Depending on view handle a special requests. */
1430         enum request (*request)(struct view *view, enum request request, struct line *line);
1431         /* Search for regex in a line. */
1432         bool (*grep)(struct view *view, struct line *line);
1433         /* Select line */
1434         void (*select)(struct view *view, struct line *line);
1435 };
1437 static struct view_ops pager_ops;
1438 static struct view_ops main_ops;
1439 static struct view_ops tree_ops;
1440 static struct view_ops blob_ops;
1441 static struct view_ops blame_ops;
1442 static struct view_ops help_ops;
1443 static struct view_ops status_ops;
1444 static struct view_ops stage_ops;
1446 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1447         { name, cmd, #env, ref, ops, map, git }
1449 #define VIEW_(id, name, ops, git, ref) \
1450         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1453 static struct view views[] = {
1454         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1455         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1456         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1457         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1458         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1459         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1460         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1461         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1462         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1463         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1464 };
1466 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1467 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1469 #define foreach_view(view, i) \
1470         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1472 #define view_is_displayed(view) \
1473         (view == display[0] || view == display[1])
1476 enum line_graphic {
1477         LINE_GRAPHIC_VLINE
1478 };
1480 static int line_graphics[] = {
1481         /* LINE_GRAPHIC_VLINE: */ '|'
1482 };
1484 static inline void
1485 set_view_attr(struct view *view, enum line_type type)
1487         if (!view->curline->selected && view->curtype != type) {
1488                 wattrset(view->win, get_line_attr(type));
1489                 wchgat(view->win, -1, 0, type, NULL);
1490                 view->curtype = type;
1491         }
1494 static int
1495 draw_chars(struct view *view, enum line_type type, const char *string,
1496            int max_len, bool use_tilde)
1498         int len = 0;
1499         int col = 0;
1500         int trimmed = FALSE;
1502         if (max_len <= 0)
1503                 return 0;
1505         if (opt_utf8) {
1506                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1507         } else {
1508                 col = len = strlen(string);
1509                 if (len > max_len) {
1510                         if (use_tilde) {
1511                                 max_len -= 1;
1512                         }
1513                         col = len = max_len;
1514                         trimmed = TRUE;
1515                 }
1516         }
1518         set_view_attr(view, type);
1519         waddnstr(view->win, string, len);
1520         if (trimmed && use_tilde) {
1521                 set_view_attr(view, LINE_DELIMITER);
1522                 waddch(view->win, '~');
1523                 col++;
1524         }
1526         return col;
1529 static int
1530 draw_space(struct view *view, enum line_type type, int max, int spaces)
1532         static char space[] = "                    ";
1533         int col = 0;
1535         spaces = MIN(max, spaces);
1537         while (spaces > 0) {
1538                 int len = MIN(spaces, sizeof(space) - 1);
1540                 col += draw_chars(view, type, space, spaces, FALSE);
1541                 spaces -= len;
1542         }
1544         return col;
1547 static bool
1548 draw_lineno(struct view *view, unsigned int lineno)
1550         char number[10];
1551         int digits3 = view->digits < 3 ? 3 : view->digits;
1552         int max_number = MIN(digits3, STRING_SIZE(number));
1553         int max = view->width - view->col;
1554         int col;
1556         if (max < max_number)
1557                 max_number = max;
1559         lineno += view->offset + 1;
1560         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1561                 static char fmt[] = "%1ld";
1563                 if (view->digits <= 9)
1564                         fmt[1] = '0' + digits3;
1566                 if (!string_format(number, fmt, lineno))
1567                         number[0] = 0;
1568                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1569         } else {
1570                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1571         }
1573         if (col < max) {
1574                 set_view_attr(view, LINE_DEFAULT);
1575                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1576                 col++;
1577         }
1579         if (col < max)
1580                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1581         view->col += col;
1583         return view->width - view->col <= 0;
1586 static bool
1587 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1589         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1590         return view->width - view->col <= 0;
1593 static bool
1594 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1596         int max = view->width - view->col;
1597         int i;
1599         if (max < size)
1600                 size = max;
1602         set_view_attr(view, type);
1603         /* Using waddch() instead of waddnstr() ensures that
1604          * they'll be rendered correctly for the cursor line. */
1605         for (i = 0; i < size; i++)
1606                 waddch(view->win, graphic[i]);
1608         view->col += size;
1609         if (size < max) {
1610                 waddch(view->win, ' ');
1611                 view->col++;
1612         }
1614         return view->width - view->col <= 0;
1617 static bool
1618 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1620         int max = MIN(view->width - view->col, len);
1621         int col;
1623         if (text)
1624                 col = draw_chars(view, type, text, max - 1, trim);
1625         else
1626                 col = draw_space(view, type, max - 1, max - 1);
1628         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1629         return view->width - view->col <= 0;
1632 static bool
1633 draw_date(struct view *view, struct tm *time)
1635         char buf[DATE_COLS];
1636         char *date;
1637         int timelen = 0;
1639         if (time)
1640                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1641         date = timelen ? buf : NULL;
1643         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1646 static bool
1647 draw_view_line(struct view *view, unsigned int lineno)
1649         struct line *line;
1650         bool selected = (view->offset + lineno == view->lineno);
1651         bool draw_ok;
1653         assert(view_is_displayed(view));
1655         if (view->offset + lineno >= view->lines)
1656                 return FALSE;
1658         line = &view->line[view->offset + lineno];
1660         wmove(view->win, lineno, 0);
1661         view->col = 0;
1662         view->curline = line;
1663         view->curtype = LINE_NONE;
1664         line->selected = FALSE;
1666         if (selected) {
1667                 set_view_attr(view, LINE_CURSOR);
1668                 line->selected = TRUE;
1669                 view->ops->select(view, line);
1670         } else if (line->selected) {
1671                 wclrtoeol(view->win);
1672         }
1674         scrollok(view->win, FALSE);
1675         draw_ok = view->ops->draw(view, line, lineno);
1676         scrollok(view->win, TRUE);
1678         return draw_ok;
1681 static void
1682 redraw_view_dirty(struct view *view)
1684         bool dirty = FALSE;
1685         int lineno;
1687         for (lineno = 0; lineno < view->height; lineno++) {
1688                 struct line *line = &view->line[view->offset + lineno];
1690                 if (!line->dirty)
1691                         continue;
1692                 line->dirty = 0;
1693                 dirty = TRUE;
1694                 if (!draw_view_line(view, lineno))
1695                         break;
1696         }
1698         if (!dirty)
1699                 return;
1700         redrawwin(view->win);
1701         if (input_mode)
1702                 wnoutrefresh(view->win);
1703         else
1704                 wrefresh(view->win);
1707 static void
1708 redraw_view_from(struct view *view, int lineno)
1710         assert(0 <= lineno && lineno < view->height);
1712         for (; lineno < view->height; lineno++) {
1713                 if (!draw_view_line(view, lineno))
1714                         break;
1715         }
1717         redrawwin(view->win);
1718         if (input_mode)
1719                 wnoutrefresh(view->win);
1720         else
1721                 wrefresh(view->win);
1724 static void
1725 redraw_view(struct view *view)
1727         wclear(view->win);
1728         redraw_view_from(view, 0);
1732 static void
1733 update_view_title(struct view *view)
1735         char buf[SIZEOF_STR];
1736         char state[SIZEOF_STR];
1737         size_t bufpos = 0, statelen = 0;
1739         assert(view_is_displayed(view));
1741         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1742                 unsigned int view_lines = view->offset + view->height;
1743                 unsigned int lines = view->lines
1744                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1745                                    : 0;
1747                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1748                                    view->ops->type,
1749                                    view->lineno + 1,
1750                                    view->lines,
1751                                    lines);
1753                 if (view->pipe) {
1754                         time_t secs = time(NULL) - view->start_time;
1756                         /* Three git seconds are a long time ... */
1757                         if (secs > 2)
1758                                 string_format_from(state, &statelen, " %lds", secs);
1759                 }
1760         }
1762         string_format_from(buf, &bufpos, "[%s]", view->name);
1763         if (*view->ref && bufpos < view->width) {
1764                 size_t refsize = strlen(view->ref);
1765                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1767                 if (minsize < view->width)
1768                         refsize = view->width - minsize + 7;
1769                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1770         }
1772         if (statelen && bufpos < view->width) {
1773                 string_format_from(buf, &bufpos, " %s", state);
1774         }
1776         if (view == display[current_view])
1777                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1778         else
1779                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1781         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1782         wclrtoeol(view->title);
1783         wmove(view->title, 0, view->width - 1);
1785         if (input_mode)
1786                 wnoutrefresh(view->title);
1787         else
1788                 wrefresh(view->title);
1791 static void
1792 resize_display(void)
1794         int offset, i;
1795         struct view *base = display[0];
1796         struct view *view = display[1] ? display[1] : display[0];
1798         /* Setup window dimensions */
1800         getmaxyx(stdscr, base->height, base->width);
1802         /* Make room for the status window. */
1803         base->height -= 1;
1805         if (view != base) {
1806                 /* Horizontal split. */
1807                 view->width   = base->width;
1808                 view->height  = SCALE_SPLIT_VIEW(base->height);
1809                 base->height -= view->height;
1811                 /* Make room for the title bar. */
1812                 view->height -= 1;
1813         }
1815         /* Make room for the title bar. */
1816         base->height -= 1;
1818         offset = 0;
1820         foreach_displayed_view (view, i) {
1821                 if (!view->win) {
1822                         view->win = newwin(view->height, 0, offset, 0);
1823                         if (!view->win)
1824                                 die("Failed to create %s view", view->name);
1826                         scrollok(view->win, TRUE);
1828                         view->title = newwin(1, 0, offset + view->height, 0);
1829                         if (!view->title)
1830                                 die("Failed to create title window");
1832                 } else {
1833                         wresize(view->win, view->height, view->width);
1834                         mvwin(view->win,   offset, 0);
1835                         mvwin(view->title, offset + view->height, 0);
1836                 }
1838                 offset += view->height + 1;
1839         }
1842 static void
1843 redraw_display(void)
1845         struct view *view;
1846         int i;
1848         foreach_displayed_view (view, i) {
1849                 redraw_view(view);
1850                 update_view_title(view);
1851         }
1854 static void
1855 update_display_cursor(struct view *view)
1857         /* Move the cursor to the right-most column of the cursor line.
1858          *
1859          * XXX: This could turn out to be a bit expensive, but it ensures that
1860          * the cursor does not jump around. */
1861         if (view->lines) {
1862                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1863                 wrefresh(view->win);
1864         }
1867 /*
1868  * Navigation
1869  */
1871 /* Scrolling backend */
1872 static void
1873 do_scroll_view(struct view *view, int lines)
1875         bool redraw_current_line = FALSE;
1877         /* The rendering expects the new offset. */
1878         view->offset += lines;
1880         assert(0 <= view->offset && view->offset < view->lines);
1881         assert(lines);
1883         /* Move current line into the view. */
1884         if (view->lineno < view->offset) {
1885                 view->lineno = view->offset;
1886                 redraw_current_line = TRUE;
1887         } else if (view->lineno >= view->offset + view->height) {
1888                 view->lineno = view->offset + view->height - 1;
1889                 redraw_current_line = TRUE;
1890         }
1892         assert(view->offset <= view->lineno && view->lineno < view->lines);
1894         /* Redraw the whole screen if scrolling is pointless. */
1895         if (view->height < ABS(lines)) {
1896                 redraw_view(view);
1898         } else {
1899                 int line = lines > 0 ? view->height - lines : 0;
1900                 int end = line + ABS(lines);
1902                 wscrl(view->win, lines);
1904                 for (; line < end; line++) {
1905                         if (!draw_view_line(view, line))
1906                                 break;
1907                 }
1909                 if (redraw_current_line)
1910                         draw_view_line(view, view->lineno - view->offset);
1911         }
1913         redrawwin(view->win);
1914         wrefresh(view->win);
1915         report("");
1918 /* Scroll frontend */
1919 static void
1920 scroll_view(struct view *view, enum request request)
1922         int lines = 1;
1924         assert(view_is_displayed(view));
1926         switch (request) {
1927         case REQ_SCROLL_PAGE_DOWN:
1928                 lines = view->height;
1929         case REQ_SCROLL_LINE_DOWN:
1930                 if (view->offset + lines > view->lines)
1931                         lines = view->lines - view->offset;
1933                 if (lines == 0 || view->offset + view->height >= view->lines) {
1934                         report("Cannot scroll beyond the last line");
1935                         return;
1936                 }
1937                 break;
1939         case REQ_SCROLL_PAGE_UP:
1940                 lines = view->height;
1941         case REQ_SCROLL_LINE_UP:
1942                 if (lines > view->offset)
1943                         lines = view->offset;
1945                 if (lines == 0) {
1946                         report("Cannot scroll beyond the first line");
1947                         return;
1948                 }
1950                 lines = -lines;
1951                 break;
1953         default:
1954                 die("request %d not handled in switch", request);
1955         }
1957         do_scroll_view(view, lines);
1960 /* Cursor moving */
1961 static void
1962 move_view(struct view *view, enum request request)
1964         int scroll_steps = 0;
1965         int steps;
1967         switch (request) {
1968         case REQ_MOVE_FIRST_LINE:
1969                 steps = -view->lineno;
1970                 break;
1972         case REQ_MOVE_LAST_LINE:
1973                 steps = view->lines - view->lineno - 1;
1974                 break;
1976         case REQ_MOVE_PAGE_UP:
1977                 steps = view->height > view->lineno
1978                       ? -view->lineno : -view->height;
1979                 break;
1981         case REQ_MOVE_PAGE_DOWN:
1982                 steps = view->lineno + view->height >= view->lines
1983                       ? view->lines - view->lineno - 1 : view->height;
1984                 break;
1986         case REQ_MOVE_UP:
1987                 steps = -1;
1988                 break;
1990         case REQ_MOVE_DOWN:
1991                 steps = 1;
1992                 break;
1994         default:
1995                 die("request %d not handled in switch", request);
1996         }
1998         if (steps <= 0 && view->lineno == 0) {
1999                 report("Cannot move beyond the first line");
2000                 return;
2002         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2003                 report("Cannot move beyond the last line");
2004                 return;
2005         }
2007         /* Move the current line */
2008         view->lineno += steps;
2009         assert(0 <= view->lineno && view->lineno < view->lines);
2011         /* Check whether the view needs to be scrolled */
2012         if (view->lineno < view->offset ||
2013             view->lineno >= view->offset + view->height) {
2014                 scroll_steps = steps;
2015                 if (steps < 0 && -steps > view->offset) {
2016                         scroll_steps = -view->offset;
2018                 } else if (steps > 0) {
2019                         if (view->lineno == view->lines - 1 &&
2020                             view->lines > view->height) {
2021                                 scroll_steps = view->lines - view->offset - 1;
2022                                 if (scroll_steps >= view->height)
2023                                         scroll_steps -= view->height - 1;
2024                         }
2025                 }
2026         }
2028         if (!view_is_displayed(view)) {
2029                 view->offset += scroll_steps;
2030                 assert(0 <= view->offset && view->offset < view->lines);
2031                 view->ops->select(view, &view->line[view->lineno]);
2032                 return;
2033         }
2035         /* Repaint the old "current" line if we be scrolling */
2036         if (ABS(steps) < view->height)
2037                 draw_view_line(view, view->lineno - steps - view->offset);
2039         if (scroll_steps) {
2040                 do_scroll_view(view, scroll_steps);
2041                 return;
2042         }
2044         /* Draw the current line */
2045         draw_view_line(view, view->lineno - view->offset);
2047         redrawwin(view->win);
2048         wrefresh(view->win);
2049         report("");
2053 /*
2054  * Searching
2055  */
2057 static void search_view(struct view *view, enum request request);
2059 static bool
2060 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2062         assert(view_is_displayed(view));
2064         if (!view->ops->grep(view, line))
2065                 return FALSE;
2067         if (lineno - view->offset >= view->height) {
2068                 view->offset = lineno;
2069                 view->lineno = lineno;
2070                 redraw_view(view);
2072         } else {
2073                 unsigned long old_lineno = view->lineno - view->offset;
2075                 view->lineno = lineno;
2076                 draw_view_line(view, old_lineno);
2078                 draw_view_line(view, view->lineno - view->offset);
2079                 redrawwin(view->win);
2080                 wrefresh(view->win);
2081         }
2083         report("Line %ld matches '%s'", lineno + 1, view->grep);
2084         return TRUE;
2087 static void
2088 find_next(struct view *view, enum request request)
2090         unsigned long lineno = view->lineno;
2091         int direction;
2093         if (!*view->grep) {
2094                 if (!*opt_search)
2095                         report("No previous search");
2096                 else
2097                         search_view(view, request);
2098                 return;
2099         }
2101         switch (request) {
2102         case REQ_SEARCH:
2103         case REQ_FIND_NEXT:
2104                 direction = 1;
2105                 break;
2107         case REQ_SEARCH_BACK:
2108         case REQ_FIND_PREV:
2109                 direction = -1;
2110                 break;
2112         default:
2113                 return;
2114         }
2116         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2117                 lineno += direction;
2119         /* Note, lineno is unsigned long so will wrap around in which case it
2120          * will become bigger than view->lines. */
2121         for (; lineno < view->lines; lineno += direction) {
2122                 struct line *line = &view->line[lineno];
2124                 if (find_next_line(view, lineno, line))
2125                         return;
2126         }
2128         report("No match found for '%s'", view->grep);
2131 static void
2132 search_view(struct view *view, enum request request)
2134         int regex_err;
2136         if (view->regex) {
2137                 regfree(view->regex);
2138                 *view->grep = 0;
2139         } else {
2140                 view->regex = calloc(1, sizeof(*view->regex));
2141                 if (!view->regex)
2142                         return;
2143         }
2145         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2146         if (regex_err != 0) {
2147                 char buf[SIZEOF_STR] = "unknown error";
2149                 regerror(regex_err, view->regex, buf, sizeof(buf));
2150                 report("Search failed: %s", buf);
2151                 return;
2152         }
2154         string_copy(view->grep, opt_search);
2156         find_next(view, request);
2159 /*
2160  * Incremental updating
2161  */
2163 static void
2164 end_update(struct view *view)
2166         if (!view->pipe)
2167                 return;
2168         set_nonblocking_input(FALSE);
2169         if (view->pipe == stdin)
2170                 fclose(view->pipe);
2171         else
2172                 pclose(view->pipe);
2173         view->pipe = NULL;
2176 static bool
2177 begin_update(struct view *view)
2179         if (view->pipe)
2180                 end_update(view);
2182         if (opt_cmd[0]) {
2183                 string_copy(view->cmd, opt_cmd);
2184                 opt_cmd[0] = 0;
2185                 /* When running random commands, initially show the
2186                  * command in the title. However, it maybe later be
2187                  * overwritten if a commit line is selected. */
2188                 if (view == VIEW(REQ_VIEW_PAGER))
2189                         string_copy(view->ref, view->cmd);
2190                 else
2191                         view->ref[0] = 0;
2193         } else if (view == VIEW(REQ_VIEW_TREE)) {
2194                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2195                 char path[SIZEOF_STR];
2197                 if (strcmp(view->vid, view->id))
2198                         opt_path[0] = path[0] = 0;
2199                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2200                         return FALSE;
2202                 if (!string_format(view->cmd, format, view->id, path))
2203                         return FALSE;
2205         } else {
2206                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2207                 const char *id = view->id;
2209                 if (!string_format(view->cmd, format, id, id, id, id, id))
2210                         return FALSE;
2212                 /* Put the current ref_* value to the view title ref
2213                  * member. This is needed by the blob view. Most other
2214                  * views sets it automatically after loading because the
2215                  * first line is a commit line. */
2216                 string_copy_rev(view->ref, view->id);
2217         }
2219         /* Special case for the pager view. */
2220         if (opt_pipe) {
2221                 view->pipe = opt_pipe;
2222                 opt_pipe = NULL;
2223         } else {
2224                 view->pipe = popen(view->cmd, "r");
2225         }
2227         if (!view->pipe)
2228                 return FALSE;
2230         set_nonblocking_input(TRUE);
2232         view->offset = 0;
2233         view->lines  = 0;
2234         view->lineno = 0;
2235         string_copy_rev(view->vid, view->id);
2237         if (view->line) {
2238                 int i;
2240                 for (i = 0; i < view->lines; i++)
2241                         if (view->line[i].data)
2242                                 free(view->line[i].data);
2244                 free(view->line);
2245                 view->line = NULL;
2246         }
2248         view->start_time = time(NULL);
2250         return TRUE;
2253 #define ITEM_CHUNK_SIZE 256
2254 static void *
2255 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2257         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2258         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2260         if (mem == NULL || num_chunks != num_chunks_new) {
2261                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2262                 mem = realloc(mem, *size * item_size);
2263         }
2265         return mem;
2268 static struct line *
2269 realloc_lines(struct view *view, size_t line_size)
2271         size_t alloc = view->line_alloc;
2272         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2273                                          sizeof(*view->line));
2275         if (!tmp)
2276                 return NULL;
2278         view->line = tmp;
2279         view->line_alloc = alloc;
2280         view->line_size = line_size;
2281         return view->line;
2284 static bool
2285 update_view(struct view *view)
2287         char in_buffer[BUFSIZ];
2288         char out_buffer[BUFSIZ * 2];
2289         char *line;
2290         /* The number of lines to read. If too low it will cause too much
2291          * redrawing (and possible flickering), if too high responsiveness
2292          * will suffer. */
2293         unsigned long lines = view->height;
2294         int redraw_from = -1;
2296         if (!view->pipe)
2297                 return TRUE;
2299         /* Only redraw if lines are visible. */
2300         if (view->offset + view->height >= view->lines)
2301                 redraw_from = view->lines - view->offset;
2303         /* FIXME: This is probably not perfect for backgrounded views. */
2304         if (!realloc_lines(view, view->lines + lines))
2305                 goto alloc_error;
2307         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2308                 size_t linelen = strlen(line);
2310                 if (linelen)
2311                         line[linelen - 1] = 0;
2313                 if (opt_iconv != ICONV_NONE) {
2314                         ICONV_CONST char *inbuf = line;
2315                         size_t inlen = linelen;
2317                         char *outbuf = out_buffer;
2318                         size_t outlen = sizeof(out_buffer);
2320                         size_t ret;
2322                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2323                         if (ret != (size_t) -1) {
2324                                 line = out_buffer;
2325                                 linelen = strlen(out_buffer);
2326                         }
2327                 }
2329                 if (!view->ops->read(view, line))
2330                         goto alloc_error;
2332                 if (lines-- == 1)
2333                         break;
2334         }
2336         {
2337                 int digits;
2339                 lines = view->lines;
2340                 for (digits = 0; lines; digits++)
2341                         lines /= 10;
2343                 /* Keep the displayed view in sync with line number scaling. */
2344                 if (digits != view->digits) {
2345                         view->digits = digits;
2346                         redraw_from = 0;
2347                 }
2348         }
2350         if (!view_is_displayed(view))
2351                 goto check_pipe;
2353         if (view == VIEW(REQ_VIEW_TREE)) {
2354                 /* Clear the view and redraw everything since the tree sorting
2355                  * might have rearranged things. */
2356                 redraw_view(view);
2358         } else if (redraw_from >= 0) {
2359                 /* If this is an incremental update, redraw the previous line
2360                  * since for commits some members could have changed when
2361                  * loading the main view. */
2362                 if (redraw_from > 0)
2363                         redraw_from--;
2365                 /* Since revision graph visualization requires knowledge
2366                  * about the parent commit, it causes a further one-off
2367                  * needed to be redrawn for incremental updates. */
2368                 if (redraw_from > 0 && opt_rev_graph)
2369                         redraw_from--;
2371                 /* Incrementally draw avoids flickering. */
2372                 redraw_view_from(view, redraw_from);
2373         }
2375         if (view == VIEW(REQ_VIEW_BLAME))
2376                 redraw_view_dirty(view);
2378         /* Update the title _after_ the redraw so that if the redraw picks up a
2379          * commit reference in view->ref it'll be available here. */
2380         update_view_title(view);
2382 check_pipe:
2383         if (ferror(view->pipe)) {
2384                 report("Failed to read: %s", strerror(errno));
2385                 goto end;
2387         } else if (feof(view->pipe)) {
2388                 report("");
2389                 goto end;
2390         }
2392         return TRUE;
2394 alloc_error:
2395         report("Allocation failure");
2397 end:
2398         if (view->ops->read(view, NULL))
2399                 end_update(view);
2400         return FALSE;
2403 static struct line *
2404 add_line_data(struct view *view, void *data, enum line_type type)
2406         struct line *line = &view->line[view->lines++];
2408         memset(line, 0, sizeof(*line));
2409         line->type = type;
2410         line->data = data;
2412         return line;
2415 static struct line *
2416 add_line_text(struct view *view, char *data, enum line_type type)
2418         if (data)
2419                 data = strdup(data);
2421         return data ? add_line_data(view, data, type) : NULL;
2425 /*
2426  * View opening
2427  */
2429 enum open_flags {
2430         OPEN_DEFAULT = 0,       /* Use default view switching. */
2431         OPEN_SPLIT = 1,         /* Split current view. */
2432         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2433         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2434         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2435 };
2437 static void
2438 open_view(struct view *prev, enum request request, enum open_flags flags)
2440         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2441         bool split = !!(flags & OPEN_SPLIT);
2442         bool reload = !!(flags & OPEN_RELOAD);
2443         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2444         struct view *view = VIEW(request);
2445         int nviews = displayed_views();
2446         struct view *base_view = display[0];
2448         if (view == prev && nviews == 1 && !reload) {
2449                 report("Already in %s view", view->name);
2450                 return;
2451         }
2453         if (view->git_dir && !opt_git_dir[0]) {
2454                 report("The %s view is disabled in pager view", view->name);
2455                 return;
2456         }
2458         if (split) {
2459                 display[1] = view;
2460                 if (!backgrounded)
2461                         current_view = 1;
2462         } else if (!nomaximize) {
2463                 /* Maximize the current view. */
2464                 memset(display, 0, sizeof(display));
2465                 current_view = 0;
2466                 display[current_view] = view;
2467         }
2469         /* Resize the view when switching between split- and full-screen,
2470          * or when switching between two different full-screen views. */
2471         if (nviews != displayed_views() ||
2472             (nviews == 1 && base_view != display[0]))
2473                 resize_display();
2475         if (view->ops->open) {
2476                 if (!view->ops->open(view)) {
2477                         report("Failed to load %s view", view->name);
2478                         return;
2479                 }
2481         } else if ((reload || strcmp(view->vid, view->id)) &&
2482                    !begin_update(view)) {
2483                 report("Failed to load %s view", view->name);
2484                 return;
2485         }
2487         if (split && prev->lineno - prev->offset >= prev->height) {
2488                 /* Take the title line into account. */
2489                 int lines = prev->lineno - prev->offset - prev->height + 1;
2491                 /* Scroll the view that was split if the current line is
2492                  * outside the new limited view. */
2493                 do_scroll_view(prev, lines);
2494         }
2496         if (prev && view != prev) {
2497                 if (split && !backgrounded) {
2498                         /* "Blur" the previous view. */
2499                         update_view_title(prev);
2500                 }
2502                 view->parent = prev;
2503         }
2505         if (view->pipe && view->lines == 0) {
2506                 /* Clear the old view and let the incremental updating refill
2507                  * the screen. */
2508                 werase(view->win);
2509                 report("");
2510         } else {
2511                 redraw_view(view);
2512                 report("");
2513         }
2515         /* If the view is backgrounded the above calls to report()
2516          * won't redraw the view title. */
2517         if (backgrounded)
2518                 update_view_title(view);
2521 static void
2522 open_external_viewer(const char *cmd)
2524         def_prog_mode();           /* save current tty modes */
2525         endwin();                  /* restore original tty modes */
2526         system(cmd);
2527         fprintf(stderr, "Press Enter to continue");
2528         getc(stdin);
2529         reset_prog_mode();
2530         redraw_display();
2533 static void
2534 open_mergetool(const char *file)
2536         char cmd[SIZEOF_STR];
2537         char file_sq[SIZEOF_STR];
2539         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2540             string_format(cmd, "git mergetool %s", file_sq)) {
2541                 open_external_viewer(cmd);
2542         }
2545 static void
2546 open_editor(bool from_root, const char *file)
2548         char cmd[SIZEOF_STR];
2549         char file_sq[SIZEOF_STR];
2550         char *editor;
2551         char *prefix = from_root ? opt_cdup : "";
2553         editor = getenv("GIT_EDITOR");
2554         if (!editor && *opt_editor)
2555                 editor = opt_editor;
2556         if (!editor)
2557                 editor = getenv("VISUAL");
2558         if (!editor)
2559                 editor = getenv("EDITOR");
2560         if (!editor)
2561                 editor = "vi";
2563         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2564             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2565                 open_external_viewer(cmd);
2566         }
2569 static void
2570 open_run_request(enum request request)
2572         struct run_request *req = get_run_request(request);
2573         char buf[SIZEOF_STR * 2];
2574         size_t bufpos;
2575         char *cmd;
2577         if (!req) {
2578                 report("Unknown run request");
2579                 return;
2580         }
2582         bufpos = 0;
2583         cmd = req->cmd;
2585         while (cmd) {
2586                 char *next = strstr(cmd, "%(");
2587                 int len = next - cmd;
2588                 char *value;
2590                 if (!next) {
2591                         len = strlen(cmd);
2592                         value = "";
2594                 } else if (!strncmp(next, "%(head)", 7)) {
2595                         value = ref_head;
2597                 } else if (!strncmp(next, "%(commit)", 9)) {
2598                         value = ref_commit;
2600                 } else if (!strncmp(next, "%(blob)", 7)) {
2601                         value = ref_blob;
2603                 } else {
2604                         report("Unknown replacement in run request: `%s`", req->cmd);
2605                         return;
2606                 }
2608                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2609                         return;
2611                 if (next)
2612                         next = strchr(next, ')') + 1;
2613                 cmd = next;
2614         }
2616         open_external_viewer(buf);
2619 /*
2620  * User request switch noodle
2621  */
2623 static int
2624 view_driver(struct view *view, enum request request)
2626         int i;
2628         if (request == REQ_NONE) {
2629                 doupdate();
2630                 return TRUE;
2631         }
2633         if (request > REQ_NONE) {
2634                 open_run_request(request);
2635                 /* FIXME: When all views can refresh always do this. */
2636                 if (view == VIEW(REQ_VIEW_STATUS) ||
2637                     view == VIEW(REQ_VIEW_STAGE))
2638                         request = REQ_REFRESH;
2639                 else
2640                         return TRUE;
2641         }
2643         if (view && view->lines) {
2644                 request = view->ops->request(view, request, &view->line[view->lineno]);
2645                 if (request == REQ_NONE)
2646                         return TRUE;
2647         }
2649         switch (request) {
2650         case REQ_MOVE_UP:
2651         case REQ_MOVE_DOWN:
2652         case REQ_MOVE_PAGE_UP:
2653         case REQ_MOVE_PAGE_DOWN:
2654         case REQ_MOVE_FIRST_LINE:
2655         case REQ_MOVE_LAST_LINE:
2656                 move_view(view, request);
2657                 break;
2659         case REQ_SCROLL_LINE_DOWN:
2660         case REQ_SCROLL_LINE_UP:
2661         case REQ_SCROLL_PAGE_DOWN:
2662         case REQ_SCROLL_PAGE_UP:
2663                 scroll_view(view, request);
2664                 break;
2666         case REQ_VIEW_BLAME:
2667                 if (!opt_file[0]) {
2668                         report("No file chosen, press %s to open tree view",
2669                                get_key(REQ_VIEW_TREE));
2670                         break;
2671                 }
2672                 open_view(view, request, OPEN_DEFAULT);
2673                 break;
2675         case REQ_VIEW_BLOB:
2676                 if (!ref_blob[0]) {
2677                         report("No file chosen, press %s to open tree view",
2678                                get_key(REQ_VIEW_TREE));
2679                         break;
2680                 }
2681                 open_view(view, request, OPEN_DEFAULT);
2682                 break;
2684         case REQ_VIEW_PAGER:
2685                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2686                         report("No pager content, press %s to run command from prompt",
2687                                get_key(REQ_PROMPT));
2688                         break;
2689                 }
2690                 open_view(view, request, OPEN_DEFAULT);
2691                 break;
2693         case REQ_VIEW_STAGE:
2694                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2695                         report("No stage content, press %s to open the status view and choose file",
2696                                get_key(REQ_VIEW_STATUS));
2697                         break;
2698                 }
2699                 open_view(view, request, OPEN_DEFAULT);
2700                 break;
2702         case REQ_VIEW_STATUS:
2703                 if (opt_is_inside_work_tree == FALSE) {
2704                         report("The status view requires a working tree");
2705                         break;
2706                 }
2707                 open_view(view, request, OPEN_DEFAULT);
2708                 break;
2710         case REQ_VIEW_MAIN:
2711         case REQ_VIEW_DIFF:
2712         case REQ_VIEW_LOG:
2713         case REQ_VIEW_TREE:
2714         case REQ_VIEW_HELP:
2715                 open_view(view, request, OPEN_DEFAULT);
2716                 break;
2718         case REQ_NEXT:
2719         case REQ_PREVIOUS:
2720                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2722                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2723                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2724                    (view == VIEW(REQ_VIEW_DIFF) &&
2725                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2726                    (view == VIEW(REQ_VIEW_STAGE) &&
2727                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2728                    (view == VIEW(REQ_VIEW_BLOB) &&
2729                      view->parent == VIEW(REQ_VIEW_TREE))) {
2730                         int line;
2732                         view = view->parent;
2733                         line = view->lineno;
2734                         move_view(view, request);
2735                         if (view_is_displayed(view))
2736                                 update_view_title(view);
2737                         if (line != view->lineno)
2738                                 view->ops->request(view, REQ_ENTER,
2739                                                    &view->line[view->lineno]);
2741                 } else {
2742                         move_view(view, request);
2743                 }
2744                 break;
2746         case REQ_VIEW_NEXT:
2747         {
2748                 int nviews = displayed_views();
2749                 int next_view = (current_view + 1) % nviews;
2751                 if (next_view == current_view) {
2752                         report("Only one view is displayed");
2753                         break;
2754                 }
2756                 current_view = next_view;
2757                 /* Blur out the title of the previous view. */
2758                 update_view_title(view);
2759                 report("");
2760                 break;
2761         }
2762         case REQ_REFRESH:
2763                 report("Refreshing is not yet supported for the %s view", view->name);
2764                 break;
2766         case REQ_MAXIMIZE:
2767                 if (displayed_views() == 2)
2768                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2769                 break;
2771         case REQ_TOGGLE_LINENO:
2772                 opt_line_number = !opt_line_number;
2773                 redraw_display();
2774                 break;
2776         case REQ_TOGGLE_DATE:
2777                 opt_date = !opt_date;
2778                 redraw_display();
2779                 break;
2781         case REQ_TOGGLE_AUTHOR:
2782                 opt_author = !opt_author;
2783                 redraw_display();
2784                 break;
2786         case REQ_TOGGLE_REV_GRAPH:
2787                 opt_rev_graph = !opt_rev_graph;
2788                 redraw_display();
2789                 break;
2791         case REQ_TOGGLE_REFS:
2792                 opt_show_refs = !opt_show_refs;
2793                 redraw_display();
2794                 break;
2796         case REQ_PROMPT:
2797                 /* Always reload^Wrerun commands from the prompt. */
2798                 open_view(view, opt_request, OPEN_RELOAD);
2799                 break;
2801         case REQ_SEARCH:
2802         case REQ_SEARCH_BACK:
2803                 search_view(view, request);
2804                 break;
2806         case REQ_FIND_NEXT:
2807         case REQ_FIND_PREV:
2808                 find_next(view, request);
2809                 break;
2811         case REQ_STOP_LOADING:
2812                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2813                         view = &views[i];
2814                         if (view->pipe)
2815                                 report("Stopped loading the %s view", view->name),
2816                         end_update(view);
2817                 }
2818                 break;
2820         case REQ_SHOW_VERSION:
2821                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2822                 return TRUE;
2824         case REQ_SCREEN_RESIZE:
2825                 resize_display();
2826                 /* Fall-through */
2827         case REQ_SCREEN_REDRAW:
2828                 redraw_display();
2829                 break;
2831         case REQ_EDIT:
2832                 report("Nothing to edit");
2833                 break;
2836         case REQ_ENTER:
2837                 report("Nothing to enter");
2838                 break;
2841         case REQ_VIEW_CLOSE:
2842                 /* XXX: Mark closed views by letting view->parent point to the
2843                  * view itself. Parents to closed view should never be
2844                  * followed. */
2845                 if (view->parent &&
2846                     view->parent->parent != view->parent) {
2847                         memset(display, 0, sizeof(display));
2848                         current_view = 0;
2849                         display[current_view] = view->parent;
2850                         view->parent = view;
2851                         resize_display();
2852                         redraw_display();
2853                         break;
2854                 }
2855                 /* Fall-through */
2856         case REQ_QUIT:
2857                 return FALSE;
2859         default:
2860                 /* An unknown key will show most commonly used commands. */
2861                 report("Unknown key, press 'h' for help");
2862                 return TRUE;
2863         }
2865         return TRUE;
2869 /*
2870  * Pager backend
2871  */
2873 static bool
2874 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2876         char *text = line->data;
2878         if (opt_line_number && draw_lineno(view, lineno))
2879                 return TRUE;
2881         draw_text(view, line->type, text, TRUE);
2882         return TRUE;
2885 static bool
2886 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2888         char refbuf[SIZEOF_STR];
2889         char *ref = NULL;
2890         FILE *pipe;
2892         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2893                 return TRUE;
2895         pipe = popen(refbuf, "r");
2896         if (!pipe)
2897                 return TRUE;
2899         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2900                 ref = chomp_string(ref);
2901         pclose(pipe);
2903         if (!ref || !*ref)
2904                 return TRUE;
2906         /* This is the only fatal call, since it can "corrupt" the buffer. */
2907         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2908                 return FALSE;
2910         return TRUE;
2913 static void
2914 add_pager_refs(struct view *view, struct line *line)
2916         char buf[SIZEOF_STR];
2917         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2918         struct ref **refs;
2919         size_t bufpos = 0, refpos = 0;
2920         const char *sep = "Refs: ";
2921         bool is_tag = FALSE;
2923         assert(line->type == LINE_COMMIT);
2925         refs = get_refs(commit_id);
2926         if (!refs) {
2927                 if (view == VIEW(REQ_VIEW_DIFF))
2928                         goto try_add_describe_ref;
2929                 return;
2930         }
2932         do {
2933                 struct ref *ref = refs[refpos];
2934                 char *fmt = ref->tag    ? "%s[%s]" :
2935                             ref->remote ? "%s<%s>" : "%s%s";
2937                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2938                         return;
2939                 sep = ", ";
2940                 if (ref->tag)
2941                         is_tag = TRUE;
2942         } while (refs[refpos++]->next);
2944         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2945 try_add_describe_ref:
2946                 /* Add <tag>-g<commit_id> "fake" reference. */
2947                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2948                         return;
2949         }
2951         if (bufpos == 0)
2952                 return;
2954         if (!realloc_lines(view, view->line_size + 1))
2955                 return;
2957         add_line_text(view, buf, LINE_PP_REFS);
2960 static bool
2961 pager_read(struct view *view, char *data)
2963         struct line *line;
2965         if (!data)
2966                 return TRUE;
2968         line = add_line_text(view, data, get_line_type(data));
2969         if (!line)
2970                 return FALSE;
2972         if (line->type == LINE_COMMIT &&
2973             (view == VIEW(REQ_VIEW_DIFF) ||
2974              view == VIEW(REQ_VIEW_LOG)))
2975                 add_pager_refs(view, line);
2977         return TRUE;
2980 static enum request
2981 pager_request(struct view *view, enum request request, struct line *line)
2983         int split = 0;
2985         if (request != REQ_ENTER)
2986                 return request;
2988         if (line->type == LINE_COMMIT &&
2989            (view == VIEW(REQ_VIEW_LOG) ||
2990             view == VIEW(REQ_VIEW_PAGER))) {
2991                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2992                 split = 1;
2993         }
2995         /* Always scroll the view even if it was split. That way
2996          * you can use Enter to scroll through the log view and
2997          * split open each commit diff. */
2998         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3000         /* FIXME: A minor workaround. Scrolling the view will call report("")
3001          * but if we are scrolling a non-current view this won't properly
3002          * update the view title. */
3003         if (split)
3004                 update_view_title(view);
3006         return REQ_NONE;
3009 static bool
3010 pager_grep(struct view *view, struct line *line)
3012         regmatch_t pmatch;
3013         char *text = line->data;
3015         if (!*text)
3016                 return FALSE;
3018         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3019                 return FALSE;
3021         return TRUE;
3024 static void
3025 pager_select(struct view *view, struct line *line)
3027         if (line->type == LINE_COMMIT) {
3028                 char *text = (char *)line->data + STRING_SIZE("commit ");
3030                 if (view != VIEW(REQ_VIEW_PAGER))
3031                         string_copy_rev(view->ref, text);
3032                 string_copy_rev(ref_commit, text);
3033         }
3036 static struct view_ops pager_ops = {
3037         "line",
3038         NULL,
3039         pager_read,
3040         pager_draw,
3041         pager_request,
3042         pager_grep,
3043         pager_select,
3044 };
3047 /*
3048  * Help backend
3049  */
3051 static bool
3052 help_open(struct view *view)
3054         char buf[BUFSIZ];
3055         int lines = ARRAY_SIZE(req_info) + 2;
3056         int i;
3058         if (view->lines > 0)
3059                 return TRUE;
3061         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3062                 if (!req_info[i].request)
3063                         lines++;
3065         lines += run_requests + 1;
3067         view->line = calloc(lines, sizeof(*view->line));
3068         if (!view->line)
3069                 return FALSE;
3071         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3073         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3074                 char *key;
3076                 if (req_info[i].request == REQ_NONE)
3077                         continue;
3079                 if (!req_info[i].request) {
3080                         add_line_text(view, "", LINE_DEFAULT);
3081                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3082                         continue;
3083                 }
3085                 key = get_key(req_info[i].request);
3086                 if (!*key)
3087                         key = "(no key defined)";
3089                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3090                         continue;
3092                 add_line_text(view, buf, LINE_DEFAULT);
3093         }
3095         if (run_requests) {
3096                 add_line_text(view, "", LINE_DEFAULT);
3097                 add_line_text(view, "External commands:", LINE_DEFAULT);
3098         }
3100         for (i = 0; i < run_requests; i++) {
3101                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3102                 char *key;
3104                 if (!req)
3105                         continue;
3107                 key = get_key_name(req->key);
3108                 if (!*key)
3109                         key = "(no key defined)";
3111                 if (!string_format(buf, "    %-10s %-14s `%s`",
3112                                    keymap_table[req->keymap].name,
3113                                    key, req->cmd))
3114                         continue;
3116                 add_line_text(view, buf, LINE_DEFAULT);
3117         }
3119         return TRUE;
3122 static struct view_ops help_ops = {
3123         "line",
3124         help_open,
3125         NULL,
3126         pager_draw,
3127         pager_request,
3128         pager_grep,
3129         pager_select,
3130 };
3133 /*
3134  * Tree backend
3135  */
3137 struct tree_stack_entry {
3138         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3139         unsigned long lineno;           /* Line number to restore */
3140         char *name;                     /* Position of name in opt_path */
3141 };
3143 /* The top of the path stack. */
3144 static struct tree_stack_entry *tree_stack = NULL;
3145 unsigned long tree_lineno = 0;
3147 static void
3148 pop_tree_stack_entry(void)
3150         struct tree_stack_entry *entry = tree_stack;
3152         tree_lineno = entry->lineno;
3153         entry->name[0] = 0;
3154         tree_stack = entry->prev;
3155         free(entry);
3158 static void
3159 push_tree_stack_entry(char *name, unsigned long lineno)
3161         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3162         size_t pathlen = strlen(opt_path);
3164         if (!entry)
3165                 return;
3167         entry->prev = tree_stack;
3168         entry->name = opt_path + pathlen;
3169         tree_stack = entry;
3171         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3172                 pop_tree_stack_entry();
3173                 return;
3174         }
3176         /* Move the current line to the first tree entry. */
3177         tree_lineno = 1;
3178         entry->lineno = lineno;
3181 /* Parse output from git-ls-tree(1):
3182  *
3183  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3184  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3185  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3186  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3187  */
3189 #define SIZEOF_TREE_ATTR \
3190         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3192 #define TREE_UP_FORMAT "040000 tree %s\t.."
3194 static int
3195 tree_compare_entry(enum line_type type1, char *name1,
3196                    enum line_type type2, char *name2)
3198         if (type1 != type2) {
3199                 if (type1 == LINE_TREE_DIR)
3200                         return -1;
3201                 return 1;
3202         }
3204         return strcmp(name1, name2);
3207 static char *
3208 tree_path(struct line *line)
3210         char *path = line->data;
3212         return path + SIZEOF_TREE_ATTR;
3215 static bool
3216 tree_read(struct view *view, char *text)
3218         size_t textlen = text ? strlen(text) : 0;
3219         char buf[SIZEOF_STR];
3220         unsigned long pos;
3221         enum line_type type;
3222         bool first_read = view->lines == 0;
3224         if (!text)
3225                 return TRUE;
3226         if (textlen <= SIZEOF_TREE_ATTR)
3227                 return FALSE;
3229         type = text[STRING_SIZE("100644 ")] == 't'
3230              ? LINE_TREE_DIR : LINE_TREE_FILE;
3232         if (first_read) {
3233                 /* Add path info line */
3234                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3235                     !realloc_lines(view, view->line_size + 1) ||
3236                     !add_line_text(view, buf, LINE_DEFAULT))
3237                         return FALSE;
3239                 /* Insert "link" to parent directory. */
3240                 if (*opt_path) {
3241                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3242                             !realloc_lines(view, view->line_size + 1) ||
3243                             !add_line_text(view, buf, LINE_TREE_DIR))
3244                                 return FALSE;
3245                 }
3246         }
3248         /* Strip the path part ... */
3249         if (*opt_path) {
3250                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3251                 size_t striplen = strlen(opt_path);
3252                 char *path = text + SIZEOF_TREE_ATTR;
3254                 if (pathlen > striplen)
3255                         memmove(path, path + striplen,
3256                                 pathlen - striplen + 1);
3257         }
3259         /* Skip "Directory ..." and ".." line. */
3260         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3261                 struct line *line = &view->line[pos];
3262                 char *path1 = tree_path(line);
3263                 char *path2 = text + SIZEOF_TREE_ATTR;
3264                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3266                 if (cmp <= 0)
3267                         continue;
3269                 text = strdup(text);
3270                 if (!text)
3271                         return FALSE;
3273                 if (view->lines > pos)
3274                         memmove(&view->line[pos + 1], &view->line[pos],
3275                                 (view->lines - pos) * sizeof(*line));
3277                 line = &view->line[pos];
3278                 line->data = text;
3279                 line->type = type;
3280                 view->lines++;
3281                 return TRUE;
3282         }
3284         if (!add_line_text(view, text, type))
3285                 return FALSE;
3287         if (tree_lineno > view->lineno) {
3288                 view->lineno = tree_lineno;
3289                 tree_lineno = 0;
3290         }
3292         return TRUE;
3295 static enum request
3296 tree_request(struct view *view, enum request request, struct line *line)
3298         enum open_flags flags;
3300         if (request == REQ_VIEW_BLAME) {
3301                 char *filename = tree_path(line);
3303                 if (line->type == LINE_TREE_DIR) {
3304                         report("Cannot show blame for directory %s", opt_path);
3305                         return REQ_NONE;
3306                 }
3308                 string_copy(opt_ref, view->vid);
3309                 string_format(opt_file, "%s%s", opt_path, filename);
3310                 return request;
3311         }
3312         if (request == REQ_TREE_PARENT) {
3313                 if (*opt_path) {
3314                         /* fake 'cd  ..' */
3315                         request = REQ_ENTER;
3316                         line = &view->line[1];
3317                 } else {
3318                         /* quit view if at top of tree */
3319                         return REQ_VIEW_CLOSE;
3320                 }
3321         }
3322         if (request != REQ_ENTER)
3323                 return request;
3325         /* Cleanup the stack if the tree view is at a different tree. */
3326         while (!*opt_path && tree_stack)
3327                 pop_tree_stack_entry();
3329         switch (line->type) {
3330         case LINE_TREE_DIR:
3331                 /* Depending on whether it is a subdir or parent (updir?) link
3332                  * mangle the path buffer. */
3333                 if (line == &view->line[1] && *opt_path) {
3334                         pop_tree_stack_entry();
3336                 } else {
3337                         char *basename = tree_path(line);
3339                         push_tree_stack_entry(basename, view->lineno);
3340                 }
3342                 /* Trees and subtrees share the same ID, so they are not not
3343                  * unique like blobs. */
3344                 flags = OPEN_RELOAD;
3345                 request = REQ_VIEW_TREE;
3346                 break;
3348         case LINE_TREE_FILE:
3349                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3350                 request = REQ_VIEW_BLOB;
3351                 break;
3353         default:
3354                 return TRUE;
3355         }
3357         open_view(view, request, flags);
3358         if (request == REQ_VIEW_TREE) {
3359                 view->lineno = tree_lineno;
3360         }
3362         return REQ_NONE;
3365 static void
3366 tree_select(struct view *view, struct line *line)
3368         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3370         if (line->type == LINE_TREE_FILE) {
3371                 string_copy_rev(ref_blob, text);
3373         } else if (line->type != LINE_TREE_DIR) {
3374                 return;
3375         }
3377         string_copy_rev(view->ref, text);
3380 static struct view_ops tree_ops = {
3381         "file",
3382         NULL,
3383         tree_read,
3384         pager_draw,
3385         tree_request,
3386         pager_grep,
3387         tree_select,
3388 };
3390 static bool
3391 blob_read(struct view *view, char *line)
3393         if (!line)
3394                 return TRUE;
3395         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3398 static struct view_ops blob_ops = {
3399         "line",
3400         NULL,
3401         blob_read,
3402         pager_draw,
3403         pager_request,
3404         pager_grep,
3405         pager_select,
3406 };
3408 /*
3409  * Blame backend
3410  *
3411  * Loading the blame view is a two phase job:
3412  *
3413  *  1. File content is read either using opt_file from the
3414  *     filesystem or using git-cat-file.
3415  *  2. Then blame information is incrementally added by
3416  *     reading output from git-blame.
3417  */
3419 struct blame_commit {
3420         char id[SIZEOF_REV];            /* SHA1 ID. */
3421         char title[128];                /* First line of the commit message. */
3422         char author[75];                /* Author of the commit. */
3423         struct tm time;                 /* Date from the author ident. */
3424         char filename[128];             /* Name of file. */
3425 };
3427 struct blame {
3428         struct blame_commit *commit;
3429         unsigned int header:1;
3430         char text[1];
3431 };
3433 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3434 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3436 static bool
3437 blame_open(struct view *view)
3439         char path[SIZEOF_STR];
3440         char ref[SIZEOF_STR] = "";
3442         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3443                 return FALSE;
3445         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3446                 return FALSE;
3448         if (*opt_ref) {
3449                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3450                         return FALSE;
3451         } else {
3452                 view->pipe = fopen(opt_file, "r");
3453                 if (!view->pipe &&
3454                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3455                         return FALSE;
3456         }
3458         if (!view->pipe)
3459                 view->pipe = popen(view->cmd, "r");
3460         if (!view->pipe)
3461                 return FALSE;
3463         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3464                 return FALSE;
3466         string_format(view->ref, "%s ...", opt_file);
3467         string_copy_rev(view->vid, opt_file);
3468         set_nonblocking_input(TRUE);
3470         if (view->line) {
3471                 int i;
3473                 for (i = 0; i < view->lines; i++)
3474                         free(view->line[i].data);
3475                 free(view->line);
3476         }
3478         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3479         view->offset = view->lines  = view->lineno = 0;
3480         view->line = NULL;
3481         view->start_time = time(NULL);
3483         return TRUE;
3486 static struct blame_commit *
3487 get_blame_commit(struct view *view, const char *id)
3489         size_t i;
3491         for (i = 0; i < view->lines; i++) {
3492                 struct blame *blame = view->line[i].data;
3494                 if (!blame->commit)
3495                         continue;
3497                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3498                         return blame->commit;
3499         }
3501         {
3502                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3504                 if (commit)
3505                         string_ncopy(commit->id, id, SIZEOF_REV);
3506                 return commit;
3507         }
3510 static bool
3511 parse_number(char **posref, size_t *number, size_t min, size_t max)
3513         char *pos = *posref;
3515         *posref = NULL;
3516         pos = strchr(pos + 1, ' ');
3517         if (!pos || !isdigit(pos[1]))
3518                 return FALSE;
3519         *number = atoi(pos + 1);
3520         if (*number < min || *number > max)
3521                 return FALSE;
3523         *posref = pos;
3524         return TRUE;
3527 static struct blame_commit *
3528 parse_blame_commit(struct view *view, char *text, int *blamed)
3530         struct blame_commit *commit;
3531         struct blame *blame;
3532         char *pos = text + SIZEOF_REV - 1;
3533         size_t lineno;
3534         size_t group;
3536         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3537                 return NULL;
3539         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3540             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3541                 return NULL;
3543         commit = get_blame_commit(view, text);
3544         if (!commit)
3545                 return NULL;
3547         *blamed += group;
3548         while (group--) {
3549                 struct line *line = &view->line[lineno + group - 1];
3551                 blame = line->data;
3552                 blame->commit = commit;
3553                 blame->header = !group;
3554                 line->dirty = 1;
3555         }
3557         return commit;
3560 static bool
3561 blame_read_file(struct view *view, char *line)
3563         if (!line) {
3564                 FILE *pipe = NULL;
3566                 if (view->lines > 0)
3567                         pipe = popen(view->cmd, "r");
3568                 else if (!view->parent)
3569                         die("No blame exist for %s", view->vid);
3570                 view->cmd[0] = 0;
3571                 if (!pipe) {
3572                         report("Failed to load blame data");
3573                         return TRUE;
3574                 }
3576                 fclose(view->pipe);
3577                 view->pipe = pipe;
3578                 return FALSE;
3580         } else {
3581                 size_t linelen = strlen(line);
3582                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3584                 if (!line)
3585                         return FALSE;
3587                 blame->commit = NULL;
3588                 strncpy(blame->text, line, linelen);
3589                 blame->text[linelen] = 0;
3590                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3591         }
3594 static bool
3595 match_blame_header(const char *name, char **line)
3597         size_t namelen = strlen(name);
3598         bool matched = !strncmp(name, *line, namelen);
3600         if (matched)
3601                 *line += namelen;
3603         return matched;
3606 static bool
3607 blame_read(struct view *view, char *line)
3609         static struct blame_commit *commit = NULL;
3610         static int blamed = 0;
3611         static time_t author_time;
3613         if (*view->cmd)
3614                 return blame_read_file(view, line);
3616         if (!line) {
3617                 /* Reset all! */
3618                 commit = NULL;
3619                 blamed = 0;
3620                 string_format(view->ref, "%s", view->vid);
3621                 if (view_is_displayed(view)) {
3622                         update_view_title(view);
3623                         redraw_view_from(view, 0);
3624                 }
3625                 return TRUE;
3626         }
3628         if (!commit) {
3629                 commit = parse_blame_commit(view, line, &blamed);
3630                 string_format(view->ref, "%s %2d%%", view->vid,
3631                               blamed * 100 / view->lines);
3633         } else if (match_blame_header("author ", &line)) {
3634                 string_ncopy(commit->author, line, strlen(line));
3636         } else if (match_blame_header("author-time ", &line)) {
3637                 author_time = (time_t) atol(line);
3639         } else if (match_blame_header("author-tz ", &line)) {
3640                 long tz;
3642                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3643                 tz += ('0' - line[2]) * 60 * 60;
3644                 tz += ('0' - line[3]) * 60;
3645                 tz += ('0' - line[4]) * 60;
3647                 if (line[0] == '-')
3648                         tz = -tz;
3650                 author_time -= tz;
3651                 gmtime_r(&author_time, &commit->time);
3653         } else if (match_blame_header("summary ", &line)) {
3654                 string_ncopy(commit->title, line, strlen(line));
3656         } else if (match_blame_header("filename ", &line)) {
3657                 string_ncopy(commit->filename, line, strlen(line));
3658                 commit = NULL;
3659         }
3661         return TRUE;
3664 static bool
3665 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3667         struct blame *blame = line->data;
3668         struct tm *time = NULL;
3669         char *id = NULL, *author = NULL;
3671         if (blame->commit && *blame->commit->filename) {
3672                 id = blame->commit->id;
3673                 author = blame->commit->author;
3674                 time = &blame->commit->time;
3675         }
3677         if (opt_date && draw_date(view, time))
3678                 return TRUE;
3680         if (opt_author &&
3681             draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3682                 return TRUE;
3684         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3685                 return TRUE;
3687         if (draw_lineno(view, lineno))
3688                 return TRUE;
3690         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3691         return TRUE;
3694 static enum request
3695 blame_request(struct view *view, enum request request, struct line *line)
3697         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3698         struct blame *blame = line->data;
3700         switch (request) {
3701         case REQ_ENTER:
3702                 if (!blame->commit) {
3703                         report("No commit loaded yet");
3704                         break;
3705                 }
3707                 if (!strcmp(blame->commit->id, NULL_ID)) {
3708                         char path[SIZEOF_STR];
3710                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3711                                 break;
3712                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3713                 }
3715                 open_view(view, REQ_VIEW_DIFF, flags);
3716                 break;
3718         default:
3719                 return request;
3720         }
3722         return REQ_NONE;
3725 static bool
3726 blame_grep(struct view *view, struct line *line)
3728         struct blame *blame = line->data;
3729         struct blame_commit *commit = blame->commit;
3730         regmatch_t pmatch;
3732 #define MATCH(text, on)                                                 \
3733         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3735         if (commit) {
3736                 char buf[DATE_COLS + 1];
3738                 if (MATCH(commit->title, 1) ||
3739                     MATCH(commit->author, opt_author) ||
3740                     MATCH(commit->id, opt_date))
3741                         return TRUE;
3743                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3744                     MATCH(buf, 1))
3745                         return TRUE;
3746         }
3748         return MATCH(blame->text, 1);
3750 #undef MATCH
3753 static void
3754 blame_select(struct view *view, struct line *line)
3756         struct blame *blame = line->data;
3757         struct blame_commit *commit = blame->commit;
3759         if (!commit)
3760                 return;
3762         if (!strcmp(commit->id, NULL_ID))
3763                 string_ncopy(ref_commit, "HEAD", 4);
3764         else
3765                 string_copy_rev(ref_commit, commit->id);
3768 static struct view_ops blame_ops = {
3769         "line",
3770         blame_open,
3771         blame_read,
3772         blame_draw,
3773         blame_request,
3774         blame_grep,
3775         blame_select,
3776 };
3778 /*
3779  * Status backend
3780  */
3782 struct status {
3783         char status;
3784         struct {
3785                 mode_t mode;
3786                 char rev[SIZEOF_REV];
3787                 char name[SIZEOF_STR];
3788         } old;
3789         struct {
3790                 mode_t mode;
3791                 char rev[SIZEOF_REV];
3792                 char name[SIZEOF_STR];
3793         } new;
3794 };
3796 static char status_onbranch[SIZEOF_STR];
3797 static struct status stage_status;
3798 static enum line_type stage_line_type;
3800 /* Get fields from the diff line:
3801  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3802  */
3803 static inline bool
3804 status_get_diff(struct status *file, char *buf, size_t bufsize)
3806         char *old_mode = buf +  1;
3807         char *new_mode = buf +  8;
3808         char *old_rev  = buf + 15;
3809         char *new_rev  = buf + 56;
3810         char *status   = buf + 97;
3812         if (bufsize < 99 ||
3813             old_mode[-1] != ':' ||
3814             new_mode[-1] != ' ' ||
3815             old_rev[-1]  != ' ' ||
3816             new_rev[-1]  != ' ' ||
3817             status[-1]   != ' ')
3818                 return FALSE;
3820         file->status = *status;
3822         string_copy_rev(file->old.rev, old_rev);
3823         string_copy_rev(file->new.rev, new_rev);
3825         file->old.mode = strtoul(old_mode, NULL, 8);
3826         file->new.mode = strtoul(new_mode, NULL, 8);
3828         file->old.name[0] = file->new.name[0] = 0;
3830         return TRUE;
3833 static bool
3834 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3836         struct status *file = NULL;
3837         struct status *unmerged = NULL;
3838         char buf[SIZEOF_STR * 4];
3839         size_t bufsize = 0;
3840         FILE *pipe;
3842         pipe = popen(cmd, "r");
3843         if (!pipe)
3844                 return FALSE;
3846         add_line_data(view, NULL, type);
3848         while (!feof(pipe) && !ferror(pipe)) {
3849                 char *sep;
3850                 size_t readsize;
3852                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3853                 if (!readsize)
3854                         break;
3855                 bufsize += readsize;
3857                 /* Process while we have NUL chars. */
3858                 while ((sep = memchr(buf, 0, bufsize))) {
3859                         size_t sepsize = sep - buf + 1;
3861                         if (!file) {
3862                                 if (!realloc_lines(view, view->line_size + 1))
3863                                         goto error_out;
3865                                 file = calloc(1, sizeof(*file));
3866                                 if (!file)
3867                                         goto error_out;
3869                                 add_line_data(view, file, type);
3870                         }
3872                         /* Parse diff info part. */
3873                         if (status) {
3874                                 file->status = status;
3875                                 if (status == 'A')
3876                                         string_copy(file->old.rev, NULL_ID);
3878                         } else if (!file->status) {
3879                                 if (!status_get_diff(file, buf, sepsize))
3880                                         goto error_out;
3882                                 bufsize -= sepsize;
3883                                 memmove(buf, sep + 1, bufsize);
3885                                 sep = memchr(buf, 0, bufsize);
3886                                 if (!sep)
3887                                         break;
3888                                 sepsize = sep - buf + 1;
3890                                 /* Collapse all 'M'odified entries that
3891                                  * follow a associated 'U'nmerged entry.
3892                                  */
3893                                 if (file->status == 'U') {
3894                                         unmerged = file;
3896                                 } else if (unmerged) {
3897                                         int collapse = !strcmp(buf, unmerged->new.name);
3899                                         unmerged = NULL;
3900                                         if (collapse) {
3901                                                 free(file);
3902                                                 view->lines--;
3903                                                 continue;
3904                                         }
3905                                 }
3906                         }
3908                         /* Grab the old name for rename/copy. */
3909                         if (!*file->old.name &&
3910                             (file->status == 'R' || file->status == 'C')) {
3911                                 sepsize = sep - buf + 1;
3912                                 string_ncopy(file->old.name, buf, sepsize);
3913                                 bufsize -= sepsize;
3914                                 memmove(buf, sep + 1, bufsize);
3916                                 sep = memchr(buf, 0, bufsize);
3917                                 if (!sep)
3918                                         break;
3919                                 sepsize = sep - buf + 1;
3920                         }
3922                         /* git-ls-files just delivers a NUL separated
3923                          * list of file names similar to the second half
3924                          * of the git-diff-* output. */
3925                         string_ncopy(file->new.name, buf, sepsize);
3926                         if (!*file->old.name)
3927                                 string_copy(file->old.name, file->new.name);
3928                         bufsize -= sepsize;
3929                         memmove(buf, sep + 1, bufsize);
3930                         file = NULL;
3931                 }
3932         }
3934         if (ferror(pipe)) {
3935 error_out:
3936                 pclose(pipe);
3937                 return FALSE;
3938         }
3940         if (!view->line[view->lines - 1].data)
3941                 add_line_data(view, NULL, LINE_STAT_NONE);
3943         pclose(pipe);
3944         return TRUE;
3947 /* Don't show unmerged entries in the staged section. */
3948 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3949 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3950 #define STATUS_LIST_OTHER_CMD \
3951         "git ls-files -z --others --exclude-per-directory=.gitignore"
3952 #define STATUS_LIST_NO_HEAD_CMD \
3953         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3955 #define STATUS_DIFF_INDEX_SHOW_CMD \
3956         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3958 #define STATUS_DIFF_FILES_SHOW_CMD \
3959         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3961 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3962         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3964 /* First parse staged info using git-diff-index(1), then parse unstaged
3965  * info using git-diff-files(1), and finally untracked files using
3966  * git-ls-files(1). */
3967 static bool
3968 status_open(struct view *view)
3970         struct stat statbuf;
3971         char exclude[SIZEOF_STR];
3972         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3973         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3974         unsigned long prev_lineno = view->lineno;
3975         char indexstatus = 0;
3976         size_t i;
3978         for (i = 0; i < view->lines; i++)
3979                 free(view->line[i].data);
3980         free(view->line);
3981         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3982         view->line = NULL;
3984         if (!realloc_lines(view, view->line_size + 7))
3985                 return FALSE;
3987         add_line_data(view, NULL, LINE_STAT_HEAD);
3988         if (opt_no_head)
3989                 string_copy(status_onbranch, "Initial commit");
3990         else if (!*opt_head)
3991                 string_copy(status_onbranch, "Not currently on any branch");
3992         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3993                 return FALSE;
3995         if (opt_no_head) {
3996                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3997                 indexstatus = 'A';
3998         }
4000         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4001                 return FALSE;
4003         if (stat(exclude, &statbuf) >= 0) {
4004                 size_t cmdsize = strlen(othercmd);
4006                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4007                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4008                         return FALSE;
4010                 cmdsize = strlen(indexcmd);
4011                 if (opt_no_head &&
4012                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4013                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4014                         return FALSE;
4015         }
4017         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4019         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4020             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4021             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4022                 return FALSE;
4024         /* If all went well restore the previous line number to stay in
4025          * the context or select a line with something that can be
4026          * updated. */
4027         if (prev_lineno >= view->lines)
4028                 prev_lineno = view->lines - 1;
4029         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4030                 prev_lineno++;
4031         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4032                 prev_lineno--;
4034         /* If the above fails, always skip the "On branch" line. */
4035         if (prev_lineno < view->lines)
4036                 view->lineno = prev_lineno;
4037         else
4038                 view->lineno = 1;
4040         if (view->lineno < view->offset)
4041                 view->offset = view->lineno;
4042         else if (view->offset + view->height <= view->lineno)
4043                 view->offset = view->lineno - view->height + 1;
4045         return TRUE;
4048 static bool
4049 status_draw(struct view *view, struct line *line, unsigned int lineno)
4051         struct status *status = line->data;
4052         enum line_type type;
4053         char *text;
4055         if (!status) {
4056                 switch (line->type) {
4057                 case LINE_STAT_STAGED:
4058                         type = LINE_STAT_SECTION;
4059                         text = "Changes to be committed:";
4060                         break;
4062                 case LINE_STAT_UNSTAGED:
4063                         type = LINE_STAT_SECTION;
4064                         text = "Changed but not updated:";
4065                         break;
4067                 case LINE_STAT_UNTRACKED:
4068                         type = LINE_STAT_SECTION;
4069                         text = "Untracked files:";
4070                         break;
4072                 case LINE_STAT_NONE:
4073                         type = LINE_DEFAULT;
4074                         text = "    (no files)";
4075                         break;
4077                 case LINE_STAT_HEAD:
4078                         type = LINE_STAT_HEAD;
4079                         text = status_onbranch;
4080                         break;
4082                 default:
4083                         return FALSE;
4084                 }
4085         } else {
4086                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4088                 buf[0] = status->status;
4089                 if (draw_text(view, line->type, buf, TRUE))
4090                         return TRUE;
4091                 type = LINE_DEFAULT;
4092                 text = status->new.name;
4093         }
4095         draw_text(view, type, text, TRUE);
4096         return TRUE;
4099 static enum request
4100 status_enter(struct view *view, struct line *line)
4102         struct status *status = line->data;
4103         char oldpath[SIZEOF_STR] = "";
4104         char newpath[SIZEOF_STR] = "";
4105         char *info;
4106         size_t cmdsize = 0;
4107         enum open_flags split;
4109         if (line->type == LINE_STAT_NONE ||
4110             (!status && line[1].type == LINE_STAT_NONE)) {
4111                 report("No file to diff");
4112                 return REQ_NONE;
4113         }
4115         if (status) {
4116                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4117                         return REQ_QUIT;
4118                 /* Diffs for unmerged entries are empty when pasing the
4119                  * new path, so leave it empty. */
4120                 if (status->status != 'U' &&
4121                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4122                         return REQ_QUIT;
4123         }
4125         if (opt_cdup[0] &&
4126             line->type != LINE_STAT_UNTRACKED &&
4127             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4128                 return REQ_QUIT;
4130         switch (line->type) {
4131         case LINE_STAT_STAGED:
4132                 if (opt_no_head) {
4133                         if (!string_format_from(opt_cmd, &cmdsize,
4134                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4135                                                 newpath))
4136                                 return REQ_QUIT;
4137                 } else {
4138                         if (!string_format_from(opt_cmd, &cmdsize,
4139                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4140                                                 oldpath, newpath))
4141                                 return REQ_QUIT;
4142                 }
4144                 if (status)
4145                         info = "Staged changes to %s";
4146                 else
4147                         info = "Staged changes";
4148                 break;
4150         case LINE_STAT_UNSTAGED:
4151                 if (!string_format_from(opt_cmd, &cmdsize,
4152                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4153                         return REQ_QUIT;
4154                 if (status)
4155                         info = "Unstaged changes to %s";
4156                 else
4157                         info = "Unstaged changes";
4158                 break;
4160         case LINE_STAT_UNTRACKED:
4161                 if (opt_pipe)
4162                         return REQ_QUIT;
4164                 if (!status) {
4165                         report("No file to show");
4166                         return REQ_NONE;
4167                 }
4169                 opt_pipe = fopen(status->new.name, "r");
4170                 info = "Untracked file %s";
4171                 break;
4173         case LINE_STAT_HEAD:
4174                 return REQ_NONE;
4176         default:
4177                 die("line type %d not handled in switch", line->type);
4178         }
4180         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4181         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4182         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4183                 if (status) {
4184                         stage_status = *status;
4185                 } else {
4186                         memset(&stage_status, 0, sizeof(stage_status));
4187                 }
4189                 stage_line_type = line->type;
4190                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4191         }
4193         return REQ_NONE;
4196 static bool
4197 status_exists(struct status *status, enum line_type type)
4199         struct view *view = VIEW(REQ_VIEW_STATUS);
4200         struct line *line;
4202         for (line = view->line; line < view->line + view->lines; line++) {
4203                 struct status *pos = line->data;
4205                 if (line->type == type && pos &&
4206                     !strcmp(status->new.name, pos->new.name))
4207                         return TRUE;
4208         }
4210         return FALSE;
4214 static FILE *
4215 status_update_prepare(enum line_type type)
4217         char cmd[SIZEOF_STR];
4218         size_t cmdsize = 0;
4220         if (opt_cdup[0] &&
4221             type != LINE_STAT_UNTRACKED &&
4222             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4223                 return NULL;
4225         switch (type) {
4226         case LINE_STAT_STAGED:
4227                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4228                 break;
4230         case LINE_STAT_UNSTAGED:
4231         case LINE_STAT_UNTRACKED:
4232                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4233                 break;
4235         default:
4236                 die("line type %d not handled in switch", type);
4237         }
4239         return popen(cmd, "w");
4242 static bool
4243 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4245         char buf[SIZEOF_STR];
4246         size_t bufsize = 0;
4247         size_t written = 0;
4249         switch (type) {
4250         case LINE_STAT_STAGED:
4251                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4252                                         status->old.mode,
4253                                         status->old.rev,
4254                                         status->old.name, 0))
4255                         return FALSE;
4256                 break;
4258         case LINE_STAT_UNSTAGED:
4259         case LINE_STAT_UNTRACKED:
4260                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4261                         return FALSE;
4262                 break;
4264         default:
4265                 die("line type %d not handled in switch", type);
4266         }
4268         while (!ferror(pipe) && written < bufsize) {
4269                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4270         }
4272         return written == bufsize;
4275 static bool
4276 status_update_file(struct status *status, enum line_type type)
4278         FILE *pipe = status_update_prepare(type);
4279         bool result;
4281         if (!pipe)
4282                 return FALSE;
4284         result = status_update_write(pipe, status, type);
4285         pclose(pipe);
4286         return result;
4289 static bool
4290 status_update_files(struct view *view, struct line *line)
4292         FILE *pipe = status_update_prepare(line->type);
4293         bool result = TRUE;
4294         struct line *pos = view->line + view->lines;
4295         int files = 0;
4296         int file, done;
4298         if (!pipe)
4299                 return FALSE;
4301         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4302                 files++;
4304         for (file = 0, done = 0; result && file < files; line++, file++) {
4305                 int almost_done = file * 100 / files;
4307                 if (almost_done > done) {
4308                         done = almost_done;
4309                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4310                                       file, files, done);
4311                         update_view_title(view);
4312                 }
4313                 result = status_update_write(pipe, line->data, line->type);
4314         }
4316         pclose(pipe);
4317         return result;
4320 static bool
4321 status_update(struct view *view)
4323         struct line *line = &view->line[view->lineno];
4325         assert(view->lines);
4327         if (!line->data) {
4328                 /* This should work even for the "On branch" line. */
4329                 if (line < view->line + view->lines && !line[1].data) {
4330                         report("Nothing to update");
4331                         return FALSE;
4332                 }
4334                 if (!status_update_files(view, line + 1)) {
4335                         report("Failed to update file status");
4336                         return FALSE;
4337                 }
4339         } else if (!status_update_file(line->data, line->type)) {
4340                 report("Failed to update file status");
4341                 return FALSE;
4342         }
4344         return TRUE;
4347 static enum request
4348 status_request(struct view *view, enum request request, struct line *line)
4350         struct status *status = line->data;
4352         switch (request) {
4353         case REQ_STATUS_UPDATE:
4354                 if (!status_update(view))
4355                         return REQ_NONE;
4356                 break;
4358         case REQ_STATUS_MERGE:
4359                 if (!status || status->status != 'U') {
4360                         report("Merging only possible for files with unmerged status ('U').");
4361                         return REQ_NONE;
4362                 }
4363                 open_mergetool(status->new.name);
4364                 break;
4366         case REQ_EDIT:
4367                 if (!status)
4368                         return request;
4370                 open_editor(status->status != '?', status->new.name);
4371                 break;
4373         case REQ_VIEW_BLAME:
4374                 if (status) {
4375                         string_copy(opt_file, status->new.name);
4376                         opt_ref[0] = 0;
4377                 }
4378                 return request;
4380         case REQ_ENTER:
4381                 /* After returning the status view has been split to
4382                  * show the stage view. No further reloading is
4383                  * necessary. */
4384                 status_enter(view, line);
4385                 return REQ_NONE;
4387         case REQ_REFRESH:
4388                 /* Simply reload the view. */
4389                 break;
4391         default:
4392                 return request;
4393         }
4395         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4397         return REQ_NONE;
4400 static void
4401 status_select(struct view *view, struct line *line)
4403         struct status *status = line->data;
4404         char file[SIZEOF_STR] = "all files";
4405         char *text;
4406         char *key;
4408         if (status && !string_format(file, "'%s'", status->new.name))
4409                 return;
4411         if (!status && line[1].type == LINE_STAT_NONE)
4412                 line++;
4414         switch (line->type) {
4415         case LINE_STAT_STAGED:
4416                 text = "Press %s to unstage %s for commit";
4417                 break;
4419         case LINE_STAT_UNSTAGED:
4420                 text = "Press %s to stage %s for commit";
4421                 break;
4423         case LINE_STAT_UNTRACKED:
4424                 text = "Press %s to stage %s for addition";
4425                 break;
4427         case LINE_STAT_HEAD:
4428         case LINE_STAT_NONE:
4429                 text = "Nothing to update";
4430                 break;
4432         default:
4433                 die("line type %d not handled in switch", line->type);
4434         }
4436         if (status && status->status == 'U') {
4437                 text = "Press %s to resolve conflict in %s";
4438                 key = get_key(REQ_STATUS_MERGE);
4440         } else {
4441                 key = get_key(REQ_STATUS_UPDATE);
4442         }
4444         string_format(view->ref, text, key, file);
4447 static bool
4448 status_grep(struct view *view, struct line *line)
4450         struct status *status = line->data;
4451         enum { S_STATUS, S_NAME, S_END } state;
4452         char buf[2] = "?";
4453         regmatch_t pmatch;
4455         if (!status)
4456                 return FALSE;
4458         for (state = S_STATUS; state < S_END; state++) {
4459                 char *text;
4461                 switch (state) {
4462                 case S_NAME:    text = status->new.name;        break;
4463                 case S_STATUS:
4464                         buf[0] = status->status;
4465                         text = buf;
4466                         break;
4468                 default:
4469                         return FALSE;
4470                 }
4472                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4473                         return TRUE;
4474         }
4476         return FALSE;
4479 static struct view_ops status_ops = {
4480         "file",
4481         status_open,
4482         NULL,
4483         status_draw,
4484         status_request,
4485         status_grep,
4486         status_select,
4487 };
4490 static bool
4491 stage_diff_line(FILE *pipe, struct line *line)
4493         char *buf = line->data;
4494         size_t bufsize = strlen(buf);
4495         size_t written = 0;
4497         while (!ferror(pipe) && written < bufsize) {
4498                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4499         }
4501         fputc('\n', pipe);
4503         return written == bufsize;
4506 static bool
4507 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4509         while (line < end) {
4510                 if (!stage_diff_line(pipe, line++))
4511                         return FALSE;
4512                 if (line->type == LINE_DIFF_CHUNK ||
4513                     line->type == LINE_DIFF_HEADER)
4514                         break;
4515         }
4517         return TRUE;
4520 static struct line *
4521 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4523         for (; view->line < line; line--)
4524                 if (line->type == type)
4525                         return line;
4527         return NULL;
4530 static bool
4531 stage_update_chunk(struct view *view, struct line *chunk)
4533         char cmd[SIZEOF_STR];
4534         size_t cmdsize = 0;
4535         struct line *diff_hdr;
4536         FILE *pipe;
4538         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4539         if (!diff_hdr)
4540                 return FALSE;
4542         if (opt_cdup[0] &&
4543             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4544                 return FALSE;
4546         if (!string_format_from(cmd, &cmdsize,
4547                                 "git apply --whitespace=nowarn --cached %s - && "
4548                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4549                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4550                 return FALSE;
4552         pipe = popen(cmd, "w");
4553         if (!pipe)
4554                 return FALSE;
4556         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4557             !stage_diff_write(pipe, chunk, view->line + view->lines))
4558                 chunk = NULL;
4560         pclose(pipe);
4562         return chunk ? TRUE : FALSE;
4565 static bool
4566 stage_update(struct view *view, struct line *line)
4568         struct line *chunk = NULL;
4570         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4571                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4573         if (chunk) {
4574                 if (!stage_update_chunk(view, chunk)) {
4575                         report("Failed to apply chunk");
4576                         return FALSE;
4577                 }
4579         } else if (!stage_status.status) {
4580                 view = VIEW(REQ_VIEW_STATUS);
4582                 for (line = view->line; line < view->line + view->lines; line++)
4583                         if (line->type == stage_line_type)
4584                                 break;
4586                 if (!status_update_files(view, line + 1)) {
4587                         report("Failed to update files");
4588                         return FALSE;
4589                 }
4591         } else if (!status_update_file(&stage_status, stage_line_type)) {
4592                 report("Failed to update file");
4593                 return FALSE;
4594         }
4596         return TRUE;
4599 static enum request
4600 stage_request(struct view *view, enum request request, struct line *line)
4602         switch (request) {
4603         case REQ_STATUS_UPDATE:
4604                 if (!stage_update(view, line))
4605                         return REQ_NONE;
4606                 break;
4608         case REQ_EDIT:
4609                 if (!stage_status.new.name[0])
4610                         return request;
4612                 open_editor(stage_status.status != '?', stage_status.new.name);
4613                 break;
4615         case REQ_REFRESH:
4616                 /* Reload everything ... */
4617                 break;
4619         case REQ_VIEW_BLAME:
4620                 if (stage_status.new.name[0]) {
4621                         string_copy(opt_file, stage_status.new.name);
4622                         opt_ref[0] = 0;
4623                 }
4624                 return request;
4626         case REQ_ENTER:
4627                 return pager_request(view, request, line);
4629         default:
4630                 return request;
4631         }
4633         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4635         /* Check whether the staged entry still exists, and close the
4636          * stage view if it doesn't. */
4637         if (!status_exists(&stage_status, stage_line_type))
4638                 return REQ_VIEW_CLOSE;
4640         if (stage_line_type == LINE_STAT_UNTRACKED)
4641                 opt_pipe = fopen(stage_status.new.name, "r");
4642         else
4643                 string_copy(opt_cmd, view->cmd);
4644         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4646         return REQ_NONE;
4649 static struct view_ops stage_ops = {
4650         "line",
4651         NULL,
4652         pager_read,
4653         pager_draw,
4654         stage_request,
4655         pager_grep,
4656         pager_select,
4657 };
4660 /*
4661  * Revision graph
4662  */
4664 struct commit {
4665         char id[SIZEOF_REV];            /* SHA1 ID. */
4666         char title[128];                /* First line of the commit message. */
4667         char author[75];                /* Author of the commit. */
4668         struct tm time;                 /* Date from the author ident. */
4669         struct ref **refs;              /* Repository references. */
4670         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4671         size_t graph_size;              /* The width of the graph array. */
4672         bool has_parents;               /* Rewritten --parents seen. */
4673 };
4675 /* Size of rev graph with no  "padding" columns */
4676 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4678 struct rev_graph {
4679         struct rev_graph *prev, *next, *parents;
4680         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4681         size_t size;
4682         struct commit *commit;
4683         size_t pos;
4684         unsigned int boundary:1;
4685 };
4687 /* Parents of the commit being visualized. */
4688 static struct rev_graph graph_parents[4];
4690 /* The current stack of revisions on the graph. */
4691 static struct rev_graph graph_stacks[4] = {
4692         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4693         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4694         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4695         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4696 };
4698 static inline bool
4699 graph_parent_is_merge(struct rev_graph *graph)
4701         return graph->parents->size > 1;
4704 static inline void
4705 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4707         struct commit *commit = graph->commit;
4709         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4710                 commit->graph[commit->graph_size++] = symbol;
4713 static void
4714 done_rev_graph(struct rev_graph *graph)
4716         if (graph_parent_is_merge(graph) &&
4717             graph->pos < graph->size - 1 &&
4718             graph->next->size == graph->size + graph->parents->size - 1) {
4719                 size_t i = graph->pos + graph->parents->size - 1;
4721                 graph->commit->graph_size = i * 2;
4722                 while (i < graph->next->size - 1) {
4723                         append_to_rev_graph(graph, ' ');
4724                         append_to_rev_graph(graph, '\\');
4725                         i++;
4726                 }
4727         }
4729         graph->size = graph->pos = 0;
4730         graph->commit = NULL;
4731         memset(graph->parents, 0, sizeof(*graph->parents));
4734 static void
4735 push_rev_graph(struct rev_graph *graph, char *parent)
4737         int i;
4739         /* "Collapse" duplicate parents lines.
4740          *
4741          * FIXME: This needs to also update update the drawn graph but
4742          * for now it just serves as a method for pruning graph lines. */
4743         for (i = 0; i < graph->size; i++)
4744                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4745                         return;
4747         if (graph->size < SIZEOF_REVITEMS) {
4748                 string_copy_rev(graph->rev[graph->size++], parent);
4749         }
4752 static chtype
4753 get_rev_graph_symbol(struct rev_graph *graph)
4755         chtype symbol;
4757         if (graph->boundary)
4758                 symbol = REVGRAPH_BOUND;
4759         else if (graph->parents->size == 0)
4760                 symbol = REVGRAPH_INIT;
4761         else if (graph_parent_is_merge(graph))
4762                 symbol = REVGRAPH_MERGE;
4763         else if (graph->pos >= graph->size)
4764                 symbol = REVGRAPH_BRANCH;
4765         else
4766                 symbol = REVGRAPH_COMMIT;
4768         return symbol;
4771 static void
4772 draw_rev_graph(struct rev_graph *graph)
4774         struct rev_filler {
4775                 chtype separator, line;
4776         };
4777         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4778         static struct rev_filler fillers[] = {
4779                 { ' ',  '|' },
4780                 { '`',  '.' },
4781                 { '\'', ' ' },
4782                 { '/',  ' ' },
4783         };
4784         chtype symbol = get_rev_graph_symbol(graph);
4785         struct rev_filler *filler;
4786         size_t i;
4788         if (opt_line_graphics)
4789                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4791         filler = &fillers[DEFAULT];
4793         for (i = 0; i < graph->pos; i++) {
4794                 append_to_rev_graph(graph, filler->line);
4795                 if (graph_parent_is_merge(graph->prev) &&
4796                     graph->prev->pos == i)
4797                         filler = &fillers[RSHARP];
4799                 append_to_rev_graph(graph, filler->separator);
4800         }
4802         /* Place the symbol for this revision. */
4803         append_to_rev_graph(graph, symbol);
4805         if (graph->prev->size > graph->size)
4806                 filler = &fillers[RDIAG];
4807         else
4808                 filler = &fillers[DEFAULT];
4810         i++;
4812         for (; i < graph->size; i++) {
4813                 append_to_rev_graph(graph, filler->separator);
4814                 append_to_rev_graph(graph, filler->line);
4815                 if (graph_parent_is_merge(graph->prev) &&
4816                     i < graph->prev->pos + graph->parents->size)
4817                         filler = &fillers[RSHARP];
4818                 if (graph->prev->size > graph->size)
4819                         filler = &fillers[LDIAG];
4820         }
4822         if (graph->prev->size > graph->size) {
4823                 append_to_rev_graph(graph, filler->separator);
4824                 if (filler->line != ' ')
4825                         append_to_rev_graph(graph, filler->line);
4826         }
4829 /* Prepare the next rev graph */
4830 static void
4831 prepare_rev_graph(struct rev_graph *graph)
4833         size_t i;
4835         /* First, traverse all lines of revisions up to the active one. */
4836         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4837                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4838                         break;
4840                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4841         }
4843         /* Interleave the new revision parent(s). */
4844         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4845                 push_rev_graph(graph->next, graph->parents->rev[i]);
4847         /* Lastly, put any remaining revisions. */
4848         for (i = graph->pos + 1; i < graph->size; i++)
4849                 push_rev_graph(graph->next, graph->rev[i]);
4852 static void
4853 update_rev_graph(struct rev_graph *graph)
4855         /* If this is the finalizing update ... */
4856         if (graph->commit)
4857                 prepare_rev_graph(graph);
4859         /* Graph visualization needs a one rev look-ahead,
4860          * so the first update doesn't visualize anything. */
4861         if (!graph->prev->commit)
4862                 return;
4864         draw_rev_graph(graph->prev);
4865         done_rev_graph(graph->prev->prev);
4869 /*
4870  * Main view backend
4871  */
4873 static bool
4874 main_draw(struct view *view, struct line *line, unsigned int lineno)
4876         struct commit *commit = line->data;
4878         if (!*commit->author)
4879                 return FALSE;
4881         if (opt_date && draw_date(view, &commit->time))
4882                 return TRUE;
4884         if (opt_author &&
4885             draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4886                 return TRUE;
4888         if (opt_rev_graph && commit->graph_size &&
4889             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4890                 return TRUE;
4892         if (opt_show_refs && commit->refs) {
4893                 size_t i = 0;
4895                 do {
4896                         enum line_type type;
4898                         if (commit->refs[i]->head)
4899                                 type = LINE_MAIN_HEAD;
4900                         else if (commit->refs[i]->ltag)
4901                                 type = LINE_MAIN_LOCAL_TAG;
4902                         else if (commit->refs[i]->tag)
4903                                 type = LINE_MAIN_TAG;
4904                         else if (commit->refs[i]->tracked)
4905                                 type = LINE_MAIN_TRACKED;
4906                         else if (commit->refs[i]->remote)
4907                                 type = LINE_MAIN_REMOTE;
4908                         else
4909                                 type = LINE_MAIN_REF;
4911                         if (draw_text(view, type, "[", TRUE) ||
4912                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
4913                             draw_text(view, type, "]", TRUE))
4914                                 return TRUE;
4916                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4917                                 return TRUE;
4918                 } while (commit->refs[i++]->next);
4919         }
4921         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4922         return TRUE;
4925 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4926 static bool
4927 main_read(struct view *view, char *line)
4929         static struct rev_graph *graph = graph_stacks;
4930         enum line_type type;
4931         struct commit *commit;
4933         if (!line) {
4934                 if (!view->lines && !view->parent)
4935                         die("No revisions match the given arguments.");
4936                 update_rev_graph(graph);
4937                 return TRUE;
4938         }
4940         type = get_line_type(line);
4941         if (type == LINE_COMMIT) {
4942                 commit = calloc(1, sizeof(struct commit));
4943                 if (!commit)
4944                         return FALSE;
4946                 line += STRING_SIZE("commit ");
4947                 if (*line == '-') {
4948                         graph->boundary = 1;
4949                         line++;
4950                 }
4952                 string_copy_rev(commit->id, line);
4953                 commit->refs = get_refs(commit->id);
4954                 graph->commit = commit;
4955                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4957                 while ((line = strchr(line, ' '))) {
4958                         line++;
4959                         push_rev_graph(graph->parents, line);
4960                         commit->has_parents = TRUE;
4961                 }
4962                 return TRUE;
4963         }
4965         if (!view->lines)
4966                 return TRUE;
4967         commit = view->line[view->lines - 1].data;
4969         switch (type) {
4970         case LINE_PARENT:
4971                 if (commit->has_parents)
4972                         break;
4973                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4974                 break;
4976         case LINE_AUTHOR:
4977         {
4978                 /* Parse author lines where the name may be empty:
4979                  *      author  <email@address.tld> 1138474660 +0100
4980                  */
4981                 char *ident = line + STRING_SIZE("author ");
4982                 char *nameend = strchr(ident, '<');
4983                 char *emailend = strchr(ident, '>');
4985                 if (!nameend || !emailend)
4986                         break;
4988                 update_rev_graph(graph);
4989                 graph = graph->next;
4991                 *nameend = *emailend = 0;
4992                 ident = chomp_string(ident);
4993                 if (!*ident) {
4994                         ident = chomp_string(nameend + 1);
4995                         if (!*ident)
4996                                 ident = "Unknown";
4997                 }
4999                 string_ncopy(commit->author, ident, strlen(ident));
5001                 /* Parse epoch and timezone */
5002                 if (emailend[1] == ' ') {
5003                         char *secs = emailend + 2;
5004                         char *zone = strchr(secs, ' ');
5005                         time_t time = (time_t) atol(secs);
5007                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5008                                 long tz;
5010                                 zone++;
5011                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5012                                 tz += ('0' - zone[2]) * 60 * 60;
5013                                 tz += ('0' - zone[3]) * 60;
5014                                 tz += ('0' - zone[4]) * 60;
5016                                 if (zone[0] == '-')
5017                                         tz = -tz;
5019                                 time -= tz;
5020                         }
5022                         gmtime_r(&time, &commit->time);
5023                 }
5024                 break;
5025         }
5026         default:
5027                 /* Fill in the commit title if it has not already been set. */
5028                 if (commit->title[0])
5029                         break;
5031                 /* Require titles to start with a non-space character at the
5032                  * offset used by git log. */
5033                 if (strncmp(line, "    ", 4))
5034                         break;
5035                 line += 4;
5036                 /* Well, if the title starts with a whitespace character,
5037                  * try to be forgiving.  Otherwise we end up with no title. */
5038                 while (isspace(*line))
5039                         line++;
5040                 if (*line == '\0')
5041                         break;
5042                 /* FIXME: More graceful handling of titles; append "..." to
5043                  * shortened titles, etc. */
5045                 string_ncopy(commit->title, line, strlen(line));
5046         }
5048         return TRUE;
5051 static enum request
5052 main_request(struct view *view, enum request request, struct line *line)
5054         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5056         if (request == REQ_ENTER)
5057                 open_view(view, REQ_VIEW_DIFF, flags);
5058         else
5059                 return request;
5061         return REQ_NONE;
5064 static bool
5065 grep_refs(struct ref **refs, regex_t *regex)
5067         regmatch_t pmatch;
5068         size_t i = 0;
5070         if (!refs)
5071                 return FALSE;
5072         do {
5073                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5074                         return TRUE;
5075         } while (refs[i++]->next);
5077         return FALSE;
5080 static bool
5081 main_grep(struct view *view, struct line *line)
5083         struct commit *commit = line->data;
5084         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5085         char buf[DATE_COLS + 1];
5086         regmatch_t pmatch;
5088         for (state = S_TITLE; state < S_END; state++) {
5089                 char *text;
5091                 switch (state) {
5092                 case S_TITLE:   text = commit->title;   break;
5093                 case S_AUTHOR:
5094                         if (!opt_author)
5095                                 continue;
5096                         text = commit->author;
5097                         break;
5098                 case S_DATE:
5099                         if (!opt_date)
5100                                 continue;
5101                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5102                                 continue;
5103                         text = buf;
5104                         break;
5105                 case S_REFS:
5106                         if (!opt_show_refs)
5107                                 continue;
5108                         if (grep_refs(commit->refs, view->regex) == TRUE)
5109                                 return TRUE;
5110                         continue;
5111                 default:
5112                         return FALSE;
5113                 }
5115                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5116                         return TRUE;
5117         }
5119         return FALSE;
5122 static void
5123 main_select(struct view *view, struct line *line)
5125         struct commit *commit = line->data;
5127         string_copy_rev(view->ref, commit->id);
5128         string_copy_rev(ref_commit, view->ref);
5131 static struct view_ops main_ops = {
5132         "commit",
5133         NULL,
5134         main_read,
5135         main_draw,
5136         main_request,
5137         main_grep,
5138         main_select,
5139 };
5142 /*
5143  * Unicode / UTF-8 handling
5144  *
5145  * NOTE: Much of the following code for dealing with unicode is derived from
5146  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5147  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5148  */
5150 /* I've (over)annotated a lot of code snippets because I am not entirely
5151  * confident that the approach taken by this small UTF-8 interface is correct.
5152  * --jonas */
5154 static inline int
5155 unicode_width(unsigned long c)
5157         if (c >= 0x1100 &&
5158            (c <= 0x115f                         /* Hangul Jamo */
5159             || c == 0x2329
5160             || c == 0x232a
5161             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5162                                                 /* CJK ... Yi */
5163             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5164             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5165             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5166             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5167             || (c >= 0xffe0  && c <= 0xffe6)
5168             || (c >= 0x20000 && c <= 0x2fffd)
5169             || (c >= 0x30000 && c <= 0x3fffd)))
5170                 return 2;
5172         if (c == '\t')
5173                 return opt_tab_size;
5175         return 1;
5178 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5179  * Illegal bytes are set one. */
5180 static const unsigned char utf8_bytes[256] = {
5181         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,
5182         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,
5183         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,
5184         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,
5185         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,
5186         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,
5187         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,
5188         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,
5189 };
5191 /* Decode UTF-8 multi-byte representation into a unicode character. */
5192 static inline unsigned long
5193 utf8_to_unicode(const char *string, size_t length)
5195         unsigned long unicode;
5197         switch (length) {
5198         case 1:
5199                 unicode  =   string[0];
5200                 break;
5201         case 2:
5202                 unicode  =  (string[0] & 0x1f) << 6;
5203                 unicode +=  (string[1] & 0x3f);
5204                 break;
5205         case 3:
5206                 unicode  =  (string[0] & 0x0f) << 12;
5207                 unicode += ((string[1] & 0x3f) << 6);
5208                 unicode +=  (string[2] & 0x3f);
5209                 break;
5210         case 4:
5211                 unicode  =  (string[0] & 0x0f) << 18;
5212                 unicode += ((string[1] & 0x3f) << 12);
5213                 unicode += ((string[2] & 0x3f) << 6);
5214                 unicode +=  (string[3] & 0x3f);
5215                 break;
5216         case 5:
5217                 unicode  =  (string[0] & 0x0f) << 24;
5218                 unicode += ((string[1] & 0x3f) << 18);
5219                 unicode += ((string[2] & 0x3f) << 12);
5220                 unicode += ((string[3] & 0x3f) << 6);
5221                 unicode +=  (string[4] & 0x3f);
5222                 break;
5223         case 6:
5224                 unicode  =  (string[0] & 0x01) << 30;
5225                 unicode += ((string[1] & 0x3f) << 24);
5226                 unicode += ((string[2] & 0x3f) << 18);
5227                 unicode += ((string[3] & 0x3f) << 12);
5228                 unicode += ((string[4] & 0x3f) << 6);
5229                 unicode +=  (string[5] & 0x3f);
5230                 break;
5231         default:
5232                 die("Invalid unicode length");
5233         }
5235         /* Invalid characters could return the special 0xfffd value but NUL
5236          * should be just as good. */
5237         return unicode > 0xffff ? 0 : unicode;
5240 /* Calculates how much of string can be shown within the given maximum width
5241  * and sets trimmed parameter to non-zero value if all of string could not be
5242  * shown. If the reserve flag is TRUE, it will reserve at least one
5243  * trailing character, which can be useful when drawing a delimiter.
5244  *
5245  * Returns the number of bytes to output from string to satisfy max_width. */
5246 static size_t
5247 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5249         const char *start = string;
5250         const char *end = strchr(string, '\0');
5251         unsigned char last_bytes = 0;
5252         size_t last_ucwidth = 0;
5254         *width = 0;
5255         *trimmed = 0;
5257         while (string < end) {
5258                 int c = *(unsigned char *) string;
5259                 unsigned char bytes = utf8_bytes[c];
5260                 size_t ucwidth;
5261                 unsigned long unicode;
5263                 if (string + bytes > end)
5264                         break;
5266                 /* Change representation to figure out whether
5267                  * it is a single- or double-width character. */
5269                 unicode = utf8_to_unicode(string, bytes);
5270                 /* FIXME: Graceful handling of invalid unicode character. */
5271                 if (!unicode)
5272                         break;
5274                 ucwidth = unicode_width(unicode);
5275                 *width  += ucwidth;
5276                 if (*width > max_width) {
5277                         *trimmed = 1;
5278                         *width -= ucwidth;
5279                         if (reserve && *width == max_width) {
5280                                 string -= last_bytes;
5281                                 *width -= last_ucwidth;
5282                         }
5283                         break;
5284                 }
5286                 string  += bytes;
5287                 last_bytes = bytes;
5288                 last_ucwidth = ucwidth;
5289         }
5291         return string - start;
5295 /*
5296  * Status management
5297  */
5299 /* Whether or not the curses interface has been initialized. */
5300 static bool cursed = FALSE;
5302 /* The status window is used for polling keystrokes. */
5303 static WINDOW *status_win;
5305 static bool status_empty = TRUE;
5307 /* Update status and title window. */
5308 static void
5309 report(const char *msg, ...)
5311         struct view *view = display[current_view];
5313         if (input_mode)
5314                 return;
5316         if (!view) {
5317                 char buf[SIZEOF_STR];
5318                 va_list args;
5320                 va_start(args, msg);
5321                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5322                         buf[sizeof(buf) - 1] = 0;
5323                         buf[sizeof(buf) - 2] = '.';
5324                         buf[sizeof(buf) - 3] = '.';
5325                         buf[sizeof(buf) - 4] = '.';
5326                 }
5327                 va_end(args);
5328                 die("%s", buf);
5329         }
5331         if (!status_empty || *msg) {
5332                 va_list args;
5334                 va_start(args, msg);
5336                 wmove(status_win, 0, 0);
5337                 if (*msg) {
5338                         vwprintw(status_win, msg, args);
5339                         status_empty = FALSE;
5340                 } else {
5341                         status_empty = TRUE;
5342                 }
5343                 wclrtoeol(status_win);
5344                 wrefresh(status_win);
5346                 va_end(args);
5347         }
5349         update_view_title(view);
5350         update_display_cursor(view);
5353 /* Controls when nodelay should be in effect when polling user input. */
5354 static void
5355 set_nonblocking_input(bool loading)
5357         static unsigned int loading_views;
5359         if ((loading == FALSE && loading_views-- == 1) ||
5360             (loading == TRUE  && loading_views++ == 0))
5361                 nodelay(status_win, loading);
5364 static void
5365 init_display(void)
5367         int x, y;
5369         /* Initialize the curses library */
5370         if (isatty(STDIN_FILENO)) {
5371                 cursed = !!initscr();
5372         } else {
5373                 /* Leave stdin and stdout alone when acting as a pager. */
5374                 FILE *io = fopen("/dev/tty", "r+");
5376                 if (!io)
5377                         die("Failed to open /dev/tty");
5378                 cursed = !!newterm(NULL, io, io);
5379         }
5381         if (!cursed)
5382                 die("Failed to initialize curses");
5384         nonl();         /* Tell curses not to do NL->CR/NL on output */
5385         cbreak();       /* Take input chars one at a time, no wait for \n */
5386         noecho();       /* Don't echo input */
5387         leaveok(stdscr, TRUE);
5389         if (has_colors())
5390                 init_colors();
5392         getmaxyx(stdscr, y, x);
5393         status_win = newwin(1, 0, y - 1, 0);
5394         if (!status_win)
5395                 die("Failed to create status window");
5397         /* Enable keyboard mapping */
5398         keypad(status_win, TRUE);
5399         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5401         TABSIZE = opt_tab_size;
5402         if (opt_line_graphics) {
5403                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5404         }
5407 static char *
5408 read_prompt(const char *prompt)
5410         enum { READING, STOP, CANCEL } status = READING;
5411         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5412         int pos = 0;
5414         while (status == READING) {
5415                 struct view *view;
5416                 int i, key;
5418                 input_mode = TRUE;
5420                 foreach_view (view, i)
5421                         update_view(view);
5423                 input_mode = FALSE;
5425                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5426                 wclrtoeol(status_win);
5428                 /* Refresh, accept single keystroke of input */
5429                 key = wgetch(status_win);
5430                 switch (key) {
5431                 case KEY_RETURN:
5432                 case KEY_ENTER:
5433                 case '\n':
5434                         status = pos ? STOP : CANCEL;
5435                         break;
5437                 case KEY_BACKSPACE:
5438                         if (pos > 0)
5439                                 pos--;
5440                         else
5441                                 status = CANCEL;
5442                         break;
5444                 case KEY_ESC:
5445                         status = CANCEL;
5446                         break;
5448                 case ERR:
5449                         break;
5451                 default:
5452                         if (pos >= sizeof(buf)) {
5453                                 report("Input string too long");
5454                                 return NULL;
5455                         }
5457                         if (isprint(key))
5458                                 buf[pos++] = (char) key;
5459                 }
5460         }
5462         /* Clear the status window */
5463         status_empty = FALSE;
5464         report("");
5466         if (status == CANCEL)
5467                 return NULL;
5469         buf[pos++] = 0;
5471         return buf;
5474 /*
5475  * Repository references
5476  */
5478 static struct ref *refs = NULL;
5479 static size_t refs_alloc = 0;
5480 static size_t refs_size = 0;
5482 /* Id <-> ref store */
5483 static struct ref ***id_refs = NULL;
5484 static size_t id_refs_alloc = 0;
5485 static size_t id_refs_size = 0;
5487 static struct ref **
5488 get_refs(char *id)
5490         struct ref ***tmp_id_refs;
5491         struct ref **ref_list = NULL;
5492         size_t ref_list_alloc = 0;
5493         size_t ref_list_size = 0;
5494         size_t i;
5496         for (i = 0; i < id_refs_size; i++)
5497                 if (!strcmp(id, id_refs[i][0]->id))
5498                         return id_refs[i];
5500         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5501                                     sizeof(*id_refs));
5502         if (!tmp_id_refs)
5503                 return NULL;
5505         id_refs = tmp_id_refs;
5507         for (i = 0; i < refs_size; i++) {
5508                 struct ref **tmp;
5510                 if (strcmp(id, refs[i].id))
5511                         continue;
5513                 tmp = realloc_items(ref_list, &ref_list_alloc,
5514                                     ref_list_size + 1, sizeof(*ref_list));
5515                 if (!tmp) {
5516                         if (ref_list)
5517                                 free(ref_list);
5518                         return NULL;
5519                 }
5521                 ref_list = tmp;
5522                 if (ref_list_size > 0)
5523                         ref_list[ref_list_size - 1]->next = 1;
5524                 ref_list[ref_list_size] = &refs[i];
5526                 /* XXX: The properties of the commit chains ensures that we can
5527                  * safely modify the shared ref. The repo references will
5528                  * always be similar for the same id. */
5529                 ref_list[ref_list_size]->next = 0;
5530                 ref_list_size++;
5531         }
5533         if (ref_list)
5534                 id_refs[id_refs_size++] = ref_list;
5536         return ref_list;
5539 static int
5540 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5542         struct ref *ref;
5543         bool tag = FALSE;
5544         bool ltag = FALSE;
5545         bool remote = FALSE;
5546         bool tracked = FALSE;
5547         bool check_replace = FALSE;
5548         bool head = FALSE;
5550         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5551                 if (!strcmp(name + namelen - 3, "^{}")) {
5552                         namelen -= 3;
5553                         name[namelen] = 0;
5554                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5555                                 check_replace = TRUE;
5556                 } else {
5557                         ltag = TRUE;
5558                 }
5560                 tag = TRUE;
5561                 namelen -= STRING_SIZE("refs/tags/");
5562                 name    += STRING_SIZE("refs/tags/");
5564         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5565                 remote = TRUE;
5566                 namelen -= STRING_SIZE("refs/remotes/");
5567                 name    += STRING_SIZE("refs/remotes/");
5568                 tracked  = !strcmp(opt_remote, name);
5570         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5571                 namelen -= STRING_SIZE("refs/heads/");
5572                 name    += STRING_SIZE("refs/heads/");
5573                 head     = !strncmp(opt_head, name, namelen);
5575         } else if (!strcmp(name, "HEAD")) {
5576                 opt_no_head = FALSE;
5577                 return OK;
5578         }
5580         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5581                 /* it's an annotated tag, replace the previous sha1 with the
5582                  * resolved commit id; relies on the fact git-ls-remote lists
5583                  * the commit id of an annotated tag right beofre the commit id
5584                  * it points to. */
5585                 refs[refs_size - 1].ltag = ltag;
5586                 string_copy_rev(refs[refs_size - 1].id, id);
5588                 return OK;
5589         }
5590         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5591         if (!refs)
5592                 return ERR;
5594         ref = &refs[refs_size++];
5595         ref->name = malloc(namelen + 1);
5596         if (!ref->name)
5597                 return ERR;
5599         strncpy(ref->name, name, namelen);
5600         ref->name[namelen] = 0;
5601         ref->head = head;
5602         ref->tag = tag;
5603         ref->ltag = ltag;
5604         ref->remote = remote;
5605         ref->tracked = tracked;
5606         string_copy_rev(ref->id, id);
5608         return OK;
5611 static int
5612 load_refs(void)
5614         const char *cmd_env = getenv("TIG_LS_REMOTE");
5615         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5617         return read_properties(popen(cmd, "r"), "\t", read_ref);
5620 static int
5621 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5623         if (!strcmp(name, "i18n.commitencoding"))
5624                 string_ncopy(opt_encoding, value, valuelen);
5626         if (!strcmp(name, "core.editor"))
5627                 string_ncopy(opt_editor, value, valuelen);
5629         /* branch.<head>.remote */
5630         if (*opt_head &&
5631             !strncmp(name, "branch.", 7) &&
5632             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5633             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5634                 string_ncopy(opt_remote, value, valuelen);
5636         if (*opt_head && *opt_remote &&
5637             !strncmp(name, "branch.", 7) &&
5638             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5639             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5640                 size_t from = strlen(opt_remote);
5642                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5643                         value += STRING_SIZE("refs/heads/");
5644                         valuelen -= STRING_SIZE("refs/heads/");
5645                 }
5647                 if (!string_format_from(opt_remote, &from, "/%s", value))
5648                         opt_remote[0] = 0;
5649         }
5651         return OK;
5654 static int
5655 load_git_config(void)
5657         return read_properties(popen(GIT_CONFIG " --list", "r"),
5658                                "=", read_repo_config_option);
5661 static int
5662 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5664         if (!opt_git_dir[0]) {
5665                 string_ncopy(opt_git_dir, name, namelen);
5667         } else if (opt_is_inside_work_tree == -1) {
5668                 /* This can be 3 different values depending on the
5669                  * version of git being used. If git-rev-parse does not
5670                  * understand --is-inside-work-tree it will simply echo
5671                  * the option else either "true" or "false" is printed.
5672                  * Default to true for the unknown case. */
5673                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5675         } else if (opt_cdup[0] == ' ') {
5676                 string_ncopy(opt_cdup, name, namelen);
5677         } else {
5678                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5679                         namelen -= STRING_SIZE("refs/heads/");
5680                         name    += STRING_SIZE("refs/heads/");
5681                         string_ncopy(opt_head, name, namelen);
5682                 }
5683         }
5685         return OK;
5688 static int
5689 load_repo_info(void)
5691         int result;
5692         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5693                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5695         /* XXX: The line outputted by "--show-cdup" can be empty so
5696          * initialize it to something invalid to make it possible to
5697          * detect whether it has been set or not. */
5698         opt_cdup[0] = ' ';
5700         result = read_properties(pipe, "=", read_repo_info);
5701         if (opt_cdup[0] == ' ')
5702                 opt_cdup[0] = 0;
5704         return result;
5707 static int
5708 read_properties(FILE *pipe, const char *separators,
5709                 int (*read_property)(char *, size_t, char *, size_t))
5711         char buffer[BUFSIZ];
5712         char *name;
5713         int state = OK;
5715         if (!pipe)
5716                 return ERR;
5718         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5719                 char *value;
5720                 size_t namelen;
5721                 size_t valuelen;
5723                 name = chomp_string(name);
5724                 namelen = strcspn(name, separators);
5726                 if (name[namelen]) {
5727                         name[namelen] = 0;
5728                         value = chomp_string(name + namelen + 1);
5729                         valuelen = strlen(value);
5731                 } else {
5732                         value = "";
5733                         valuelen = 0;
5734                 }
5736                 state = read_property(name, namelen, value, valuelen);
5737         }
5739         if (state != ERR && ferror(pipe))
5740                 state = ERR;
5742         pclose(pipe);
5744         return state;
5748 /*
5749  * Main
5750  */
5752 static void __NORETURN
5753 quit(int sig)
5755         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5756         if (cursed)
5757                 endwin();
5758         exit(0);
5761 static void __NORETURN
5762 die(const char *err, ...)
5764         va_list args;
5766         endwin();
5768         va_start(args, err);
5769         fputs("tig: ", stderr);
5770         vfprintf(stderr, err, args);
5771         fputs("\n", stderr);
5772         va_end(args);
5774         exit(1);
5777 static void
5778 warn(const char *msg, ...)
5780         va_list args;
5782         va_start(args, msg);
5783         fputs("tig warning: ", stderr);
5784         vfprintf(stderr, msg, args);
5785         fputs("\n", stderr);
5786         va_end(args);
5789 int
5790 main(int argc, char *argv[])
5792         struct view *view;
5793         enum request request;
5794         size_t i;
5796         signal(SIGINT, quit);
5798         if (setlocale(LC_ALL, "")) {
5799                 char *codeset = nl_langinfo(CODESET);
5801                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5802         }
5804         if (load_repo_info() == ERR)
5805                 die("Failed to load repo info.");
5807         if (load_options() == ERR)
5808                 die("Failed to load user config.");
5810         if (load_git_config() == ERR)
5811                 die("Failed to load repo config.");
5813         if (!parse_options(argc, argv))
5814                 return 0;
5816         /* Require a git repository unless when running in pager mode. */
5817         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5818                 die("Not a git repository");
5820         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5821                 opt_utf8 = FALSE;
5823         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5824                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5825                 if (opt_iconv == ICONV_NONE)
5826                         die("Failed to initialize character set conversion");
5827         }
5829         if (*opt_git_dir && load_refs() == ERR)
5830                 die("Failed to load refs.");
5832         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5833                 view->cmd_env = getenv(view->cmd_env);
5835         request = opt_request;
5837         init_display();
5839         while (view_driver(display[current_view], request)) {
5840                 int key;
5841                 int i;
5843                 foreach_view (view, i)
5844                         update_view(view);
5846                 /* Refresh, accept single keystroke of input */
5847                 key = wgetch(status_win);
5849                 /* wgetch() with nodelay() enabled returns ERR when there's no
5850                  * input. */
5851                 if (key == ERR) {
5852                         request = REQ_NONE;
5853                         continue;
5854                 }
5856                 request = get_keybinding(display[current_view]->keymap, key);
5858                 /* Some low-level request handling. This keeps access to
5859                  * status_win restricted. */
5860                 switch (request) {
5861                 case REQ_PROMPT:
5862                 {
5863                         char *cmd = read_prompt(":");
5865                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5866                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5867                                         opt_request = REQ_VIEW_DIFF;
5868                                 } else {
5869                                         opt_request = REQ_VIEW_PAGER;
5870                                 }
5871                                 break;
5872                         }
5874                         request = REQ_NONE;
5875                         break;
5876                 }
5877                 case REQ_SEARCH:
5878                 case REQ_SEARCH_BACK:
5879                 {
5880                         const char *prompt = request == REQ_SEARCH
5881                                            ? "/" : "?";
5882                         char *search = read_prompt(prompt);
5884                         if (search)
5885                                 string_ncopy(opt_search, search, strlen(search));
5886                         else
5887                                 request = REQ_NONE;
5888                         break;
5889                 }
5890                 case REQ_SCREEN_RESIZE:
5891                 {
5892                         int height, width;
5894                         getmaxyx(stdscr, height, width);
5896                         /* Resize the status view and let the view driver take
5897                          * care of resizing the displayed views. */
5898                         wresize(status_win, 1, width);
5899                         mvwin(status_win, height - 1, 0);
5900                         wrefresh(status_win);
5901                         break;
5902                 }
5903                 default:
5904                         break;
5905                 }
5906         }
5908         quit(0);
5910         return 0;