Code

Make the main and blame view share date drawing and date colors
[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 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
97 #define ID_COLS         8
99 /* The default interval between line numbers. */
100 #define NUMBER_INTERVAL 5
102 #define TABSIZE         8
104 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
106 #define NULL_ID         "0000000000000000000000000000000000000000"
108 #ifndef GIT_CONFIG
109 #define GIT_CONFIG "git config"
110 #endif
112 #define TIG_LS_REMOTE \
113         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
115 #define TIG_DIFF_CMD \
116         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
118 #define TIG_LOG_CMD     \
119         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
121 #define TIG_MAIN_CMD \
122         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
124 #define TIG_TREE_CMD    \
125         "git ls-tree %s %s"
127 #define TIG_BLOB_CMD    \
128         "git cat-file blob %s"
130 /* XXX: Needs to be defined to the empty string. */
131 #define TIG_HELP_CMD    ""
132 #define TIG_PAGER_CMD   ""
133 #define TIG_STATUS_CMD  ""
134 #define TIG_STAGE_CMD   ""
135 #define TIG_BLAME_CMD   ""
137 /* Some ascii-shorthands fitted into the ncurses namespace. */
138 #define KEY_TAB         '\t'
139 #define KEY_RETURN      '\r'
140 #define KEY_ESC         27
143 struct ref {
144         char *name;             /* Ref name; tag or head names are shortened. */
145         char id[SIZEOF_REV];    /* Commit SHA1 ID */
146         unsigned int head:1;    /* Is it the current HEAD? */
147         unsigned int tag:1;     /* Is it a tag? */
148         unsigned int ltag:1;    /* If so, is the tag local? */
149         unsigned int remote:1;  /* Is it a remote ref? */
150         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
151         unsigned int next:1;    /* For ref lists: are there more refs? */
152 };
154 static struct ref **get_refs(char *id);
156 struct int_map {
157         const char *name;
158         int namelen;
159         int value;
160 };
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164                  int *value, const char *name, int namelen)
167         int i;
169         for (i = 0; i < map_size; i++)
170                 if (namelen == map[i].namelen &&
171                     !strncasecmp(name, map[i].name, namelen)) {
172                         *value = map[i].value;
173                         return OK;
174                 }
176         return ERR;
180 /*
181  * String helpers
182  */
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187         if (srclen > dstlen - 1)
188                 srclen = dstlen - 1;
190         strncpy(dst, src, srclen);
191         dst[srclen] = 0;
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200         string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
211         int namelen;
213         while (isspace(*name))
214                 name++;
216         namelen = strlen(name) - 1;
217         while (namelen > 0 && isspace(name[namelen]))
218                 name[namelen--] = 0;
220         return name;
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
226         va_list args;
227         size_t pos = bufpos ? *bufpos : 0;
229         va_start(args, fmt);
230         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231         va_end(args);
233         if (bufpos)
234                 *bufpos = pos;
236         return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240         string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243         string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
248         size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252         /* Diff-Header == DIFF_HEADER */
253         for (i = 0; i < len; i++) {
254                 if (toupper(str1[i]) == toupper(str2[i]))
255                         continue;
257                 if (string_enum_sep(str1[i]) &&
258                     string_enum_sep(str2[i]))
259                         continue;
261                 return str1[i] - str2[i];
262         }
264         return 0;
267 /* Shell quoting
268  *
269  * NOTE: The following is a slightly modified copy of the git project's shell
270  * quoting routines found in the quote.c file.
271  *
272  * Help to copy the thing properly quoted for the shell safety.  any single
273  * quote is replaced with '\'', any exclamation point is replaced with '\!',
274  * and the whole thing is enclosed in a
275  *
276  * E.g.
277  *  original     sq_quote     result
278  *  name     ==> name      ==> 'name'
279  *  a b      ==> a b       ==> 'a b'
280  *  a'b      ==> a'\''b    ==> 'a'\''b'
281  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
282  */
284 static size_t
285 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
287         char c;
289 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
291         BUFPUT('\'');
292         while ((c = *src++)) {
293                 if (c == '\'' || c == '!') {
294                         BUFPUT('\'');
295                         BUFPUT('\\');
296                         BUFPUT(c);
297                         BUFPUT('\'');
298                 } else {
299                         BUFPUT(c);
300                 }
301         }
302         BUFPUT('\'');
304         if (bufsize < SIZEOF_STR)
305                 buf[bufsize] = 0;
307         return bufsize;
311 /*
312  * User requests
313  */
315 #define REQ_INFO \
316         /* XXX: Keep the view request first and in sync with views[]. */ \
317         REQ_GROUP("View switching") \
318         REQ_(VIEW_MAIN,         "Show main view"), \
319         REQ_(VIEW_DIFF,         "Show diff view"), \
320         REQ_(VIEW_LOG,          "Show log view"), \
321         REQ_(VIEW_TREE,         "Show tree view"), \
322         REQ_(VIEW_BLOB,         "Show blob view"), \
323         REQ_(VIEW_BLAME,        "Show blame view"), \
324         REQ_(VIEW_HELP,         "Show help page"), \
325         REQ_(VIEW_PAGER,        "Show pager view"), \
326         REQ_(VIEW_STATUS,       "Show status view"), \
327         REQ_(VIEW_STAGE,        "Show stage view"), \
328         \
329         REQ_GROUP("View manipulation") \
330         REQ_(ENTER,             "Enter current line and scroll"), \
331         REQ_(NEXT,              "Move to next"), \
332         REQ_(PREVIOUS,          "Move to previous"), \
333         REQ_(VIEW_NEXT,         "Move focus to next view"), \
334         REQ_(REFRESH,           "Reload and refresh"), \
335         REQ_(MAXIMIZE,          "Maximize the current view"), \
336         REQ_(VIEW_CLOSE,        "Close the current view"), \
337         REQ_(QUIT,              "Close all views and quit"), \
338         \
339         REQ_GROUP("Cursor navigation") \
340         REQ_(MOVE_UP,           "Move cursor one line up"), \
341         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
342         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
343         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
344         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
345         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
346         \
347         REQ_GROUP("Scrolling") \
348         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
349         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
350         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
351         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
352         \
353         REQ_GROUP("Searching") \
354         REQ_(SEARCH,            "Search the view"), \
355         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
356         REQ_(FIND_NEXT,         "Find next search match"), \
357         REQ_(FIND_PREV,         "Find previous search match"), \
358         \
359         REQ_GROUP("Misc") \
360         REQ_(PROMPT,            "Bring up the prompt"), \
361         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
362         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
363         REQ_(SHOW_VERSION,      "Show version information"), \
364         REQ_(STOP_LOADING,      "Stop all loading views"), \
365         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
366         REQ_(TOGGLE_DATE,       "Toggle date display"), \
367         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
368         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
369         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
370         REQ_(STATUS_UPDATE,     "Update file status"), \
371         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
372         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
373         REQ_(EDIT,              "Open in editor"), \
374         REQ_(NONE,              "Do nothing")
377 /* User action requests. */
378 enum request {
379 #define REQ_GROUP(help)
380 #define REQ_(req, help) REQ_##req
382         /* Offset all requests to avoid conflicts with ncurses getch values. */
383         REQ_OFFSET = KEY_MAX + 1,
384         REQ_INFO
386 #undef  REQ_GROUP
387 #undef  REQ_
388 };
390 struct request_info {
391         enum request request;
392         char *name;
393         int namelen;
394         char *help;
395 };
397 static struct request_info req_info[] = {
398 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
399 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
400         REQ_INFO
401 #undef  REQ_GROUP
402 #undef  REQ_
403 };
405 static enum request
406 get_request(const char *name)
408         int namelen = strlen(name);
409         int i;
411         for (i = 0; i < ARRAY_SIZE(req_info); i++)
412                 if (req_info[i].namelen == namelen &&
413                     !string_enum_compare(req_info[i].name, name, namelen))
414                         return req_info[i].request;
416         return REQ_NONE;
420 /*
421  * Options
422  */
424 static const char usage[] =
425 "tig " TIG_VERSION " (" __DATE__ ")\n"
426 "\n"
427 "Usage: tig        [options] [revs] [--] [paths]\n"
428 "   or: tig show   [options] [revs] [--] [paths]\n"
429 "   or: tig blame  [rev] path\n"
430 "   or: tig status\n"
431 "   or: tig <      [git command output]\n"
432 "\n"
433 "Options:\n"
434 "  -v, --version   Show version and exit\n"
435 "  -h, --help      Show help message and exit";
437 /* Option and state variables. */
438 static bool opt_date                    = TRUE;
439 static bool opt_author                  = TRUE;
440 static bool opt_line_number             = FALSE;
441 static bool opt_rev_graph               = FALSE;
442 static bool opt_show_refs               = TRUE;
443 static int opt_num_interval             = NUMBER_INTERVAL;
444 static int opt_tab_size                 = TABSIZE;
445 static enum request opt_request         = REQ_VIEW_MAIN;
446 static char opt_cmd[SIZEOF_STR]         = "";
447 static char opt_path[SIZEOF_STR]        = "";
448 static char opt_file[SIZEOF_STR]        = "";
449 static char opt_ref[SIZEOF_REF]         = "";
450 static char opt_head[SIZEOF_REF]        = "";
451 static char opt_remote[SIZEOF_REF]      = "";
452 static bool opt_no_head                 = TRUE;
453 static FILE *opt_pipe                   = NULL;
454 static char opt_encoding[20]            = "UTF-8";
455 static bool opt_utf8                    = TRUE;
456 static char opt_codeset[20]             = "UTF-8";
457 static iconv_t opt_iconv                = ICONV_NONE;
458 static char opt_search[SIZEOF_STR]      = "";
459 static char opt_cdup[SIZEOF_STR]        = "";
460 static char opt_git_dir[SIZEOF_STR]     = "";
461 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
462 static char opt_editor[SIZEOF_STR]      = "";
464 static bool
465 parse_options(int argc, char *argv[])
467         size_t buf_size;
468         char *subcommand;
469         bool seen_dashdash = FALSE;
470         int i;
472         if (!isatty(STDIN_FILENO)) {
473                 opt_request = REQ_VIEW_PAGER;
474                 opt_pipe = stdin;
475                 return TRUE;
476         }
478         if (argc <= 1)
479                 return TRUE;
481         subcommand = argv[1];
482         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
483                 opt_request = REQ_VIEW_STATUS;
484                 if (!strcmp(subcommand, "-S"))
485                         warn("`-S' has been deprecated; use `tig status' instead");
486                 if (argc > 2)
487                         warn("ignoring arguments after `%s'", subcommand);
488                 return TRUE;
490         } else if (!strcmp(subcommand, "blame")) {
491                 opt_request = REQ_VIEW_BLAME;
492                 if (argc <= 2 || argc > 4)
493                         die("invalid number of options to blame\n\n%s", usage);
495                 i = 2;
496                 if (argc == 4) {
497                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
498                         i++;
499                 }
501                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
502                 return TRUE;
504         } else if (!strcmp(subcommand, "show")) {
505                 opt_request = REQ_VIEW_DIFF;
507         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
508                 opt_request = subcommand[0] == 'l'
509                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510                 warn("`tig %s' has been deprecated", subcommand);
512         } else {
513                 subcommand = NULL;
514         }
516         if (!subcommand)
517                 /* XXX: This is vulnerable to the user overriding
518                  * options required for the main view parser. */
519                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
520         else
521                 string_format(opt_cmd, "git %s", subcommand);
523         buf_size = strlen(opt_cmd);
525         for (i = 1 + !!subcommand; i < argc; i++) {
526                 char *opt = argv[i];
528                 if (seen_dashdash || !strcmp(opt, "--")) {
529                         seen_dashdash = TRUE;
531                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
532                         printf("tig version %s\n", TIG_VERSION);
533                         return FALSE;
535                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
536                         printf("%s\n", usage);
537                         return FALSE;
538                 }
540                 opt_cmd[buf_size++] = ' ';
541                 buf_size = sq_quote(opt_cmd, buf_size, opt);
542                 if (buf_size >= sizeof(opt_cmd))
543                         die("command too long");
544         }
546         opt_cmd[buf_size] = 0;
548         return TRUE;
552 /*
553  * Line-oriented content detection.
554  */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
558 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
559 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
560 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
561 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
562 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
568 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
570 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
572 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
573 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
579 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
580 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
581 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
582 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
586 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
587 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
590 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
591 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
592 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
593 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
599 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
601 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
604 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
607 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
612 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
614 enum line_type {
615 #define LINE(type, line, fg, bg, attr) \
616         LINE_##type
617         LINE_INFO
618 #undef  LINE
619 };
621 struct line_info {
622         const char *name;       /* Option name. */
623         int namelen;            /* Size of option name. */
624         const char *line;       /* The start of line to match. */
625         int linelen;            /* Size of string to match. */
626         int fg, bg, attr;       /* Color and text attributes for the lines. */
627 };
629 static struct line_info line_info[] = {
630 #define LINE(type, line, fg, bg, attr) \
631         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
632         LINE_INFO
633 #undef  LINE
634 };
636 static enum line_type
637 get_line_type(char *line)
639         int linelen = strlen(line);
640         enum line_type type;
642         for (type = 0; type < ARRAY_SIZE(line_info); type++)
643                 /* Case insensitive search matches Signed-off-by lines better. */
644                 if (linelen >= line_info[type].linelen &&
645                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
646                         return type;
648         return LINE_DEFAULT;
651 static inline int
652 get_line_attr(enum line_type type)
654         assert(type < ARRAY_SIZE(line_info));
655         return COLOR_PAIR(type) | line_info[type].attr;
658 static struct line_info *
659 get_line_info(char *name)
661         size_t namelen = strlen(name);
662         enum line_type type;
664         for (type = 0; type < ARRAY_SIZE(line_info); type++)
665                 if (namelen == line_info[type].namelen &&
666                     !string_enum_compare(line_info[type].name, name, namelen))
667                         return &line_info[type];
669         return NULL;
672 static void
673 init_colors(void)
675         int default_bg = line_info[LINE_DEFAULT].bg;
676         int default_fg = line_info[LINE_DEFAULT].fg;
677         enum line_type type;
679         start_color();
681         if (assume_default_colors(default_fg, default_bg) == ERR) {
682                 default_bg = COLOR_BLACK;
683                 default_fg = COLOR_WHITE;
684         }
686         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
687                 struct line_info *info = &line_info[type];
688                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
689                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
691                 init_pair(type, fg, bg);
692         }
695 struct line {
696         enum line_type type;
698         /* State flags */
699         unsigned int selected:1;
700         unsigned int dirty:1;
702         void *data;             /* User data */
703 };
706 /*
707  * Keys
708  */
710 struct keybinding {
711         int alias;
712         enum request request;
713         struct keybinding *next;
714 };
716 static struct keybinding default_keybindings[] = {
717         /* View switching */
718         { 'm',          REQ_VIEW_MAIN },
719         { 'd',          REQ_VIEW_DIFF },
720         { 'l',          REQ_VIEW_LOG },
721         { 't',          REQ_VIEW_TREE },
722         { 'f',          REQ_VIEW_BLOB },
723         { 'B',          REQ_VIEW_BLAME },
724         { 'p',          REQ_VIEW_PAGER },
725         { 'h',          REQ_VIEW_HELP },
726         { 'S',          REQ_VIEW_STATUS },
727         { 'c',          REQ_VIEW_STAGE },
729         /* View manipulation */
730         { 'q',          REQ_VIEW_CLOSE },
731         { KEY_TAB,      REQ_VIEW_NEXT },
732         { KEY_RETURN,   REQ_ENTER },
733         { KEY_UP,       REQ_PREVIOUS },
734         { KEY_DOWN,     REQ_NEXT },
735         { 'R',          REQ_REFRESH },
736         { 'M',          REQ_MAXIMIZE },
738         /* Cursor navigation */
739         { 'k',          REQ_MOVE_UP },
740         { 'j',          REQ_MOVE_DOWN },
741         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
742         { KEY_END,      REQ_MOVE_LAST_LINE },
743         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
744         { ' ',          REQ_MOVE_PAGE_DOWN },
745         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
746         { 'b',          REQ_MOVE_PAGE_UP },
747         { '-',          REQ_MOVE_PAGE_UP },
749         /* Scrolling */
750         { KEY_IC,       REQ_SCROLL_LINE_UP },
751         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
752         { 'w',          REQ_SCROLL_PAGE_UP },
753         { 's',          REQ_SCROLL_PAGE_DOWN },
755         /* Searching */
756         { '/',          REQ_SEARCH },
757         { '?',          REQ_SEARCH_BACK },
758         { 'n',          REQ_FIND_NEXT },
759         { 'N',          REQ_FIND_PREV },
761         /* Misc */
762         { 'Q',          REQ_QUIT },
763         { 'z',          REQ_STOP_LOADING },
764         { 'v',          REQ_SHOW_VERSION },
765         { 'r',          REQ_SCREEN_REDRAW },
766         { '.',          REQ_TOGGLE_LINENO },
767         { 'D',          REQ_TOGGLE_DATE },
768         { 'A',          REQ_TOGGLE_AUTHOR },
769         { 'g',          REQ_TOGGLE_REV_GRAPH },
770         { 'F',          REQ_TOGGLE_REFS },
771         { ':',          REQ_PROMPT },
772         { 'u',          REQ_STATUS_UPDATE },
773         { 'M',          REQ_STATUS_MERGE },
774         { ',',          REQ_TREE_PARENT },
775         { 'e',          REQ_EDIT },
777         /* Using the ncurses SIGWINCH handler. */
778         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
779 };
781 #define KEYMAP_INFO \
782         KEYMAP_(GENERIC), \
783         KEYMAP_(MAIN), \
784         KEYMAP_(DIFF), \
785         KEYMAP_(LOG), \
786         KEYMAP_(TREE), \
787         KEYMAP_(BLOB), \
788         KEYMAP_(BLAME), \
789         KEYMAP_(PAGER), \
790         KEYMAP_(HELP), \
791         KEYMAP_(STATUS), \
792         KEYMAP_(STAGE)
794 enum keymap {
795 #define KEYMAP_(name) KEYMAP_##name
796         KEYMAP_INFO
797 #undef  KEYMAP_
798 };
800 static struct int_map keymap_table[] = {
801 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
802         KEYMAP_INFO
803 #undef  KEYMAP_
804 };
806 #define set_keymap(map, name) \
807         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
809 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
811 static void
812 add_keybinding(enum keymap keymap, enum request request, int key)
814         struct keybinding *keybinding;
816         keybinding = calloc(1, sizeof(*keybinding));
817         if (!keybinding)
818                 die("Failed to allocate keybinding");
820         keybinding->alias = key;
821         keybinding->request = request;
822         keybinding->next = keybindings[keymap];
823         keybindings[keymap] = keybinding;
826 /* Looks for a key binding first in the given map, then in the generic map, and
827  * lastly in the default keybindings. */
828 static enum request
829 get_keybinding(enum keymap keymap, int key)
831         struct keybinding *kbd;
832         int i;
834         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
835                 if (kbd->alias == key)
836                         return kbd->request;
838         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
839                 if (kbd->alias == key)
840                         return kbd->request;
842         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
843                 if (default_keybindings[i].alias == key)
844                         return default_keybindings[i].request;
846         return (enum request) key;
850 struct key {
851         char *name;
852         int value;
853 };
855 static struct key key_table[] = {
856         { "Enter",      KEY_RETURN },
857         { "Space",      ' ' },
858         { "Backspace",  KEY_BACKSPACE },
859         { "Tab",        KEY_TAB },
860         { "Escape",     KEY_ESC },
861         { "Left",       KEY_LEFT },
862         { "Right",      KEY_RIGHT },
863         { "Up",         KEY_UP },
864         { "Down",       KEY_DOWN },
865         { "Insert",     KEY_IC },
866         { "Delete",     KEY_DC },
867         { "Hash",       '#' },
868         { "Home",       KEY_HOME },
869         { "End",        KEY_END },
870         { "PageUp",     KEY_PPAGE },
871         { "PageDown",   KEY_NPAGE },
872         { "F1",         KEY_F(1) },
873         { "F2",         KEY_F(2) },
874         { "F3",         KEY_F(3) },
875         { "F4",         KEY_F(4) },
876         { "F5",         KEY_F(5) },
877         { "F6",         KEY_F(6) },
878         { "F7",         KEY_F(7) },
879         { "F8",         KEY_F(8) },
880         { "F9",         KEY_F(9) },
881         { "F10",        KEY_F(10) },
882         { "F11",        KEY_F(11) },
883         { "F12",        KEY_F(12) },
884 };
886 static int
887 get_key_value(const char *name)
889         int i;
891         for (i = 0; i < ARRAY_SIZE(key_table); i++)
892                 if (!strcasecmp(key_table[i].name, name))
893                         return key_table[i].value;
895         if (strlen(name) == 1 && isprint(*name))
896                 return (int) *name;
898         return ERR;
901 static char *
902 get_key_name(int key_value)
904         static char key_char[] = "'X'";
905         char *seq = NULL;
906         int key;
908         for (key = 0; key < ARRAY_SIZE(key_table); key++)
909                 if (key_table[key].value == key_value)
910                         seq = key_table[key].name;
912         if (seq == NULL &&
913             key_value < 127 &&
914             isprint(key_value)) {
915                 key_char[1] = (char) key_value;
916                 seq = key_char;
917         }
919         return seq ? seq : "'?'";
922 static char *
923 get_key(enum request request)
925         static char buf[BUFSIZ];
926         size_t pos = 0;
927         char *sep = "";
928         int i;
930         buf[pos] = 0;
932         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
933                 struct keybinding *keybinding = &default_keybindings[i];
935                 if (keybinding->request != request)
936                         continue;
938                 if (!string_format_from(buf, &pos, "%s%s", sep,
939                                         get_key_name(keybinding->alias)))
940                         return "Too many keybindings!";
941                 sep = ", ";
942         }
944         return buf;
947 struct run_request {
948         enum keymap keymap;
949         int key;
950         char cmd[SIZEOF_STR];
951 };
953 static struct run_request *run_request;
954 static size_t run_requests;
956 static enum request
957 add_run_request(enum keymap keymap, int key, int argc, char **argv)
959         struct run_request *tmp;
960         struct run_request req = { keymap, key };
961         size_t bufpos;
963         for (bufpos = 0; argc > 0; argc--, argv++)
964                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
965                         return REQ_NONE;
967         req.cmd[bufpos - 1] = 0;
969         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
970         if (!tmp)
971                 return REQ_NONE;
973         run_request = tmp;
974         run_request[run_requests++] = req;
976         return REQ_NONE + run_requests;
979 static struct run_request *
980 get_run_request(enum request request)
982         if (request <= REQ_NONE)
983                 return NULL;
984         return &run_request[request - REQ_NONE - 1];
987 static void
988 add_builtin_run_requests(void)
990         struct {
991                 enum keymap keymap;
992                 int key;
993                 char *argv[1];
994         } reqs[] = {
995                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
996                 { KEYMAP_GENERIC, 'G', { "git gc" } },
997         };
998         int i;
1000         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1001                 enum request req;
1003                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1004                 if (req != REQ_NONE)
1005                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1006         }
1009 /*
1010  * User config file handling.
1011  */
1013 static struct int_map color_map[] = {
1014 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1015         COLOR_MAP(DEFAULT),
1016         COLOR_MAP(BLACK),
1017         COLOR_MAP(BLUE),
1018         COLOR_MAP(CYAN),
1019         COLOR_MAP(GREEN),
1020         COLOR_MAP(MAGENTA),
1021         COLOR_MAP(RED),
1022         COLOR_MAP(WHITE),
1023         COLOR_MAP(YELLOW),
1024 };
1026 #define set_color(color, name) \
1027         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1029 static struct int_map attr_map[] = {
1030 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1031         ATTR_MAP(NORMAL),
1032         ATTR_MAP(BLINK),
1033         ATTR_MAP(BOLD),
1034         ATTR_MAP(DIM),
1035         ATTR_MAP(REVERSE),
1036         ATTR_MAP(STANDOUT),
1037         ATTR_MAP(UNDERLINE),
1038 };
1040 #define set_attribute(attr, name) \
1041         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1043 static int   config_lineno;
1044 static bool  config_errors;
1045 static char *config_msg;
1047 /* Wants: object fgcolor bgcolor [attr] */
1048 static int
1049 option_color_command(int argc, char *argv[])
1051         struct line_info *info;
1053         if (argc != 3 && argc != 4) {
1054                 config_msg = "Wrong number of arguments given to color command";
1055                 return ERR;
1056         }
1058         info = get_line_info(argv[0]);
1059         if (!info) {
1060                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1061                         info = get_line_info("delimiter");
1063                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1064                         info = get_line_info("date");
1066                 } else {
1067                         config_msg = "Unknown color name";
1068                         return ERR;
1069                 }
1070         }
1072         if (set_color(&info->fg, argv[1]) == ERR ||
1073             set_color(&info->bg, argv[2]) == ERR) {
1074                 config_msg = "Unknown color";
1075                 return ERR;
1076         }
1078         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1079                 config_msg = "Unknown attribute";
1080                 return ERR;
1081         }
1083         return OK;
1086 static bool parse_bool(const char *s)
1088         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1089                 !strcmp(s, "yes")) ? TRUE : FALSE;
1092 /* Wants: name = value */
1093 static int
1094 option_set_command(int argc, char *argv[])
1096         if (argc != 3) {
1097                 config_msg = "Wrong number of arguments given to set command";
1098                 return ERR;
1099         }
1101         if (strcmp(argv[1], "=")) {
1102                 config_msg = "No value assigned";
1103                 return ERR;
1104         }
1106         if (!strcmp(argv[0], "show-author")) {
1107                 opt_author = parse_bool(argv[2]);
1108                 return OK;
1109         }
1111         if (!strcmp(argv[0], "show-date")) {
1112                 opt_date = parse_bool(argv[2]);
1113                 return OK;
1114         }
1116         if (!strcmp(argv[0], "show-rev-graph")) {
1117                 opt_rev_graph = parse_bool(argv[2]);
1118                 return OK;
1119         }
1121         if (!strcmp(argv[0], "show-refs")) {
1122                 opt_show_refs = parse_bool(argv[2]);
1123                 return OK;
1124         }
1126         if (!strcmp(argv[0], "show-line-numbers")) {
1127                 opt_line_number = parse_bool(argv[2]);
1128                 return OK;
1129         }
1131         if (!strcmp(argv[0], "line-number-interval")) {
1132                 opt_num_interval = atoi(argv[2]);
1133                 return OK;
1134         }
1136         if (!strcmp(argv[0], "tab-size")) {
1137                 opt_tab_size = atoi(argv[2]);
1138                 return OK;
1139         }
1141         if (!strcmp(argv[0], "commit-encoding")) {
1142                 char *arg = argv[2];
1143                 int delimiter = *arg;
1144                 int i;
1146                 switch (delimiter) {
1147                 case '"':
1148                 case '\'':
1149                         for (arg++, i = 0; arg[i]; i++)
1150                                 if (arg[i] == delimiter) {
1151                                         arg[i] = 0;
1152                                         break;
1153                                 }
1154                 default:
1155                         string_ncopy(opt_encoding, arg, strlen(arg));
1156                         return OK;
1157                 }
1158         }
1160         config_msg = "Unknown variable name";
1161         return ERR;
1164 /* Wants: mode request key */
1165 static int
1166 option_bind_command(int argc, char *argv[])
1168         enum request request;
1169         int keymap;
1170         int key;
1172         if (argc < 3) {
1173                 config_msg = "Wrong number of arguments given to bind command";
1174                 return ERR;
1175         }
1177         if (set_keymap(&keymap, argv[0]) == ERR) {
1178                 config_msg = "Unknown key map";
1179                 return ERR;
1180         }
1182         key = get_key_value(argv[1]);
1183         if (key == ERR) {
1184                 config_msg = "Unknown key";
1185                 return ERR;
1186         }
1188         request = get_request(argv[2]);
1189         if (request == REQ_NONE) {
1190                 const char *obsolete[] = { "cherry-pick" };
1191                 size_t namelen = strlen(argv[2]);
1192                 int i;
1194                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1195                         if (namelen == strlen(obsolete[i]) &&
1196                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1197                                 config_msg = "Obsolete request name";
1198                                 return ERR;
1199                         }
1200                 }
1201         }
1202         if (request == REQ_NONE && *argv[2]++ == '!')
1203                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1204         if (request == REQ_NONE) {
1205                 config_msg = "Unknown request name";
1206                 return ERR;
1207         }
1209         add_keybinding(keymap, request, key);
1211         return OK;
1214 static int
1215 set_option(char *opt, char *value)
1217         char *argv[16];
1218         int valuelen;
1219         int argc = 0;
1221         /* Tokenize */
1222         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1223                 argv[argc++] = value;
1224                 value += valuelen;
1226                 /* Nothing more to tokenize or last available token. */
1227                 if (!*value || argc >= ARRAY_SIZE(argv))
1228                         break;
1230                 *value++ = 0;
1231                 while (isspace(*value))
1232                         value++;
1233         }
1235         if (!strcmp(opt, "color"))
1236                 return option_color_command(argc, argv);
1238         if (!strcmp(opt, "set"))
1239                 return option_set_command(argc, argv);
1241         if (!strcmp(opt, "bind"))
1242                 return option_bind_command(argc, argv);
1244         config_msg = "Unknown option command";
1245         return ERR;
1248 static int
1249 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1251         int status = OK;
1253         config_lineno++;
1254         config_msg = "Internal error";
1256         /* Check for comment markers, since read_properties() will
1257          * only ensure opt and value are split at first " \t". */
1258         optlen = strcspn(opt, "#");
1259         if (optlen == 0)
1260                 return OK;
1262         if (opt[optlen] != 0) {
1263                 config_msg = "No option value";
1264                 status = ERR;
1266         }  else {
1267                 /* Look for comment endings in the value. */
1268                 size_t len = strcspn(value, "#");
1270                 if (len < valuelen) {
1271                         valuelen = len;
1272                         value[valuelen] = 0;
1273                 }
1275                 status = set_option(opt, value);
1276         }
1278         if (status == ERR) {
1279                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1280                         config_lineno, (int) optlen, opt, config_msg);
1281                 config_errors = TRUE;
1282         }
1284         /* Always keep going if errors are encountered. */
1285         return OK;
1288 static void
1289 load_option_file(const char *path)
1291         FILE *file;
1293         /* It's ok that the file doesn't exist. */
1294         file = fopen(path, "r");
1295         if (!file)
1296                 return;
1298         config_lineno = 0;
1299         config_errors = FALSE;
1301         if (read_properties(file, " \t", read_option) == ERR ||
1302             config_errors == TRUE)
1303                 fprintf(stderr, "Errors while loading %s.\n", path);
1306 static int
1307 load_options(void)
1309         char *home = getenv("HOME");
1310         char *tigrc_user = getenv("TIGRC_USER");
1311         char *tigrc_system = getenv("TIGRC_SYSTEM");
1312         char buf[SIZEOF_STR];
1314         add_builtin_run_requests();
1316         if (!tigrc_system) {
1317                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1318                         return ERR;
1319                 tigrc_system = buf;
1320         }
1321         load_option_file(tigrc_system);
1323         if (!tigrc_user) {
1324                 if (!home || !string_format(buf, "%s/.tigrc", home))
1325                         return ERR;
1326                 tigrc_user = buf;
1327         }
1328         load_option_file(tigrc_user);
1330         return OK;
1334 /*
1335  * The viewer
1336  */
1338 struct view;
1339 struct view_ops;
1341 /* The display array of active views and the index of the current view. */
1342 static struct view *display[2];
1343 static unsigned int current_view;
1345 /* Reading from the prompt? */
1346 static bool input_mode = FALSE;
1348 #define foreach_displayed_view(view, i) \
1349         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1351 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1353 /* Current head and commit ID */
1354 static char ref_blob[SIZEOF_REF]        = "";
1355 static char ref_commit[SIZEOF_REF]      = "HEAD";
1356 static char ref_head[SIZEOF_REF]        = "HEAD";
1358 struct view {
1359         const char *name;       /* View name */
1360         const char *cmd_fmt;    /* Default command line format */
1361         const char *cmd_env;    /* Command line set via environment */
1362         const char *id;         /* Points to either of ref_{head,commit,blob} */
1364         struct view_ops *ops;   /* View operations */
1366         enum keymap keymap;     /* What keymap does this view have */
1367         bool git_dir;           /* Whether the view requires a git directory. */
1369         char cmd[SIZEOF_STR];   /* Command buffer */
1370         char ref[SIZEOF_REF];   /* Hovered commit reference */
1371         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1373         int height, width;      /* The width and height of the main window */
1374         WINDOW *win;            /* The main window */
1375         WINDOW *title;          /* The title window living below the main window */
1377         /* Navigation */
1378         unsigned long offset;   /* Offset of the window top */
1379         unsigned long lineno;   /* Current line number */
1381         /* Searching */
1382         char grep[SIZEOF_STR];  /* Search string */
1383         regex_t *regex;         /* Pre-compiled regex */
1385         /* If non-NULL, points to the view that opened this view. If this view
1386          * is closed tig will switch back to the parent view. */
1387         struct view *parent;
1389         /* Buffering */
1390         size_t lines;           /* Total number of lines */
1391         struct line *line;      /* Line index */
1392         size_t line_alloc;      /* Total number of allocated lines */
1393         size_t line_size;       /* Total number of used lines */
1394         unsigned int digits;    /* Number of digits in the lines member. */
1396         /* Loading */
1397         FILE *pipe;
1398         time_t start_time;
1399 };
1401 struct view_ops {
1402         /* What type of content being displayed. Used in the title bar. */
1403         const char *type;
1404         /* Open and reads in all view content. */
1405         bool (*open)(struct view *view);
1406         /* Read one line; updates view->line. */
1407         bool (*read)(struct view *view, char *data);
1408         /* Draw one line; @lineno must be < view->height. */
1409         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1410         /* Depending on view handle a special requests. */
1411         enum request (*request)(struct view *view, enum request request, struct line *line);
1412         /* Search for regex in a line. */
1413         bool (*grep)(struct view *view, struct line *line);
1414         /* Select line */
1415         void (*select)(struct view *view, struct line *line);
1416 };
1418 static struct view_ops pager_ops;
1419 static struct view_ops main_ops;
1420 static struct view_ops tree_ops;
1421 static struct view_ops blob_ops;
1422 static struct view_ops blame_ops;
1423 static struct view_ops help_ops;
1424 static struct view_ops status_ops;
1425 static struct view_ops stage_ops;
1427 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1428         { name, cmd, #env, ref, ops, map, git }
1430 #define VIEW_(id, name, ops, git, ref) \
1431         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1434 static struct view views[] = {
1435         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1436         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1437         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1438         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1439         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1440         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1441         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1442         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1443         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1444         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1445 };
1447 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1448 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1450 #define foreach_view(view, i) \
1451         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1453 #define view_is_displayed(view) \
1454         (view == display[0] || view == display[1])
1456 static int
1457 draw_text(struct view *view, const char *string, int max_len,
1458           bool use_tilde, bool selected)
1460         int len = 0;
1461         int trimmed = FALSE;
1463         if (max_len <= 0)
1464                 return 0;
1466         if (opt_utf8) {
1467                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1468         } else {
1469                 len = strlen(string);
1470                 if (len > max_len) {
1471                         if (use_tilde) {
1472                                 max_len -= 1;
1473                         }
1474                         len = max_len;
1475                         trimmed = TRUE;
1476                 }
1477         }
1479         waddnstr(view->win, string, len);
1480         if (trimmed && use_tilde) {
1481                 if (!selected)
1482                         wattrset(view->win, get_line_attr(LINE_DELIMITER));
1483                 waddch(view->win, '~');
1484                 len++;
1485         }
1487         return len;
1490 static int
1491 draw_lineno(struct view *view, unsigned int lineno, int max, bool selected)
1493         static char fmt[] = "%1ld";
1494         char number[10] = "          ";
1495         int digits3 = view->digits < 3 ? 3 : view->digits;
1496         int max_number = MIN(digits3, STRING_SIZE(number));
1497         bool showtrimmed = FALSE;
1498         int col;
1500         lineno += view->offset + 1;
1501         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1502                 if (view->digits <= 9)
1503                         fmt[1] = '0' + digits3;
1505                 if (!string_format(number, fmt, lineno))
1506                         number[0] = 0;
1507                 showtrimmed = TRUE;
1508         }
1510         if (max < max_number)
1511                 max_number = max;
1513         if (!selected)
1514                 wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
1515         col = draw_text(view, number, max_number, showtrimmed, selected);
1516         if (col < max) {
1517                 if (!selected)
1518                         wattrset(view->win, A_NORMAL);
1519                 waddch(view->win, ACS_VLINE);
1520                 col++;
1521         }
1522         if (col < max) {
1523                 waddch(view->win, ' ');
1524                 col++;
1525         }
1527         return col;
1530 static int
1531 draw_date(struct view *view, struct tm *time, int max, bool selected)
1533         char buf[DATE_COLS];
1534         int col;
1535         int timelen = 0;
1537         if (max > DATE_COLS)
1538                 max = DATE_COLS;
1539         if (time)
1540                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1541         if (!timelen) {
1542                 memset(buf, ' ', sizeof(buf) - 1);
1543                 buf[sizeof(buf) - 1] = 0;
1544         }
1546         if (!selected)
1547                 wattrset(view->win, get_line_attr(LINE_DATE));
1548         col = draw_text(view, buf, max, FALSE, selected);
1549         if (col < max) {
1550                 if (!selected)
1551                         wattrset(view->win, get_line_attr(LINE_DEFAULT));
1552                 waddch(view->win, ' ');
1553                 col++;
1554         }
1556         return col;
1559 static bool
1560 draw_view_line(struct view *view, unsigned int lineno)
1562         struct line *line;
1563         bool selected = (view->offset + lineno == view->lineno);
1564         bool draw_ok;
1566         assert(view_is_displayed(view));
1568         if (view->offset + lineno >= view->lines)
1569                 return FALSE;
1571         line = &view->line[view->offset + lineno];
1573         if (selected) {
1574                 line->selected = TRUE;
1575                 view->ops->select(view, line);
1576         } else if (line->selected) {
1577                 line->selected = FALSE;
1578                 wmove(view->win, lineno, 0);
1579                 wclrtoeol(view->win);
1580         }
1582         scrollok(view->win, FALSE);
1583         draw_ok = view->ops->draw(view, line, lineno, selected);
1584         scrollok(view->win, TRUE);
1586         return draw_ok;
1589 static void
1590 redraw_view_dirty(struct view *view)
1592         bool dirty = FALSE;
1593         int lineno;
1595         for (lineno = 0; lineno < view->height; lineno++) {
1596                 struct line *line = &view->line[view->offset + lineno];
1598                 if (!line->dirty)
1599                         continue;
1600                 line->dirty = 0;
1601                 dirty = TRUE;
1602                 if (!draw_view_line(view, lineno))
1603                         break;
1604         }
1606         if (!dirty)
1607                 return;
1608         redrawwin(view->win);
1609         if (input_mode)
1610                 wnoutrefresh(view->win);
1611         else
1612                 wrefresh(view->win);
1615 static void
1616 redraw_view_from(struct view *view, int lineno)
1618         assert(0 <= lineno && lineno < view->height);
1620         for (; lineno < view->height; lineno++) {
1621                 if (!draw_view_line(view, lineno))
1622                         break;
1623         }
1625         redrawwin(view->win);
1626         if (input_mode)
1627                 wnoutrefresh(view->win);
1628         else
1629                 wrefresh(view->win);
1632 static void
1633 redraw_view(struct view *view)
1635         wclear(view->win);
1636         redraw_view_from(view, 0);
1640 static void
1641 update_view_title(struct view *view)
1643         char buf[SIZEOF_STR];
1644         char state[SIZEOF_STR];
1645         size_t bufpos = 0, statelen = 0;
1647         assert(view_is_displayed(view));
1649         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1650                 unsigned int view_lines = view->offset + view->height;
1651                 unsigned int lines = view->lines
1652                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1653                                    : 0;
1655                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1656                                    view->ops->type,
1657                                    view->lineno + 1,
1658                                    view->lines,
1659                                    lines);
1661                 if (view->pipe) {
1662                         time_t secs = time(NULL) - view->start_time;
1664                         /* Three git seconds are a long time ... */
1665                         if (secs > 2)
1666                                 string_format_from(state, &statelen, " %lds", secs);
1667                 }
1668         }
1670         string_format_from(buf, &bufpos, "[%s]", view->name);
1671         if (*view->ref && bufpos < view->width) {
1672                 size_t refsize = strlen(view->ref);
1673                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1675                 if (minsize < view->width)
1676                         refsize = view->width - minsize + 7;
1677                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1678         }
1680         if (statelen && bufpos < view->width) {
1681                 string_format_from(buf, &bufpos, " %s", state);
1682         }
1684         if (view == display[current_view])
1685                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1686         else
1687                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1689         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1690         wclrtoeol(view->title);
1691         wmove(view->title, 0, view->width - 1);
1693         if (input_mode)
1694                 wnoutrefresh(view->title);
1695         else
1696                 wrefresh(view->title);
1699 static void
1700 resize_display(void)
1702         int offset, i;
1703         struct view *base = display[0];
1704         struct view *view = display[1] ? display[1] : display[0];
1706         /* Setup window dimensions */
1708         getmaxyx(stdscr, base->height, base->width);
1710         /* Make room for the status window. */
1711         base->height -= 1;
1713         if (view != base) {
1714                 /* Horizontal split. */
1715                 view->width   = base->width;
1716                 view->height  = SCALE_SPLIT_VIEW(base->height);
1717                 base->height -= view->height;
1719                 /* Make room for the title bar. */
1720                 view->height -= 1;
1721         }
1723         /* Make room for the title bar. */
1724         base->height -= 1;
1726         offset = 0;
1728         foreach_displayed_view (view, i) {
1729                 if (!view->win) {
1730                         view->win = newwin(view->height, 0, offset, 0);
1731                         if (!view->win)
1732                                 die("Failed to create %s view", view->name);
1734                         scrollok(view->win, TRUE);
1736                         view->title = newwin(1, 0, offset + view->height, 0);
1737                         if (!view->title)
1738                                 die("Failed to create title window");
1740                 } else {
1741                         wresize(view->win, view->height, view->width);
1742                         mvwin(view->win,   offset, 0);
1743                         mvwin(view->title, offset + view->height, 0);
1744                 }
1746                 offset += view->height + 1;
1747         }
1750 static void
1751 redraw_display(void)
1753         struct view *view;
1754         int i;
1756         foreach_displayed_view (view, i) {
1757                 redraw_view(view);
1758                 update_view_title(view);
1759         }
1762 static void
1763 update_display_cursor(struct view *view)
1765         /* Move the cursor to the right-most column of the cursor line.
1766          *
1767          * XXX: This could turn out to be a bit expensive, but it ensures that
1768          * the cursor does not jump around. */
1769         if (view->lines) {
1770                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1771                 wrefresh(view->win);
1772         }
1775 /*
1776  * Navigation
1777  */
1779 /* Scrolling backend */
1780 static void
1781 do_scroll_view(struct view *view, int lines)
1783         bool redraw_current_line = FALSE;
1785         /* The rendering expects the new offset. */
1786         view->offset += lines;
1788         assert(0 <= view->offset && view->offset < view->lines);
1789         assert(lines);
1791         /* Move current line into the view. */
1792         if (view->lineno < view->offset) {
1793                 view->lineno = view->offset;
1794                 redraw_current_line = TRUE;
1795         } else if (view->lineno >= view->offset + view->height) {
1796                 view->lineno = view->offset + view->height - 1;
1797                 redraw_current_line = TRUE;
1798         }
1800         assert(view->offset <= view->lineno && view->lineno < view->lines);
1802         /* Redraw the whole screen if scrolling is pointless. */
1803         if (view->height < ABS(lines)) {
1804                 redraw_view(view);
1806         } else {
1807                 int line = lines > 0 ? view->height - lines : 0;
1808                 int end = line + ABS(lines);
1810                 wscrl(view->win, lines);
1812                 for (; line < end; line++) {
1813                         if (!draw_view_line(view, line))
1814                                 break;
1815                 }
1817                 if (redraw_current_line)
1818                         draw_view_line(view, view->lineno - view->offset);
1819         }
1821         redrawwin(view->win);
1822         wrefresh(view->win);
1823         report("");
1826 /* Scroll frontend */
1827 static void
1828 scroll_view(struct view *view, enum request request)
1830         int lines = 1;
1832         assert(view_is_displayed(view));
1834         switch (request) {
1835         case REQ_SCROLL_PAGE_DOWN:
1836                 lines = view->height;
1837         case REQ_SCROLL_LINE_DOWN:
1838                 if (view->offset + lines > view->lines)
1839                         lines = view->lines - view->offset;
1841                 if (lines == 0 || view->offset + view->height >= view->lines) {
1842                         report("Cannot scroll beyond the last line");
1843                         return;
1844                 }
1845                 break;
1847         case REQ_SCROLL_PAGE_UP:
1848                 lines = view->height;
1849         case REQ_SCROLL_LINE_UP:
1850                 if (lines > view->offset)
1851                         lines = view->offset;
1853                 if (lines == 0) {
1854                         report("Cannot scroll beyond the first line");
1855                         return;
1856                 }
1858                 lines = -lines;
1859                 break;
1861         default:
1862                 die("request %d not handled in switch", request);
1863         }
1865         do_scroll_view(view, lines);
1868 /* Cursor moving */
1869 static void
1870 move_view(struct view *view, enum request request)
1872         int scroll_steps = 0;
1873         int steps;
1875         switch (request) {
1876         case REQ_MOVE_FIRST_LINE:
1877                 steps = -view->lineno;
1878                 break;
1880         case REQ_MOVE_LAST_LINE:
1881                 steps = view->lines - view->lineno - 1;
1882                 break;
1884         case REQ_MOVE_PAGE_UP:
1885                 steps = view->height > view->lineno
1886                       ? -view->lineno : -view->height;
1887                 break;
1889         case REQ_MOVE_PAGE_DOWN:
1890                 steps = view->lineno + view->height >= view->lines
1891                       ? view->lines - view->lineno - 1 : view->height;
1892                 break;
1894         case REQ_MOVE_UP:
1895                 steps = -1;
1896                 break;
1898         case REQ_MOVE_DOWN:
1899                 steps = 1;
1900                 break;
1902         default:
1903                 die("request %d not handled in switch", request);
1904         }
1906         if (steps <= 0 && view->lineno == 0) {
1907                 report("Cannot move beyond the first line");
1908                 return;
1910         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1911                 report("Cannot move beyond the last line");
1912                 return;
1913         }
1915         /* Move the current line */
1916         view->lineno += steps;
1917         assert(0 <= view->lineno && view->lineno < view->lines);
1919         /* Check whether the view needs to be scrolled */
1920         if (view->lineno < view->offset ||
1921             view->lineno >= view->offset + view->height) {
1922                 scroll_steps = steps;
1923                 if (steps < 0 && -steps > view->offset) {
1924                         scroll_steps = -view->offset;
1926                 } else if (steps > 0) {
1927                         if (view->lineno == view->lines - 1 &&
1928                             view->lines > view->height) {
1929                                 scroll_steps = view->lines - view->offset - 1;
1930                                 if (scroll_steps >= view->height)
1931                                         scroll_steps -= view->height - 1;
1932                         }
1933                 }
1934         }
1936         if (!view_is_displayed(view)) {
1937                 view->offset += scroll_steps;
1938                 assert(0 <= view->offset && view->offset < view->lines);
1939                 view->ops->select(view, &view->line[view->lineno]);
1940                 return;
1941         }
1943         /* Repaint the old "current" line if we be scrolling */
1944         if (ABS(steps) < view->height)
1945                 draw_view_line(view, view->lineno - steps - view->offset);
1947         if (scroll_steps) {
1948                 do_scroll_view(view, scroll_steps);
1949                 return;
1950         }
1952         /* Draw the current line */
1953         draw_view_line(view, view->lineno - view->offset);
1955         redrawwin(view->win);
1956         wrefresh(view->win);
1957         report("");
1961 /*
1962  * Searching
1963  */
1965 static void search_view(struct view *view, enum request request);
1967 static bool
1968 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1970         assert(view_is_displayed(view));
1972         if (!view->ops->grep(view, line))
1973                 return FALSE;
1975         if (lineno - view->offset >= view->height) {
1976                 view->offset = lineno;
1977                 view->lineno = lineno;
1978                 redraw_view(view);
1980         } else {
1981                 unsigned long old_lineno = view->lineno - view->offset;
1983                 view->lineno = lineno;
1984                 draw_view_line(view, old_lineno);
1986                 draw_view_line(view, view->lineno - view->offset);
1987                 redrawwin(view->win);
1988                 wrefresh(view->win);
1989         }
1991         report("Line %ld matches '%s'", lineno + 1, view->grep);
1992         return TRUE;
1995 static void
1996 find_next(struct view *view, enum request request)
1998         unsigned long lineno = view->lineno;
1999         int direction;
2001         if (!*view->grep) {
2002                 if (!*opt_search)
2003                         report("No previous search");
2004                 else
2005                         search_view(view, request);
2006                 return;
2007         }
2009         switch (request) {
2010         case REQ_SEARCH:
2011         case REQ_FIND_NEXT:
2012                 direction = 1;
2013                 break;
2015         case REQ_SEARCH_BACK:
2016         case REQ_FIND_PREV:
2017                 direction = -1;
2018                 break;
2020         default:
2021                 return;
2022         }
2024         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2025                 lineno += direction;
2027         /* Note, lineno is unsigned long so will wrap around in which case it
2028          * will become bigger than view->lines. */
2029         for (; lineno < view->lines; lineno += direction) {
2030                 struct line *line = &view->line[lineno];
2032                 if (find_next_line(view, lineno, line))
2033                         return;
2034         }
2036         report("No match found for '%s'", view->grep);
2039 static void
2040 search_view(struct view *view, enum request request)
2042         int regex_err;
2044         if (view->regex) {
2045                 regfree(view->regex);
2046                 *view->grep = 0;
2047         } else {
2048                 view->regex = calloc(1, sizeof(*view->regex));
2049                 if (!view->regex)
2050                         return;
2051         }
2053         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2054         if (regex_err != 0) {
2055                 char buf[SIZEOF_STR] = "unknown error";
2057                 regerror(regex_err, view->regex, buf, sizeof(buf));
2058                 report("Search failed: %s", buf);
2059                 return;
2060         }
2062         string_copy(view->grep, opt_search);
2064         find_next(view, request);
2067 /*
2068  * Incremental updating
2069  */
2071 static void
2072 end_update(struct view *view)
2074         if (!view->pipe)
2075                 return;
2076         set_nonblocking_input(FALSE);
2077         if (view->pipe == stdin)
2078                 fclose(view->pipe);
2079         else
2080                 pclose(view->pipe);
2081         view->pipe = NULL;
2084 static bool
2085 begin_update(struct view *view)
2087         if (view->pipe)
2088                 end_update(view);
2090         if (opt_cmd[0]) {
2091                 string_copy(view->cmd, opt_cmd);
2092                 opt_cmd[0] = 0;
2093                 /* When running random commands, initially show the
2094                  * command in the title. However, it maybe later be
2095                  * overwritten if a commit line is selected. */
2096                 if (view == VIEW(REQ_VIEW_PAGER))
2097                         string_copy(view->ref, view->cmd);
2098                 else
2099                         view->ref[0] = 0;
2101         } else if (view == VIEW(REQ_VIEW_TREE)) {
2102                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2103                 char path[SIZEOF_STR];
2105                 if (strcmp(view->vid, view->id))
2106                         opt_path[0] = path[0] = 0;
2107                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2108                         return FALSE;
2110                 if (!string_format(view->cmd, format, view->id, path))
2111                         return FALSE;
2113         } else {
2114                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2115                 const char *id = view->id;
2117                 if (!string_format(view->cmd, format, id, id, id, id, id))
2118                         return FALSE;
2120                 /* Put the current ref_* value to the view title ref
2121                  * member. This is needed by the blob view. Most other
2122                  * views sets it automatically after loading because the
2123                  * first line is a commit line. */
2124                 string_copy_rev(view->ref, view->id);
2125         }
2127         /* Special case for the pager view. */
2128         if (opt_pipe) {
2129                 view->pipe = opt_pipe;
2130                 opt_pipe = NULL;
2131         } else {
2132                 view->pipe = popen(view->cmd, "r");
2133         }
2135         if (!view->pipe)
2136                 return FALSE;
2138         set_nonblocking_input(TRUE);
2140         view->offset = 0;
2141         view->lines  = 0;
2142         view->lineno = 0;
2143         string_copy_rev(view->vid, view->id);
2145         if (view->line) {
2146                 int i;
2148                 for (i = 0; i < view->lines; i++)
2149                         if (view->line[i].data)
2150                                 free(view->line[i].data);
2152                 free(view->line);
2153                 view->line = NULL;
2154         }
2156         view->start_time = time(NULL);
2158         return TRUE;
2161 #define ITEM_CHUNK_SIZE 256
2162 static void *
2163 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2165         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2166         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2168         if (mem == NULL || num_chunks != num_chunks_new) {
2169                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2170                 mem = realloc(mem, *size * item_size);
2171         }
2173         return mem;
2176 static struct line *
2177 realloc_lines(struct view *view, size_t line_size)
2179         size_t alloc = view->line_alloc;
2180         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2181                                          sizeof(*view->line));
2183         if (!tmp)
2184                 return NULL;
2186         view->line = tmp;
2187         view->line_alloc = alloc;
2188         view->line_size = line_size;
2189         return view->line;
2192 static bool
2193 update_view(struct view *view)
2195         char in_buffer[BUFSIZ];
2196         char out_buffer[BUFSIZ * 2];
2197         char *line;
2198         /* The number of lines to read. If too low it will cause too much
2199          * redrawing (and possible flickering), if too high responsiveness
2200          * will suffer. */
2201         unsigned long lines = view->height;
2202         int redraw_from = -1;
2204         if (!view->pipe)
2205                 return TRUE;
2207         /* Only redraw if lines are visible. */
2208         if (view->offset + view->height >= view->lines)
2209                 redraw_from = view->lines - view->offset;
2211         /* FIXME: This is probably not perfect for backgrounded views. */
2212         if (!realloc_lines(view, view->lines + lines))
2213                 goto alloc_error;
2215         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2216                 size_t linelen = strlen(line);
2218                 if (linelen)
2219                         line[linelen - 1] = 0;
2221                 if (opt_iconv != ICONV_NONE) {
2222                         ICONV_CONST char *inbuf = line;
2223                         size_t inlen = linelen;
2225                         char *outbuf = out_buffer;
2226                         size_t outlen = sizeof(out_buffer);
2228                         size_t ret;
2230                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2231                         if (ret != (size_t) -1) {
2232                                 line = out_buffer;
2233                                 linelen = strlen(out_buffer);
2234                         }
2235                 }
2237                 if (!view->ops->read(view, line))
2238                         goto alloc_error;
2240                 if (lines-- == 1)
2241                         break;
2242         }
2244         {
2245                 int digits;
2247                 lines = view->lines;
2248                 for (digits = 0; lines; digits++)
2249                         lines /= 10;
2251                 /* Keep the displayed view in sync with line number scaling. */
2252                 if (digits != view->digits) {
2253                         view->digits = digits;
2254                         redraw_from = 0;
2255                 }
2256         }
2258         if (!view_is_displayed(view))
2259                 goto check_pipe;
2261         if (view == VIEW(REQ_VIEW_TREE)) {
2262                 /* Clear the view and redraw everything since the tree sorting
2263                  * might have rearranged things. */
2264                 redraw_view(view);
2266         } else if (redraw_from >= 0) {
2267                 /* If this is an incremental update, redraw the previous line
2268                  * since for commits some members could have changed when
2269                  * loading the main view. */
2270                 if (redraw_from > 0)
2271                         redraw_from--;
2273                 /* Since revision graph visualization requires knowledge
2274                  * about the parent commit, it causes a further one-off
2275                  * needed to be redrawn for incremental updates. */
2276                 if (redraw_from > 0 && opt_rev_graph)
2277                         redraw_from--;
2279                 /* Incrementally draw avoids flickering. */
2280                 redraw_view_from(view, redraw_from);
2281         }
2283         if (view == VIEW(REQ_VIEW_BLAME))
2284                 redraw_view_dirty(view);
2286         /* Update the title _after_ the redraw so that if the redraw picks up a
2287          * commit reference in view->ref it'll be available here. */
2288         update_view_title(view);
2290 check_pipe:
2291         if (ferror(view->pipe)) {
2292                 report("Failed to read: %s", strerror(errno));
2293                 goto end;
2295         } else if (feof(view->pipe)) {
2296                 report("");
2297                 goto end;
2298         }
2300         return TRUE;
2302 alloc_error:
2303         report("Allocation failure");
2305 end:
2306         if (view->ops->read(view, NULL))
2307                 end_update(view);
2308         return FALSE;
2311 static struct line *
2312 add_line_data(struct view *view, void *data, enum line_type type)
2314         struct line *line = &view->line[view->lines++];
2316         memset(line, 0, sizeof(*line));
2317         line->type = type;
2318         line->data = data;
2320         return line;
2323 static struct line *
2324 add_line_text(struct view *view, char *data, enum line_type type)
2326         if (data)
2327                 data = strdup(data);
2329         return data ? add_line_data(view, data, type) : NULL;
2333 /*
2334  * View opening
2335  */
2337 enum open_flags {
2338         OPEN_DEFAULT = 0,       /* Use default view switching. */
2339         OPEN_SPLIT = 1,         /* Split current view. */
2340         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2341         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2342         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2343 };
2345 static void
2346 open_view(struct view *prev, enum request request, enum open_flags flags)
2348         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2349         bool split = !!(flags & OPEN_SPLIT);
2350         bool reload = !!(flags & OPEN_RELOAD);
2351         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2352         struct view *view = VIEW(request);
2353         int nviews = displayed_views();
2354         struct view *base_view = display[0];
2356         if (view == prev && nviews == 1 && !reload) {
2357                 report("Already in %s view", view->name);
2358                 return;
2359         }
2361         if (view->git_dir && !opt_git_dir[0]) {
2362                 report("The %s view is disabled in pager view", view->name);
2363                 return;
2364         }
2366         if (split) {
2367                 display[1] = view;
2368                 if (!backgrounded)
2369                         current_view = 1;
2370         } else if (!nomaximize) {
2371                 /* Maximize the current view. */
2372                 memset(display, 0, sizeof(display));
2373                 current_view = 0;
2374                 display[current_view] = view;
2375         }
2377         /* Resize the view when switching between split- and full-screen,
2378          * or when switching between two different full-screen views. */
2379         if (nviews != displayed_views() ||
2380             (nviews == 1 && base_view != display[0]))
2381                 resize_display();
2383         if (view->ops->open) {
2384                 if (!view->ops->open(view)) {
2385                         report("Failed to load %s view", view->name);
2386                         return;
2387                 }
2389         } else if ((reload || strcmp(view->vid, view->id)) &&
2390                    !begin_update(view)) {
2391                 report("Failed to load %s view", view->name);
2392                 return;
2393         }
2395         if (split && prev->lineno - prev->offset >= prev->height) {
2396                 /* Take the title line into account. */
2397                 int lines = prev->lineno - prev->offset - prev->height + 1;
2399                 /* Scroll the view that was split if the current line is
2400                  * outside the new limited view. */
2401                 do_scroll_view(prev, lines);
2402         }
2404         if (prev && view != prev) {
2405                 if (split && !backgrounded) {
2406                         /* "Blur" the previous view. */
2407                         update_view_title(prev);
2408                 }
2410                 view->parent = prev;
2411         }
2413         if (view->pipe && view->lines == 0) {
2414                 /* Clear the old view and let the incremental updating refill
2415                  * the screen. */
2416                 werase(view->win);
2417                 report("");
2418         } else {
2419                 redraw_view(view);
2420                 report("");
2421         }
2423         /* If the view is backgrounded the above calls to report()
2424          * won't redraw the view title. */
2425         if (backgrounded)
2426                 update_view_title(view);
2429 static void
2430 open_external_viewer(const char *cmd)
2432         def_prog_mode();           /* save current tty modes */
2433         endwin();                  /* restore original tty modes */
2434         system(cmd);
2435         fprintf(stderr, "Press Enter to continue");
2436         getc(stdin);
2437         reset_prog_mode();
2438         redraw_display();
2441 static void
2442 open_mergetool(const char *file)
2444         char cmd[SIZEOF_STR];
2445         char file_sq[SIZEOF_STR];
2447         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2448             string_format(cmd, "git mergetool %s", file_sq)) {
2449                 open_external_viewer(cmd);
2450         }
2453 static void
2454 open_editor(bool from_root, const char *file)
2456         char cmd[SIZEOF_STR];
2457         char file_sq[SIZEOF_STR];
2458         char *editor;
2459         char *prefix = from_root ? opt_cdup : "";
2461         editor = getenv("GIT_EDITOR");
2462         if (!editor && *opt_editor)
2463                 editor = opt_editor;
2464         if (!editor)
2465                 editor = getenv("VISUAL");
2466         if (!editor)
2467                 editor = getenv("EDITOR");
2468         if (!editor)
2469                 editor = "vi";
2471         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2472             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2473                 open_external_viewer(cmd);
2474         }
2477 static void
2478 open_run_request(enum request request)
2480         struct run_request *req = get_run_request(request);
2481         char buf[SIZEOF_STR * 2];
2482         size_t bufpos;
2483         char *cmd;
2485         if (!req) {
2486                 report("Unknown run request");
2487                 return;
2488         }
2490         bufpos = 0;
2491         cmd = req->cmd;
2493         while (cmd) {
2494                 char *next = strstr(cmd, "%(");
2495                 int len = next - cmd;
2496                 char *value;
2498                 if (!next) {
2499                         len = strlen(cmd);
2500                         value = "";
2502                 } else if (!strncmp(next, "%(head)", 7)) {
2503                         value = ref_head;
2505                 } else if (!strncmp(next, "%(commit)", 9)) {
2506                         value = ref_commit;
2508                 } else if (!strncmp(next, "%(blob)", 7)) {
2509                         value = ref_blob;
2511                 } else {
2512                         report("Unknown replacement in run request: `%s`", req->cmd);
2513                         return;
2514                 }
2516                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2517                         return;
2519                 if (next)
2520                         next = strchr(next, ')') + 1;
2521                 cmd = next;
2522         }
2524         open_external_viewer(buf);
2527 /*
2528  * User request switch noodle
2529  */
2531 static int
2532 view_driver(struct view *view, enum request request)
2534         int i;
2536         if (request == REQ_NONE) {
2537                 doupdate();
2538                 return TRUE;
2539         }
2541         if (request > REQ_NONE) {
2542                 open_run_request(request);
2543                 /* FIXME: When all views can refresh always do this. */
2544                 if (view == VIEW(REQ_VIEW_STATUS) ||
2545                     view == VIEW(REQ_VIEW_STAGE))
2546                         request = REQ_REFRESH;
2547                 else
2548                         return TRUE;
2549         }
2551         if (view && view->lines) {
2552                 request = view->ops->request(view, request, &view->line[view->lineno]);
2553                 if (request == REQ_NONE)
2554                         return TRUE;
2555         }
2557         switch (request) {
2558         case REQ_MOVE_UP:
2559         case REQ_MOVE_DOWN:
2560         case REQ_MOVE_PAGE_UP:
2561         case REQ_MOVE_PAGE_DOWN:
2562         case REQ_MOVE_FIRST_LINE:
2563         case REQ_MOVE_LAST_LINE:
2564                 move_view(view, request);
2565                 break;
2567         case REQ_SCROLL_LINE_DOWN:
2568         case REQ_SCROLL_LINE_UP:
2569         case REQ_SCROLL_PAGE_DOWN:
2570         case REQ_SCROLL_PAGE_UP:
2571                 scroll_view(view, request);
2572                 break;
2574         case REQ_VIEW_BLAME:
2575                 if (!opt_file[0]) {
2576                         report("No file chosen, press %s to open tree view",
2577                                get_key(REQ_VIEW_TREE));
2578                         break;
2579                 }
2580                 open_view(view, request, OPEN_DEFAULT);
2581                 break;
2583         case REQ_VIEW_BLOB:
2584                 if (!ref_blob[0]) {
2585                         report("No file chosen, press %s to open tree view",
2586                                get_key(REQ_VIEW_TREE));
2587                         break;
2588                 }
2589                 open_view(view, request, OPEN_DEFAULT);
2590                 break;
2592         case REQ_VIEW_PAGER:
2593                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2594                         report("No pager content, press %s to run command from prompt",
2595                                get_key(REQ_PROMPT));
2596                         break;
2597                 }
2598                 open_view(view, request, OPEN_DEFAULT);
2599                 break;
2601         case REQ_VIEW_STAGE:
2602                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2603                         report("No stage content, press %s to open the status view and choose file",
2604                                get_key(REQ_VIEW_STATUS));
2605                         break;
2606                 }
2607                 open_view(view, request, OPEN_DEFAULT);
2608                 break;
2610         case REQ_VIEW_STATUS:
2611                 if (opt_is_inside_work_tree == FALSE) {
2612                         report("The status view requires a working tree");
2613                         break;
2614                 }
2615                 open_view(view, request, OPEN_DEFAULT);
2616                 break;
2618         case REQ_VIEW_MAIN:
2619         case REQ_VIEW_DIFF:
2620         case REQ_VIEW_LOG:
2621         case REQ_VIEW_TREE:
2622         case REQ_VIEW_HELP:
2623                 open_view(view, request, OPEN_DEFAULT);
2624                 break;
2626         case REQ_NEXT:
2627         case REQ_PREVIOUS:
2628                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2630                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2631                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2632                    (view == VIEW(REQ_VIEW_DIFF) &&
2633                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2634                    (view == VIEW(REQ_VIEW_STAGE) &&
2635                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2636                    (view == VIEW(REQ_VIEW_BLOB) &&
2637                      view->parent == VIEW(REQ_VIEW_TREE))) {
2638                         int line;
2640                         view = view->parent;
2641                         line = view->lineno;
2642                         move_view(view, request);
2643                         if (view_is_displayed(view))
2644                                 update_view_title(view);
2645                         if (line != view->lineno)
2646                                 view->ops->request(view, REQ_ENTER,
2647                                                    &view->line[view->lineno]);
2649                 } else {
2650                         move_view(view, request);
2651                 }
2652                 break;
2654         case REQ_VIEW_NEXT:
2655         {
2656                 int nviews = displayed_views();
2657                 int next_view = (current_view + 1) % nviews;
2659                 if (next_view == current_view) {
2660                         report("Only one view is displayed");
2661                         break;
2662                 }
2664                 current_view = next_view;
2665                 /* Blur out the title of the previous view. */
2666                 update_view_title(view);
2667                 report("");
2668                 break;
2669         }
2670         case REQ_REFRESH:
2671                 report("Refreshing is not yet supported for the %s view", view->name);
2672                 break;
2674         case REQ_MAXIMIZE:
2675                 if (displayed_views() == 2)
2676                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2677                 break;
2679         case REQ_TOGGLE_LINENO:
2680                 opt_line_number = !opt_line_number;
2681                 redraw_display();
2682                 break;
2684         case REQ_TOGGLE_DATE:
2685                 opt_date = !opt_date;
2686                 redraw_display();
2687                 break;
2689         case REQ_TOGGLE_AUTHOR:
2690                 opt_author = !opt_author;
2691                 redraw_display();
2692                 break;
2694         case REQ_TOGGLE_REV_GRAPH:
2695                 opt_rev_graph = !opt_rev_graph;
2696                 redraw_display();
2697                 break;
2699         case REQ_TOGGLE_REFS:
2700                 opt_show_refs = !opt_show_refs;
2701                 redraw_display();
2702                 break;
2704         case REQ_PROMPT:
2705                 /* Always reload^Wrerun commands from the prompt. */
2706                 open_view(view, opt_request, OPEN_RELOAD);
2707                 break;
2709         case REQ_SEARCH:
2710         case REQ_SEARCH_BACK:
2711                 search_view(view, request);
2712                 break;
2714         case REQ_FIND_NEXT:
2715         case REQ_FIND_PREV:
2716                 find_next(view, request);
2717                 break;
2719         case REQ_STOP_LOADING:
2720                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2721                         view = &views[i];
2722                         if (view->pipe)
2723                                 report("Stopped loading the %s view", view->name),
2724                         end_update(view);
2725                 }
2726                 break;
2728         case REQ_SHOW_VERSION:
2729                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2730                 return TRUE;
2732         case REQ_SCREEN_RESIZE:
2733                 resize_display();
2734                 /* Fall-through */
2735         case REQ_SCREEN_REDRAW:
2736                 redraw_display();
2737                 break;
2739         case REQ_EDIT:
2740                 report("Nothing to edit");
2741                 break;
2744         case REQ_ENTER:
2745                 report("Nothing to enter");
2746                 break;
2749         case REQ_VIEW_CLOSE:
2750                 /* XXX: Mark closed views by letting view->parent point to the
2751                  * view itself. Parents to closed view should never be
2752                  * followed. */
2753                 if (view->parent &&
2754                     view->parent->parent != view->parent) {
2755                         memset(display, 0, sizeof(display));
2756                         current_view = 0;
2757                         display[current_view] = view->parent;
2758                         view->parent = view;
2759                         resize_display();
2760                         redraw_display();
2761                         break;
2762                 }
2763                 /* Fall-through */
2764         case REQ_QUIT:
2765                 return FALSE;
2767         default:
2768                 /* An unknown key will show most commonly used commands. */
2769                 report("Unknown key, press 'h' for help");
2770                 return TRUE;
2771         }
2773         return TRUE;
2777 /*
2778  * Pager backend
2779  */
2781 static bool
2782 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2784         static char spaces[] = "                    ";
2785         char *text = line->data;
2786         enum line_type type = line->type;
2787         int attr = A_NORMAL;
2788         int col = 0;
2790         wmove(view->win, lineno, 0);
2792         if (selected) {
2793                 type = LINE_CURSOR;
2794                 wchgat(view->win, -1, 0, type, NULL);
2795                 attr = get_line_attr(type);
2796         }
2797         wattrset(view->win, attr);
2799         if (opt_line_number) {
2800                 col += draw_lineno(view, lineno, view->width, selected);
2801                 if (col >= view->width)
2802                         return TRUE;
2803         }
2805         if (!selected) {
2806                 attr = get_line_attr(type);
2807                 wattrset(view->win, attr);
2808         }
2809         if (opt_tab_size < TABSIZE) {
2810                 int col_offset = col;
2812                 col = 0;
2813                 while (text && col_offset + col < view->width) {
2814                         int cols_max = view->width - col_offset - col;
2815                         char *pos = text;
2816                         int cols;
2818                         if (*text == '\t') {
2819                                 text++;
2820                                 assert(sizeof(spaces) > TABSIZE);
2821                                 pos = spaces;
2822                                 cols = opt_tab_size - (col % opt_tab_size);
2824                         } else {
2825                                 text = strchr(text, '\t');
2826                                 cols = line ? text - pos : strlen(pos);
2827                         }
2829                         waddnstr(view->win, pos, MIN(cols, cols_max));
2830                         col += cols;
2831                 }
2833         } else {
2834                 draw_text(view, text, view->width - col, TRUE, selected);
2835         }
2837         return TRUE;
2840 static bool
2841 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2843         char refbuf[SIZEOF_STR];
2844         char *ref = NULL;
2845         FILE *pipe;
2847         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2848                 return TRUE;
2850         pipe = popen(refbuf, "r");
2851         if (!pipe)
2852                 return TRUE;
2854         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2855                 ref = chomp_string(ref);
2856         pclose(pipe);
2858         if (!ref || !*ref)
2859                 return TRUE;
2861         /* This is the only fatal call, since it can "corrupt" the buffer. */
2862         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2863                 return FALSE;
2865         return TRUE;
2868 static void
2869 add_pager_refs(struct view *view, struct line *line)
2871         char buf[SIZEOF_STR];
2872         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2873         struct ref **refs;
2874         size_t bufpos = 0, refpos = 0;
2875         const char *sep = "Refs: ";
2876         bool is_tag = FALSE;
2878         assert(line->type == LINE_COMMIT);
2880         refs = get_refs(commit_id);
2881         if (!refs) {
2882                 if (view == VIEW(REQ_VIEW_DIFF))
2883                         goto try_add_describe_ref;
2884                 return;
2885         }
2887         do {
2888                 struct ref *ref = refs[refpos];
2889                 char *fmt = ref->tag    ? "%s[%s]" :
2890                             ref->remote ? "%s<%s>" : "%s%s";
2892                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2893                         return;
2894                 sep = ", ";
2895                 if (ref->tag)
2896                         is_tag = TRUE;
2897         } while (refs[refpos++]->next);
2899         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2900 try_add_describe_ref:
2901                 /* Add <tag>-g<commit_id> "fake" reference. */
2902                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2903                         return;
2904         }
2906         if (bufpos == 0)
2907                 return;
2909         if (!realloc_lines(view, view->line_size + 1))
2910                 return;
2912         add_line_text(view, buf, LINE_PP_REFS);
2915 static bool
2916 pager_read(struct view *view, char *data)
2918         struct line *line;
2920         if (!data)
2921                 return TRUE;
2923         line = add_line_text(view, data, get_line_type(data));
2924         if (!line)
2925                 return FALSE;
2927         if (line->type == LINE_COMMIT &&
2928             (view == VIEW(REQ_VIEW_DIFF) ||
2929              view == VIEW(REQ_VIEW_LOG)))
2930                 add_pager_refs(view, line);
2932         return TRUE;
2935 static enum request
2936 pager_request(struct view *view, enum request request, struct line *line)
2938         int split = 0;
2940         if (request != REQ_ENTER)
2941                 return request;
2943         if (line->type == LINE_COMMIT &&
2944            (view == VIEW(REQ_VIEW_LOG) ||
2945             view == VIEW(REQ_VIEW_PAGER))) {
2946                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2947                 split = 1;
2948         }
2950         /* Always scroll the view even if it was split. That way
2951          * you can use Enter to scroll through the log view and
2952          * split open each commit diff. */
2953         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2955         /* FIXME: A minor workaround. Scrolling the view will call report("")
2956          * but if we are scrolling a non-current view this won't properly
2957          * update the view title. */
2958         if (split)
2959                 update_view_title(view);
2961         return REQ_NONE;
2964 static bool
2965 pager_grep(struct view *view, struct line *line)
2967         regmatch_t pmatch;
2968         char *text = line->data;
2970         if (!*text)
2971                 return FALSE;
2973         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2974                 return FALSE;
2976         return TRUE;
2979 static void
2980 pager_select(struct view *view, struct line *line)
2982         if (line->type == LINE_COMMIT) {
2983                 char *text = (char *)line->data + STRING_SIZE("commit ");
2985                 if (view != VIEW(REQ_VIEW_PAGER))
2986                         string_copy_rev(view->ref, text);
2987                 string_copy_rev(ref_commit, text);
2988         }
2991 static struct view_ops pager_ops = {
2992         "line",
2993         NULL,
2994         pager_read,
2995         pager_draw,
2996         pager_request,
2997         pager_grep,
2998         pager_select,
2999 };
3002 /*
3003  * Help backend
3004  */
3006 static bool
3007 help_open(struct view *view)
3009         char buf[BUFSIZ];
3010         int lines = ARRAY_SIZE(req_info) + 2;
3011         int i;
3013         if (view->lines > 0)
3014                 return TRUE;
3016         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3017                 if (!req_info[i].request)
3018                         lines++;
3020         lines += run_requests + 1;
3022         view->line = calloc(lines, sizeof(*view->line));
3023         if (!view->line)
3024                 return FALSE;
3026         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3028         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3029                 char *key;
3031                 if (req_info[i].request == REQ_NONE)
3032                         continue;
3034                 if (!req_info[i].request) {
3035                         add_line_text(view, "", LINE_DEFAULT);
3036                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3037                         continue;
3038                 }
3040                 key = get_key(req_info[i].request);
3041                 if (!*key)
3042                         key = "(no key defined)";
3044                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3045                         continue;
3047                 add_line_text(view, buf, LINE_DEFAULT);
3048         }
3050         if (run_requests) {
3051                 add_line_text(view, "", LINE_DEFAULT);
3052                 add_line_text(view, "External commands:", LINE_DEFAULT);
3053         }
3055         for (i = 0; i < run_requests; i++) {
3056                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3057                 char *key;
3059                 if (!req)
3060                         continue;
3062                 key = get_key_name(req->key);
3063                 if (!*key)
3064                         key = "(no key defined)";
3066                 if (!string_format(buf, "    %-10s %-14s `%s`",
3067                                    keymap_table[req->keymap].name,
3068                                    key, req->cmd))
3069                         continue;
3071                 add_line_text(view, buf, LINE_DEFAULT);
3072         }
3074         return TRUE;
3077 static struct view_ops help_ops = {
3078         "line",
3079         help_open,
3080         NULL,
3081         pager_draw,
3082         pager_request,
3083         pager_grep,
3084         pager_select,
3085 };
3088 /*
3089  * Tree backend
3090  */
3092 struct tree_stack_entry {
3093         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3094         unsigned long lineno;           /* Line number to restore */
3095         char *name;                     /* Position of name in opt_path */
3096 };
3098 /* The top of the path stack. */
3099 static struct tree_stack_entry *tree_stack = NULL;
3100 unsigned long tree_lineno = 0;
3102 static void
3103 pop_tree_stack_entry(void)
3105         struct tree_stack_entry *entry = tree_stack;
3107         tree_lineno = entry->lineno;
3108         entry->name[0] = 0;
3109         tree_stack = entry->prev;
3110         free(entry);
3113 static void
3114 push_tree_stack_entry(char *name, unsigned long lineno)
3116         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3117         size_t pathlen = strlen(opt_path);
3119         if (!entry)
3120                 return;
3122         entry->prev = tree_stack;
3123         entry->name = opt_path + pathlen;
3124         tree_stack = entry;
3126         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3127                 pop_tree_stack_entry();
3128                 return;
3129         }
3131         /* Move the current line to the first tree entry. */
3132         tree_lineno = 1;
3133         entry->lineno = lineno;
3136 /* Parse output from git-ls-tree(1):
3137  *
3138  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3139  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3140  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3141  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3142  */
3144 #define SIZEOF_TREE_ATTR \
3145         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3147 #define TREE_UP_FORMAT "040000 tree %s\t.."
3149 static int
3150 tree_compare_entry(enum line_type type1, char *name1,
3151                    enum line_type type2, char *name2)
3153         if (type1 != type2) {
3154                 if (type1 == LINE_TREE_DIR)
3155                         return -1;
3156                 return 1;
3157         }
3159         return strcmp(name1, name2);
3162 static char *
3163 tree_path(struct line *line)
3165         char *path = line->data;
3167         return path + SIZEOF_TREE_ATTR;
3170 static bool
3171 tree_read(struct view *view, char *text)
3173         size_t textlen = text ? strlen(text) : 0;
3174         char buf[SIZEOF_STR];
3175         unsigned long pos;
3176         enum line_type type;
3177         bool first_read = view->lines == 0;
3179         if (!text)
3180                 return TRUE;
3181         if (textlen <= SIZEOF_TREE_ATTR)
3182                 return FALSE;
3184         type = text[STRING_SIZE("100644 ")] == 't'
3185              ? LINE_TREE_DIR : LINE_TREE_FILE;
3187         if (first_read) {
3188                 /* Add path info line */
3189                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3190                     !realloc_lines(view, view->line_size + 1) ||
3191                     !add_line_text(view, buf, LINE_DEFAULT))
3192                         return FALSE;
3194                 /* Insert "link" to parent directory. */
3195                 if (*opt_path) {
3196                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3197                             !realloc_lines(view, view->line_size + 1) ||
3198                             !add_line_text(view, buf, LINE_TREE_DIR))
3199                                 return FALSE;
3200                 }
3201         }
3203         /* Strip the path part ... */
3204         if (*opt_path) {
3205                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3206                 size_t striplen = strlen(opt_path);
3207                 char *path = text + SIZEOF_TREE_ATTR;
3209                 if (pathlen > striplen)
3210                         memmove(path, path + striplen,
3211                                 pathlen - striplen + 1);
3212         }
3214         /* Skip "Directory ..." and ".." line. */
3215         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3216                 struct line *line = &view->line[pos];
3217                 char *path1 = tree_path(line);
3218                 char *path2 = text + SIZEOF_TREE_ATTR;
3219                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3221                 if (cmp <= 0)
3222                         continue;
3224                 text = strdup(text);
3225                 if (!text)
3226                         return FALSE;
3228                 if (view->lines > pos)
3229                         memmove(&view->line[pos + 1], &view->line[pos],
3230                                 (view->lines - pos) * sizeof(*line));
3232                 line = &view->line[pos];
3233                 line->data = text;
3234                 line->type = type;
3235                 view->lines++;
3236                 return TRUE;
3237         }
3239         if (!add_line_text(view, text, type))
3240                 return FALSE;
3242         if (tree_lineno > view->lineno) {
3243                 view->lineno = tree_lineno;
3244                 tree_lineno = 0;
3245         }
3247         return TRUE;
3250 static enum request
3251 tree_request(struct view *view, enum request request, struct line *line)
3253         enum open_flags flags;
3255         if (request == REQ_VIEW_BLAME) {
3256                 char *filename = tree_path(line);
3258                 if (line->type == LINE_TREE_DIR) {
3259                         report("Cannot show blame for directory %s", opt_path);
3260                         return REQ_NONE;
3261                 }
3263                 string_copy(opt_ref, view->vid);
3264                 string_format(opt_file, "%s%s", opt_path, filename);
3265                 return request;
3266         }
3267         if (request == REQ_TREE_PARENT) {
3268                 if (*opt_path) {
3269                         /* fake 'cd  ..' */
3270                         request = REQ_ENTER;
3271                         line = &view->line[1];
3272                 } else {
3273                         /* quit view if at top of tree */
3274                         return REQ_VIEW_CLOSE;
3275                 }
3276         }
3277         if (request != REQ_ENTER)
3278                 return request;
3280         /* Cleanup the stack if the tree view is at a different tree. */
3281         while (!*opt_path && tree_stack)
3282                 pop_tree_stack_entry();
3284         switch (line->type) {
3285         case LINE_TREE_DIR:
3286                 /* Depending on whether it is a subdir or parent (updir?) link
3287                  * mangle the path buffer. */
3288                 if (line == &view->line[1] && *opt_path) {
3289                         pop_tree_stack_entry();
3291                 } else {
3292                         char *basename = tree_path(line);
3294                         push_tree_stack_entry(basename, view->lineno);
3295                 }
3297                 /* Trees and subtrees share the same ID, so they are not not
3298                  * unique like blobs. */
3299                 flags = OPEN_RELOAD;
3300                 request = REQ_VIEW_TREE;
3301                 break;
3303         case LINE_TREE_FILE:
3304                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3305                 request = REQ_VIEW_BLOB;
3306                 break;
3308         default:
3309                 return TRUE;
3310         }
3312         open_view(view, request, flags);
3313         if (request == REQ_VIEW_TREE) {
3314                 view->lineno = tree_lineno;
3315         }
3317         return REQ_NONE;
3320 static void
3321 tree_select(struct view *view, struct line *line)
3323         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3325         if (line->type == LINE_TREE_FILE) {
3326                 string_copy_rev(ref_blob, text);
3328         } else if (line->type != LINE_TREE_DIR) {
3329                 return;
3330         }
3332         string_copy_rev(view->ref, text);
3335 static struct view_ops tree_ops = {
3336         "file",
3337         NULL,
3338         tree_read,
3339         pager_draw,
3340         tree_request,
3341         pager_grep,
3342         tree_select,
3343 };
3345 static bool
3346 blob_read(struct view *view, char *line)
3348         if (!line)
3349                 return TRUE;
3350         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3353 static struct view_ops blob_ops = {
3354         "line",
3355         NULL,
3356         blob_read,
3357         pager_draw,
3358         pager_request,
3359         pager_grep,
3360         pager_select,
3361 };
3363 /*
3364  * Blame backend
3365  *
3366  * Loading the blame view is a two phase job:
3367  *
3368  *  1. File content is read either using opt_file from the
3369  *     filesystem or using git-cat-file.
3370  *  2. Then blame information is incrementally added by
3371  *     reading output from git-blame.
3372  */
3374 struct blame_commit {
3375         char id[SIZEOF_REV];            /* SHA1 ID. */
3376         char title[128];                /* First line of the commit message. */
3377         char author[75];                /* Author of the commit. */
3378         struct tm time;                 /* Date from the author ident. */
3379         char filename[128];             /* Name of file. */
3380 };
3382 struct blame {
3383         struct blame_commit *commit;
3384         unsigned int header:1;
3385         char text[1];
3386 };
3388 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3389 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3391 static bool
3392 blame_open(struct view *view)
3394         char path[SIZEOF_STR];
3395         char ref[SIZEOF_STR] = "";
3397         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3398                 return FALSE;
3400         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3401                 return FALSE;
3403         if (*opt_ref) {
3404                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3405                         return FALSE;
3406         } else {
3407                 view->pipe = fopen(opt_file, "r");
3408                 if (!view->pipe &&
3409                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3410                         return FALSE;
3411         }
3413         if (!view->pipe)
3414                 view->pipe = popen(view->cmd, "r");
3415         if (!view->pipe)
3416                 return FALSE;
3418         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3419                 return FALSE;
3421         string_format(view->ref, "%s ...", opt_file);
3422         string_copy_rev(view->vid, opt_file);
3423         set_nonblocking_input(TRUE);
3425         if (view->line) {
3426                 int i;
3428                 for (i = 0; i < view->lines; i++)
3429                         free(view->line[i].data);
3430                 free(view->line);
3431         }
3433         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3434         view->offset = view->lines  = view->lineno = 0;
3435         view->line = NULL;
3436         view->start_time = time(NULL);
3438         return TRUE;
3441 static struct blame_commit *
3442 get_blame_commit(struct view *view, const char *id)
3444         size_t i;
3446         for (i = 0; i < view->lines; i++) {
3447                 struct blame *blame = view->line[i].data;
3449                 if (!blame->commit)
3450                         continue;
3452                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3453                         return blame->commit;
3454         }
3456         {
3457                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3459                 if (commit)
3460                         string_ncopy(commit->id, id, SIZEOF_REV);
3461                 return commit;
3462         }
3465 static bool
3466 parse_number(char **posref, size_t *number, size_t min, size_t max)
3468         char *pos = *posref;
3470         *posref = NULL;
3471         pos = strchr(pos + 1, ' ');
3472         if (!pos || !isdigit(pos[1]))
3473                 return FALSE;
3474         *number = atoi(pos + 1);
3475         if (*number < min || *number > max)
3476                 return FALSE;
3478         *posref = pos;
3479         return TRUE;
3482 static struct blame_commit *
3483 parse_blame_commit(struct view *view, char *text, int *blamed)
3485         struct blame_commit *commit;
3486         struct blame *blame;
3487         char *pos = text + SIZEOF_REV - 1;
3488         size_t lineno;
3489         size_t group;
3491         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3492                 return NULL;
3494         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3495             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3496                 return NULL;
3498         commit = get_blame_commit(view, text);
3499         if (!commit)
3500                 return NULL;
3502         *blamed += group;
3503         while (group--) {
3504                 struct line *line = &view->line[lineno + group - 1];
3506                 blame = line->data;
3507                 blame->commit = commit;
3508                 blame->header = !group;
3509                 line->dirty = 1;
3510         }
3512         return commit;
3515 static bool
3516 blame_read_file(struct view *view, char *line)
3518         if (!line) {
3519                 FILE *pipe = NULL;
3521                 if (view->lines > 0)
3522                         pipe = popen(view->cmd, "r");
3523                 else if (!view->parent)
3524                         die("No blame exist for %s", view->vid);
3525                 view->cmd[0] = 0;
3526                 if (!pipe) {
3527                         report("Failed to load blame data");
3528                         return TRUE;
3529                 }
3531                 fclose(view->pipe);
3532                 view->pipe = pipe;
3533                 return FALSE;
3535         } else {
3536                 size_t linelen = strlen(line);
3537                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3539                 if (!line)
3540                         return FALSE;
3542                 blame->commit = NULL;
3543                 strncpy(blame->text, line, linelen);
3544                 blame->text[linelen] = 0;
3545                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3546         }
3549 static bool
3550 match_blame_header(const char *name, char **line)
3552         size_t namelen = strlen(name);
3553         bool matched = !strncmp(name, *line, namelen);
3555         if (matched)
3556                 *line += namelen;
3558         return matched;
3561 static bool
3562 blame_read(struct view *view, char *line)
3564         static struct blame_commit *commit = NULL;
3565         static int blamed = 0;
3566         static time_t author_time;
3568         if (*view->cmd)
3569                 return blame_read_file(view, line);
3571         if (!line) {
3572                 /* Reset all! */
3573                 commit = NULL;
3574                 blamed = 0;
3575                 string_format(view->ref, "%s", view->vid);
3576                 if (view_is_displayed(view)) {
3577                         update_view_title(view);
3578                         redraw_view_from(view, 0);
3579                 }
3580                 return TRUE;
3581         }
3583         if (!commit) {
3584                 commit = parse_blame_commit(view, line, &blamed);
3585                 string_format(view->ref, "%s %2d%%", view->vid,
3586                               blamed * 100 / view->lines);
3588         } else if (match_blame_header("author ", &line)) {
3589                 string_ncopy(commit->author, line, strlen(line));
3591         } else if (match_blame_header("author-time ", &line)) {
3592                 author_time = (time_t) atol(line);
3594         } else if (match_blame_header("author-tz ", &line)) {
3595                 long tz;
3597                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3598                 tz += ('0' - line[2]) * 60 * 60;
3599                 tz += ('0' - line[3]) * 60;
3600                 tz += ('0' - line[4]) * 60;
3602                 if (line[0] == '-')
3603                         tz = -tz;
3605                 author_time -= tz;
3606                 gmtime_r(&author_time, &commit->time);
3608         } else if (match_blame_header("summary ", &line)) {
3609                 string_ncopy(commit->title, line, strlen(line));
3611         } else if (match_blame_header("filename ", &line)) {
3612                 string_ncopy(commit->filename, line, strlen(line));
3613                 commit = NULL;
3614         }
3616         return TRUE;
3619 static bool
3620 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3622         struct blame *blame = line->data;
3623         int col = 0;
3625         wmove(view->win, lineno, 0);
3627         if (selected) {
3628                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3629                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3630         } else {
3631                 wattrset(view->win, A_NORMAL);
3632         }
3634         if (opt_date) {
3635                 struct tm *time = blame->commit && *blame->commit->filename
3636                                 ? &blame->commit->time : NULL;
3638                 col += draw_date(view, time, view->width, selected);
3639                 if (col >= view->width)
3640                         return TRUE;
3641         }
3643         if (opt_author) {
3644                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3646                 if (!selected)
3647                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3648                 if (blame->commit)
3649                         draw_text(view, blame->commit->author, max, TRUE, selected);
3650                 col += AUTHOR_COLS;
3651                 if (col >= view->width)
3652                         return TRUE;
3653                 wmove(view->win, lineno, col);
3654         }
3656         {
3657                 int max = MIN(ID_COLS - 1, view->width - col);
3659                 if (!selected)
3660                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3661                 if (blame->commit)
3662                         draw_text(view, blame->commit->id, max, FALSE, -1);
3663                 col += ID_COLS;
3664                 if (col >= view->width)
3665                         return TRUE;
3666                 wmove(view->win, lineno, col);
3667         }
3669         {
3670                 col += draw_lineno(view, lineno, view->width - col, selected);
3671                 if (col >= view->width)
3672                         return TRUE;
3673         }
3675         col += draw_text(view, blame->text, view->width - col, TRUE, selected);
3677         return TRUE;
3680 static enum request
3681 blame_request(struct view *view, enum request request, struct line *line)
3683         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3684         struct blame *blame = line->data;
3686         switch (request) {
3687         case REQ_ENTER:
3688                 if (!blame->commit) {
3689                         report("No commit loaded yet");
3690                         break;
3691                 }
3693                 if (!strcmp(blame->commit->id, NULL_ID)) {
3694                         char path[SIZEOF_STR];
3696                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3697                                 break;
3698                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3699                 }
3701                 open_view(view, REQ_VIEW_DIFF, flags);
3702                 break;
3704         default:
3705                 return request;
3706         }
3708         return REQ_NONE;
3711 static bool
3712 blame_grep(struct view *view, struct line *line)
3714         struct blame *blame = line->data;
3715         struct blame_commit *commit = blame->commit;
3716         regmatch_t pmatch;
3718 #define MATCH(text) \
3719         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3721         if (commit) {
3722                 char buf[DATE_COLS + 1];
3724                 if (MATCH(commit->title) ||
3725                     MATCH(commit->author) ||
3726                     MATCH(commit->id))
3727                         return TRUE;
3729                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3730                     MATCH(buf))
3731                         return TRUE;
3732         }
3734         return MATCH(blame->text);
3736 #undef MATCH
3739 static void
3740 blame_select(struct view *view, struct line *line)
3742         struct blame *blame = line->data;
3743         struct blame_commit *commit = blame->commit;
3745         if (!commit)
3746                 return;
3748         if (!strcmp(commit->id, NULL_ID))
3749                 string_ncopy(ref_commit, "HEAD", 4);
3750         else
3751                 string_copy_rev(ref_commit, commit->id);
3754 static struct view_ops blame_ops = {
3755         "line",
3756         blame_open,
3757         blame_read,
3758         blame_draw,
3759         blame_request,
3760         blame_grep,
3761         blame_select,
3762 };
3764 /*
3765  * Status backend
3766  */
3768 struct status {
3769         char status;
3770         struct {
3771                 mode_t mode;
3772                 char rev[SIZEOF_REV];
3773                 char name[SIZEOF_STR];
3774         } old;
3775         struct {
3776                 mode_t mode;
3777                 char rev[SIZEOF_REV];
3778                 char name[SIZEOF_STR];
3779         } new;
3780 };
3782 static char status_onbranch[SIZEOF_STR];
3783 static struct status stage_status;
3784 static enum line_type stage_line_type;
3786 /* Get fields from the diff line:
3787  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3788  */
3789 static inline bool
3790 status_get_diff(struct status *file, char *buf, size_t bufsize)
3792         char *old_mode = buf +  1;
3793         char *new_mode = buf +  8;
3794         char *old_rev  = buf + 15;
3795         char *new_rev  = buf + 56;
3796         char *status   = buf + 97;
3798         if (bufsize < 99 ||
3799             old_mode[-1] != ':' ||
3800             new_mode[-1] != ' ' ||
3801             old_rev[-1]  != ' ' ||
3802             new_rev[-1]  != ' ' ||
3803             status[-1]   != ' ')
3804                 return FALSE;
3806         file->status = *status;
3808         string_copy_rev(file->old.rev, old_rev);
3809         string_copy_rev(file->new.rev, new_rev);
3811         file->old.mode = strtoul(old_mode, NULL, 8);
3812         file->new.mode = strtoul(new_mode, NULL, 8);
3814         file->old.name[0] = file->new.name[0] = 0;
3816         return TRUE;
3819 static bool
3820 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3822         struct status *file = NULL;
3823         struct status *unmerged = NULL;
3824         char buf[SIZEOF_STR * 4];
3825         size_t bufsize = 0;
3826         FILE *pipe;
3828         pipe = popen(cmd, "r");
3829         if (!pipe)
3830                 return FALSE;
3832         add_line_data(view, NULL, type);
3834         while (!feof(pipe) && !ferror(pipe)) {
3835                 char *sep;
3836                 size_t readsize;
3838                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3839                 if (!readsize)
3840                         break;
3841                 bufsize += readsize;
3843                 /* Process while we have NUL chars. */
3844                 while ((sep = memchr(buf, 0, bufsize))) {
3845                         size_t sepsize = sep - buf + 1;
3847                         if (!file) {
3848                                 if (!realloc_lines(view, view->line_size + 1))
3849                                         goto error_out;
3851                                 file = calloc(1, sizeof(*file));
3852                                 if (!file)
3853                                         goto error_out;
3855                                 add_line_data(view, file, type);
3856                         }
3858                         /* Parse diff info part. */
3859                         if (status) {
3860                                 file->status = status;
3861                                 if (status == 'A')
3862                                         string_copy(file->old.rev, NULL_ID);
3864                         } else if (!file->status) {
3865                                 if (!status_get_diff(file, buf, sepsize))
3866                                         goto error_out;
3868                                 bufsize -= sepsize;
3869                                 memmove(buf, sep + 1, bufsize);
3871                                 sep = memchr(buf, 0, bufsize);
3872                                 if (!sep)
3873                                         break;
3874                                 sepsize = sep - buf + 1;
3876                                 /* Collapse all 'M'odified entries that
3877                                  * follow a associated 'U'nmerged entry.
3878                                  */
3879                                 if (file->status == 'U') {
3880                                         unmerged = file;
3882                                 } else if (unmerged) {
3883                                         int collapse = !strcmp(buf, unmerged->new.name);
3885                                         unmerged = NULL;
3886                                         if (collapse) {
3887                                                 free(file);
3888                                                 view->lines--;
3889                                                 continue;
3890                                         }
3891                                 }
3892                         }
3894                         /* Grab the old name for rename/copy. */
3895                         if (!*file->old.name &&
3896                             (file->status == 'R' || file->status == 'C')) {
3897                                 sepsize = sep - buf + 1;
3898                                 string_ncopy(file->old.name, buf, sepsize);
3899                                 bufsize -= sepsize;
3900                                 memmove(buf, sep + 1, bufsize);
3902                                 sep = memchr(buf, 0, bufsize);
3903                                 if (!sep)
3904                                         break;
3905                                 sepsize = sep - buf + 1;
3906                         }
3908                         /* git-ls-files just delivers a NUL separated
3909                          * list of file names similar to the second half
3910                          * of the git-diff-* output. */
3911                         string_ncopy(file->new.name, buf, sepsize);
3912                         if (!*file->old.name)
3913                                 string_copy(file->old.name, file->new.name);
3914                         bufsize -= sepsize;
3915                         memmove(buf, sep + 1, bufsize);
3916                         file = NULL;
3917                 }
3918         }
3920         if (ferror(pipe)) {
3921 error_out:
3922                 pclose(pipe);
3923                 return FALSE;
3924         }
3926         if (!view->line[view->lines - 1].data)
3927                 add_line_data(view, NULL, LINE_STAT_NONE);
3929         pclose(pipe);
3930         return TRUE;
3933 /* Don't show unmerged entries in the staged section. */
3934 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3935 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3936 #define STATUS_LIST_OTHER_CMD \
3937         "git ls-files -z --others --exclude-per-directory=.gitignore"
3938 #define STATUS_LIST_NO_HEAD_CMD \
3939         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3941 #define STATUS_DIFF_INDEX_SHOW_CMD \
3942         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3944 #define STATUS_DIFF_FILES_SHOW_CMD \
3945         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3947 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3948         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3950 /* First parse staged info using git-diff-index(1), then parse unstaged
3951  * info using git-diff-files(1), and finally untracked files using
3952  * git-ls-files(1). */
3953 static bool
3954 status_open(struct view *view)
3956         struct stat statbuf;
3957         char exclude[SIZEOF_STR];
3958         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3959         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3960         unsigned long prev_lineno = view->lineno;
3961         char indexstatus = 0;
3962         size_t i;
3964         for (i = 0; i < view->lines; i++)
3965                 free(view->line[i].data);
3966         free(view->line);
3967         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3968         view->line = NULL;
3970         if (!realloc_lines(view, view->line_size + 7))
3971                 return FALSE;
3973         add_line_data(view, NULL, LINE_STAT_HEAD);
3974         if (opt_no_head)
3975                 string_copy(status_onbranch, "Initial commit");
3976         else if (!*opt_head)
3977                 string_copy(status_onbranch, "Not currently on any branch");
3978         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3979                 return FALSE;
3981         if (opt_no_head) {
3982                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3983                 indexstatus = 'A';
3984         }
3986         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3987                 return FALSE;
3989         if (stat(exclude, &statbuf) >= 0) {
3990                 size_t cmdsize = strlen(othercmd);
3992                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3993                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3994                         return FALSE;
3996                 cmdsize = strlen(indexcmd);
3997                 if (opt_no_head &&
3998                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3999                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4000                         return FALSE;
4001         }
4003         system("git update-index -q --refresh 2>/dev/null");
4005         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4006             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4007             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4008                 return FALSE;
4010         /* If all went well restore the previous line number to stay in
4011          * the context or select a line with something that can be
4012          * updated. */
4013         if (prev_lineno >= view->lines)
4014                 prev_lineno = view->lines - 1;
4015         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4016                 prev_lineno++;
4017         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4018                 prev_lineno--;
4020         /* If the above fails, always skip the "On branch" line. */
4021         if (prev_lineno < view->lines)
4022                 view->lineno = prev_lineno;
4023         else
4024                 view->lineno = 1;
4026         if (view->lineno < view->offset)
4027                 view->offset = view->lineno;
4028         else if (view->offset + view->height <= view->lineno)
4029                 view->offset = view->lineno - view->height + 1;
4031         return TRUE;
4034 static bool
4035 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4037         struct status *status = line->data;
4038         char *text;
4039         int col = 0;
4041         wmove(view->win, lineno, 0);
4043         if (selected) {
4044                 wattrset(view->win, get_line_attr(LINE_CURSOR));
4045                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4047         } else if (line->type == LINE_STAT_HEAD) {
4048                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4049                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4051         } else if (!status && line->type != LINE_STAT_NONE) {
4052                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4053                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4055         } else {
4056                 wattrset(view->win, get_line_attr(line->type));
4057         }
4059         if (!status) {
4060                 switch (line->type) {
4061                 case LINE_STAT_STAGED:
4062                         text = "Changes to be committed:";
4063                         break;
4065                 case LINE_STAT_UNSTAGED:
4066                         text = "Changed but not updated:";
4067                         break;
4069                 case LINE_STAT_UNTRACKED:
4070                         text = "Untracked files:";
4071                         break;
4073                 case LINE_STAT_NONE:
4074                         text = "    (no files)";
4075                         break;
4077                 case LINE_STAT_HEAD:
4078                         text = status_onbranch;
4079                         break;
4081                 default:
4082                         return FALSE;
4083                 }
4084         } else {
4085                 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4087                 col += draw_text(view, buf, view->width, TRUE, selected);
4088                 if (!selected)
4089                         wattrset(view->win, A_NORMAL);
4090                 text = status->new.name;
4091         }
4093         draw_text(view, text, view->width - col, TRUE, selected);
4094         return TRUE;
4097 static enum request
4098 status_enter(struct view *view, struct line *line)
4100         struct status *status = line->data;
4101         char oldpath[SIZEOF_STR] = "";
4102         char newpath[SIZEOF_STR] = "";
4103         char *info;
4104         size_t cmdsize = 0;
4105         enum open_flags split;
4107         if (line->type == LINE_STAT_NONE ||
4108             (!status && line[1].type == LINE_STAT_NONE)) {
4109                 report("No file to diff");
4110                 return REQ_NONE;
4111         }
4113         if (status) {
4114                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4115                         return REQ_QUIT;
4116                 /* Diffs for unmerged entries are empty when pasing the
4117                  * new path, so leave it empty. */
4118                 if (status->status != 'U' &&
4119                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4120                         return REQ_QUIT;
4121         }
4123         if (opt_cdup[0] &&
4124             line->type != LINE_STAT_UNTRACKED &&
4125             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4126                 return REQ_QUIT;
4128         switch (line->type) {
4129         case LINE_STAT_STAGED:
4130                 if (opt_no_head) {
4131                         if (!string_format_from(opt_cmd, &cmdsize,
4132                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4133                                                 newpath))
4134                                 return REQ_QUIT;
4135                 } else {
4136                         if (!string_format_from(opt_cmd, &cmdsize,
4137                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4138                                                 oldpath, newpath))
4139                                 return REQ_QUIT;
4140                 }
4142                 if (status)
4143                         info = "Staged changes to %s";
4144                 else
4145                         info = "Staged changes";
4146                 break;
4148         case LINE_STAT_UNSTAGED:
4149                 if (!string_format_from(opt_cmd, &cmdsize,
4150                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4151                         return REQ_QUIT;
4152                 if (status)
4153                         info = "Unstaged changes to %s";
4154                 else
4155                         info = "Unstaged changes";
4156                 break;
4158         case LINE_STAT_UNTRACKED:
4159                 if (opt_pipe)
4160                         return REQ_QUIT;
4162                 if (!status) {
4163                         report("No file to show");
4164                         return REQ_NONE;
4165                 }
4167                 opt_pipe = fopen(status->new.name, "r");
4168                 info = "Untracked file %s";
4169                 break;
4171         case LINE_STAT_HEAD:
4172                 return REQ_NONE;
4174         default:
4175                 die("line type %d not handled in switch", line->type);
4176         }
4178         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4179         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4180         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4181                 if (status) {
4182                         stage_status = *status;
4183                 } else {
4184                         memset(&stage_status, 0, sizeof(stage_status));
4185                 }
4187                 stage_line_type = line->type;
4188                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4189         }
4191         return REQ_NONE;
4194 static bool
4195 status_exists(struct status *status, enum line_type type)
4197         struct view *view = VIEW(REQ_VIEW_STATUS);
4198         struct line *line;
4200         for (line = view->line; line < view->line + view->lines; line++) {
4201                 struct status *pos = line->data;
4203                 if (line->type == type && pos &&
4204                     !strcmp(status->new.name, pos->new.name))
4205                         return TRUE;
4206         }
4208         return FALSE;
4212 static FILE *
4213 status_update_prepare(enum line_type type)
4215         char cmd[SIZEOF_STR];
4216         size_t cmdsize = 0;
4218         if (opt_cdup[0] &&
4219             type != LINE_STAT_UNTRACKED &&
4220             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4221                 return NULL;
4223         switch (type) {
4224         case LINE_STAT_STAGED:
4225                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4226                 break;
4228         case LINE_STAT_UNSTAGED:
4229         case LINE_STAT_UNTRACKED:
4230                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4231                 break;
4233         default:
4234                 die("line type %d not handled in switch", type);
4235         }
4237         return popen(cmd, "w");
4240 static bool
4241 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4243         char buf[SIZEOF_STR];
4244         size_t bufsize = 0;
4245         size_t written = 0;
4247         switch (type) {
4248         case LINE_STAT_STAGED:
4249                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4250                                         status->old.mode,
4251                                         status->old.rev,
4252                                         status->old.name, 0))
4253                         return FALSE;
4254                 break;
4256         case LINE_STAT_UNSTAGED:
4257         case LINE_STAT_UNTRACKED:
4258                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4259                         return FALSE;
4260                 break;
4262         default:
4263                 die("line type %d not handled in switch", type);
4264         }
4266         while (!ferror(pipe) && written < bufsize) {
4267                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4268         }
4270         return written == bufsize;
4273 static bool
4274 status_update_file(struct status *status, enum line_type type)
4276         FILE *pipe = status_update_prepare(type);
4277         bool result;
4279         if (!pipe)
4280                 return FALSE;
4282         result = status_update_write(pipe, status, type);
4283         pclose(pipe);
4284         return result;
4287 static bool
4288 status_update_files(struct view *view, struct line *line)
4290         FILE *pipe = status_update_prepare(line->type);
4291         bool result = TRUE;
4292         struct line *pos = view->line + view->lines;
4293         int files = 0;
4294         int file, done;
4296         if (!pipe)
4297                 return FALSE;
4299         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4300                 files++;
4302         for (file = 0, done = 0; result && file < files; line++, file++) {
4303                 int almost_done = file * 100 / files;
4305                 if (almost_done > done) {
4306                         done = almost_done;
4307                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4308                                       file, files, done);
4309                         update_view_title(view);
4310                 }
4311                 result = status_update_write(pipe, line->data, line->type);
4312         }
4314         pclose(pipe);
4315         return result;
4318 static bool
4319 status_update(struct view *view)
4321         struct line *line = &view->line[view->lineno];
4323         assert(view->lines);
4325         if (!line->data) {
4326                 /* This should work even for the "On branch" line. */
4327                 if (line < view->line + view->lines && !line[1].data) {
4328                         report("Nothing to update");
4329                         return FALSE;
4330                 }
4332                 if (!status_update_files(view, line + 1))
4333                         report("Failed to update file status");
4335         } else if (!status_update_file(line->data, line->type)) {
4336                 report("Failed to update file status");
4337         }
4339         return TRUE;
4342 static enum request
4343 status_request(struct view *view, enum request request, struct line *line)
4345         struct status *status = line->data;
4347         switch (request) {
4348         case REQ_STATUS_UPDATE:
4349                 if (!status_update(view))
4350                         return REQ_NONE;
4351                 break;
4353         case REQ_STATUS_MERGE:
4354                 if (!status || status->status != 'U') {
4355                         report("Merging only possible for files with unmerged status ('U').");
4356                         return REQ_NONE;
4357                 }
4358                 open_mergetool(status->new.name);
4359                 break;
4361         case REQ_EDIT:
4362                 if (!status)
4363                         return request;
4365                 open_editor(status->status != '?', status->new.name);
4366                 break;
4368         case REQ_VIEW_BLAME:
4369                 if (status) {
4370                         string_copy(opt_file, status->new.name);
4371                         opt_ref[0] = 0;
4372                 }
4373                 return request;
4375         case REQ_ENTER:
4376                 /* After returning the status view has been split to
4377                  * show the stage view. No further reloading is
4378                  * necessary. */
4379                 status_enter(view, line);
4380                 return REQ_NONE;
4382         case REQ_REFRESH:
4383                 /* Simply reload the view. */
4384                 break;
4386         default:
4387                 return request;
4388         }
4390         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4392         return REQ_NONE;
4395 static void
4396 status_select(struct view *view, struct line *line)
4398         struct status *status = line->data;
4399         char file[SIZEOF_STR] = "all files";
4400         char *text;
4401         char *key;
4403         if (status && !string_format(file, "'%s'", status->new.name))
4404                 return;
4406         if (!status && line[1].type == LINE_STAT_NONE)
4407                 line++;
4409         switch (line->type) {
4410         case LINE_STAT_STAGED:
4411                 text = "Press %s to unstage %s for commit";
4412                 break;
4414         case LINE_STAT_UNSTAGED:
4415                 text = "Press %s to stage %s for commit";
4416                 break;
4418         case LINE_STAT_UNTRACKED:
4419                 text = "Press %s to stage %s for addition";
4420                 break;
4422         case LINE_STAT_HEAD:
4423         case LINE_STAT_NONE:
4424                 text = "Nothing to update";
4425                 break;
4427         default:
4428                 die("line type %d not handled in switch", line->type);
4429         }
4431         if (status && status->status == 'U') {
4432                 text = "Press %s to resolve conflict in %s";
4433                 key = get_key(REQ_STATUS_MERGE);
4435         } else {
4436                 key = get_key(REQ_STATUS_UPDATE);
4437         }
4439         string_format(view->ref, text, key, file);
4442 static bool
4443 status_grep(struct view *view, struct line *line)
4445         struct status *status = line->data;
4446         enum { S_STATUS, S_NAME, S_END } state;
4447         char buf[2] = "?";
4448         regmatch_t pmatch;
4450         if (!status)
4451                 return FALSE;
4453         for (state = S_STATUS; state < S_END; state++) {
4454                 char *text;
4456                 switch (state) {
4457                 case S_NAME:    text = status->new.name;        break;
4458                 case S_STATUS:
4459                         buf[0] = status->status;
4460                         text = buf;
4461                         break;
4463                 default:
4464                         return FALSE;
4465                 }
4467                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4468                         return TRUE;
4469         }
4471         return FALSE;
4474 static struct view_ops status_ops = {
4475         "file",
4476         status_open,
4477         NULL,
4478         status_draw,
4479         status_request,
4480         status_grep,
4481         status_select,
4482 };
4485 static bool
4486 stage_diff_line(FILE *pipe, struct line *line)
4488         char *buf = line->data;
4489         size_t bufsize = strlen(buf);
4490         size_t written = 0;
4492         while (!ferror(pipe) && written < bufsize) {
4493                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4494         }
4496         fputc('\n', pipe);
4498         return written == bufsize;
4501 static bool
4502 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4504         while (line < end) {
4505                 if (!stage_diff_line(pipe, line++))
4506                         return FALSE;
4507                 if (line->type == LINE_DIFF_CHUNK ||
4508                     line->type == LINE_DIFF_HEADER)
4509                         break;
4510         }
4512         return TRUE;
4515 static struct line *
4516 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4518         for (; view->line < line; line--)
4519                 if (line->type == type)
4520                         return line;
4522         return NULL;
4525 static bool
4526 stage_update_chunk(struct view *view, struct line *chunk)
4528         char cmd[SIZEOF_STR];
4529         size_t cmdsize = 0;
4530         struct line *diff_hdr;
4531         FILE *pipe;
4533         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4534         if (!diff_hdr)
4535                 return FALSE;
4537         if (opt_cdup[0] &&
4538             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4539                 return FALSE;
4541         if (!string_format_from(cmd, &cmdsize,
4542                                 "git apply --whitespace=nowarn --cached %s - && "
4543                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4544                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4545                 return FALSE;
4547         pipe = popen(cmd, "w");
4548         if (!pipe)
4549                 return FALSE;
4551         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4552             !stage_diff_write(pipe, chunk, view->line + view->lines))
4553                 chunk = NULL;
4555         pclose(pipe);
4557         return chunk ? TRUE : FALSE;
4560 static bool
4561 stage_update(struct view *view, struct line *line)
4563         struct line *chunk = NULL;
4565         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4566                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4568         if (chunk) {
4569                 if (!stage_update_chunk(view, chunk)) {
4570                         report("Failed to apply chunk");
4571                         return FALSE;
4572                 }
4574         } else if (!status_update_file(&stage_status, stage_line_type)) {
4575                 report("Failed to update file");
4576                 return FALSE;
4577         }
4579         return TRUE;
4582 static enum request
4583 stage_request(struct view *view, enum request request, struct line *line)
4585         switch (request) {
4586         case REQ_STATUS_UPDATE:
4587                 stage_update(view, line);
4588                 break;
4590         case REQ_EDIT:
4591                 if (!stage_status.new.name[0])
4592                         return request;
4594                 open_editor(stage_status.status != '?', stage_status.new.name);
4595                 break;
4597         case REQ_REFRESH:
4598                 /* Reload everything ... */
4599                 break;
4601         case REQ_VIEW_BLAME:
4602                 if (stage_status.new.name[0]) {
4603                         string_copy(opt_file, stage_status.new.name);
4604                         opt_ref[0] = 0;
4605                 }
4606                 return request;
4608         case REQ_ENTER:
4609                 return pager_request(view, request, line);
4611         default:
4612                 return request;
4613         }
4615         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4617         /* Check whether the staged entry still exists, and close the
4618          * stage view if it doesn't. */
4619         if (!status_exists(&stage_status, stage_line_type))
4620                 return REQ_VIEW_CLOSE;
4622         if (stage_line_type == LINE_STAT_UNTRACKED)
4623                 opt_pipe = fopen(stage_status.new.name, "r");
4624         else
4625                 string_copy(opt_cmd, view->cmd);
4626         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4628         return REQ_NONE;
4631 static struct view_ops stage_ops = {
4632         "line",
4633         NULL,
4634         pager_read,
4635         pager_draw,
4636         stage_request,
4637         pager_grep,
4638         pager_select,
4639 };
4642 /*
4643  * Revision graph
4644  */
4646 struct commit {
4647         char id[SIZEOF_REV];            /* SHA1 ID. */
4648         char title[128];                /* First line of the commit message. */
4649         char author[75];                /* Author of the commit. */
4650         struct tm time;                 /* Date from the author ident. */
4651         struct ref **refs;              /* Repository references. */
4652         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4653         size_t graph_size;              /* The width of the graph array. */
4654         bool has_parents;               /* Rewritten --parents seen. */
4655 };
4657 /* Size of rev graph with no  "padding" columns */
4658 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4660 struct rev_graph {
4661         struct rev_graph *prev, *next, *parents;
4662         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4663         size_t size;
4664         struct commit *commit;
4665         size_t pos;
4666         unsigned int boundary:1;
4667 };
4669 /* Parents of the commit being visualized. */
4670 static struct rev_graph graph_parents[4];
4672 /* The current stack of revisions on the graph. */
4673 static struct rev_graph graph_stacks[4] = {
4674         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4675         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4676         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4677         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4678 };
4680 static inline bool
4681 graph_parent_is_merge(struct rev_graph *graph)
4683         return graph->parents->size > 1;
4686 static inline void
4687 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4689         struct commit *commit = graph->commit;
4691         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4692                 commit->graph[commit->graph_size++] = symbol;
4695 static void
4696 done_rev_graph(struct rev_graph *graph)
4698         if (graph_parent_is_merge(graph) &&
4699             graph->pos < graph->size - 1 &&
4700             graph->next->size == graph->size + graph->parents->size - 1) {
4701                 size_t i = graph->pos + graph->parents->size - 1;
4703                 graph->commit->graph_size = i * 2;
4704                 while (i < graph->next->size - 1) {
4705                         append_to_rev_graph(graph, ' ');
4706                         append_to_rev_graph(graph, '\\');
4707                         i++;
4708                 }
4709         }
4711         graph->size = graph->pos = 0;
4712         graph->commit = NULL;
4713         memset(graph->parents, 0, sizeof(*graph->parents));
4716 static void
4717 push_rev_graph(struct rev_graph *graph, char *parent)
4719         int i;
4721         /* "Collapse" duplicate parents lines.
4722          *
4723          * FIXME: This needs to also update update the drawn graph but
4724          * for now it just serves as a method for pruning graph lines. */
4725         for (i = 0; i < graph->size; i++)
4726                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4727                         return;
4729         if (graph->size < SIZEOF_REVITEMS) {
4730                 string_copy_rev(graph->rev[graph->size++], parent);
4731         }
4734 static chtype
4735 get_rev_graph_symbol(struct rev_graph *graph)
4737         chtype symbol;
4739         if (graph->boundary)
4740                 symbol = REVGRAPH_BOUND;
4741         else if (graph->parents->size == 0)
4742                 symbol = REVGRAPH_INIT;
4743         else if (graph_parent_is_merge(graph))
4744                 symbol = REVGRAPH_MERGE;
4745         else if (graph->pos >= graph->size)
4746                 symbol = REVGRAPH_BRANCH;
4747         else
4748                 symbol = REVGRAPH_COMMIT;
4750         return symbol;
4753 static void
4754 draw_rev_graph(struct rev_graph *graph)
4756         struct rev_filler {
4757                 chtype separator, line;
4758         };
4759         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4760         static struct rev_filler fillers[] = {
4761                 { ' ',  REVGRAPH_LINE },
4762                 { '`',  '.' },
4763                 { '\'', ' ' },
4764                 { '/',  ' ' },
4765         };
4766         chtype symbol = get_rev_graph_symbol(graph);
4767         struct rev_filler *filler;
4768         size_t i;
4770         filler = &fillers[DEFAULT];
4772         for (i = 0; i < graph->pos; i++) {
4773                 append_to_rev_graph(graph, filler->line);
4774                 if (graph_parent_is_merge(graph->prev) &&
4775                     graph->prev->pos == i)
4776                         filler = &fillers[RSHARP];
4778                 append_to_rev_graph(graph, filler->separator);
4779         }
4781         /* Place the symbol for this revision. */
4782         append_to_rev_graph(graph, symbol);
4784         if (graph->prev->size > graph->size)
4785                 filler = &fillers[RDIAG];
4786         else
4787                 filler = &fillers[DEFAULT];
4789         i++;
4791         for (; i < graph->size; i++) {
4792                 append_to_rev_graph(graph, filler->separator);
4793                 append_to_rev_graph(graph, filler->line);
4794                 if (graph_parent_is_merge(graph->prev) &&
4795                     i < graph->prev->pos + graph->parents->size)
4796                         filler = &fillers[RSHARP];
4797                 if (graph->prev->size > graph->size)
4798                         filler = &fillers[LDIAG];
4799         }
4801         if (graph->prev->size > graph->size) {
4802                 append_to_rev_graph(graph, filler->separator);
4803                 if (filler->line != ' ')
4804                         append_to_rev_graph(graph, filler->line);
4805         }
4808 /* Prepare the next rev graph */
4809 static void
4810 prepare_rev_graph(struct rev_graph *graph)
4812         size_t i;
4814         /* First, traverse all lines of revisions up to the active one. */
4815         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4816                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4817                         break;
4819                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4820         }
4822         /* Interleave the new revision parent(s). */
4823         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4824                 push_rev_graph(graph->next, graph->parents->rev[i]);
4826         /* Lastly, put any remaining revisions. */
4827         for (i = graph->pos + 1; i < graph->size; i++)
4828                 push_rev_graph(graph->next, graph->rev[i]);
4831 static void
4832 update_rev_graph(struct rev_graph *graph)
4834         /* If this is the finalizing update ... */
4835         if (graph->commit)
4836                 prepare_rev_graph(graph);
4838         /* Graph visualization needs a one rev look-ahead,
4839          * so the first update doesn't visualize anything. */
4840         if (!graph->prev->commit)
4841                 return;
4843         draw_rev_graph(graph->prev);
4844         done_rev_graph(graph->prev->prev);
4848 /*
4849  * Main view backend
4850  */
4852 static bool
4853 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4855         struct commit *commit = line->data;
4856         enum line_type type;
4857         int col = 0;
4859         if (!*commit->author)
4860                 return FALSE;
4862         wmove(view->win, lineno, col);
4864         if (selected) {
4865                 type = LINE_CURSOR;
4866                 wattrset(view->win, get_line_attr(type));
4867                 wchgat(view->win, -1, 0, type, NULL);
4868         } else {
4869                 type = LINE_MAIN_COMMIT;
4870         }
4872         if (opt_date) {
4873                 col += draw_date(view, &commit->time, view->width, selected);
4874                 if (col >= view->width)
4875                         return TRUE;
4876         }
4877         if (type != LINE_CURSOR)
4878                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4880         if (opt_author) {
4881                 int max_len;
4883                 max_len = view->width - col;
4884                 if (max_len > AUTHOR_COLS - 1)
4885                         max_len = AUTHOR_COLS - 1;
4886                 draw_text(view, commit->author, max_len, TRUE, selected);
4887                 col += AUTHOR_COLS;
4888                 if (col >= view->width)
4889                         return TRUE;
4890         }
4892         if (opt_rev_graph && commit->graph_size) {
4893                 size_t graph_size = view->width - col;
4894                 size_t i;
4896                 if (type != LINE_CURSOR)
4897                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4898                 wmove(view->win, lineno, col);
4899                 if (graph_size > commit->graph_size)
4900                         graph_size = commit->graph_size;
4901                 /* Using waddch() instead of waddnstr() ensures that
4902                  * they'll be rendered correctly for the cursor line. */
4903                 for (i = 0; i < graph_size; i++)
4904                         waddch(view->win, commit->graph[i]);
4906                 col += commit->graph_size + 1;
4907                 if (col >= view->width)
4908                         return TRUE;
4909                 waddch(view->win, ' ');
4910         }
4911         if (type != LINE_CURSOR)
4912                 wattrset(view->win, A_NORMAL);
4914         wmove(view->win, lineno, col);
4916         if (opt_show_refs && commit->refs) {
4917                 size_t i = 0;
4919                 do {
4920                         if (type == LINE_CURSOR)
4921                                 ;
4922                         else if (commit->refs[i]->head)
4923                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4924                         else if (commit->refs[i]->ltag)
4925                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4926                         else if (commit->refs[i]->tag)
4927                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4928                         else if (commit->refs[i]->tracked)
4929                                 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
4930                         else if (commit->refs[i]->remote)
4931                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4932                         else
4933                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4935                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4936                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4937                                          TRUE, selected);
4938                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4939                         if (type != LINE_CURSOR)
4940                                 wattrset(view->win, A_NORMAL);
4941                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4942                         if (col >= view->width)
4943                                 return TRUE;
4944                 } while (commit->refs[i++]->next);
4945         }
4947         if (type != LINE_CURSOR)
4948                 wattrset(view->win, get_line_attr(type));
4950         draw_text(view, commit->title, view->width - col, TRUE, selected);
4951         return TRUE;
4954 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4955 static bool
4956 main_read(struct view *view, char *line)
4958         static struct rev_graph *graph = graph_stacks;
4959         enum line_type type;
4960         struct commit *commit;
4962         if (!line) {
4963                 if (!view->lines && !view->parent)
4964                         die("No revisions match the given arguments.");
4965                 update_rev_graph(graph);
4966                 return TRUE;
4967         }
4969         type = get_line_type(line);
4970         if (type == LINE_COMMIT) {
4971                 commit = calloc(1, sizeof(struct commit));
4972                 if (!commit)
4973                         return FALSE;
4975                 line += STRING_SIZE("commit ");
4976                 if (*line == '-') {
4977                         graph->boundary = 1;
4978                         line++;
4979                 }
4981                 string_copy_rev(commit->id, line);
4982                 commit->refs = get_refs(commit->id);
4983                 graph->commit = commit;
4984                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4986                 while ((line = strchr(line, ' '))) {
4987                         line++;
4988                         push_rev_graph(graph->parents, line);
4989                         commit->has_parents = TRUE;
4990                 }
4991                 return TRUE;
4992         }
4994         if (!view->lines)
4995                 return TRUE;
4996         commit = view->line[view->lines - 1].data;
4998         switch (type) {
4999         case LINE_PARENT:
5000                 if (commit->has_parents)
5001                         break;
5002                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5003                 break;
5005         case LINE_AUTHOR:
5006         {
5007                 /* Parse author lines where the name may be empty:
5008                  *      author  <email@address.tld> 1138474660 +0100
5009                  */
5010                 char *ident = line + STRING_SIZE("author ");
5011                 char *nameend = strchr(ident, '<');
5012                 char *emailend = strchr(ident, '>');
5014                 if (!nameend || !emailend)
5015                         break;
5017                 update_rev_graph(graph);
5018                 graph = graph->next;
5020                 *nameend = *emailend = 0;
5021                 ident = chomp_string(ident);
5022                 if (!*ident) {
5023                         ident = chomp_string(nameend + 1);
5024                         if (!*ident)
5025                                 ident = "Unknown";
5026                 }
5028                 string_ncopy(commit->author, ident, strlen(ident));
5030                 /* Parse epoch and timezone */
5031                 if (emailend[1] == ' ') {
5032                         char *secs = emailend + 2;
5033                         char *zone = strchr(secs, ' ');
5034                         time_t time = (time_t) atol(secs);
5036                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5037                                 long tz;
5039                                 zone++;
5040                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5041                                 tz += ('0' - zone[2]) * 60 * 60;
5042                                 tz += ('0' - zone[3]) * 60;
5043                                 tz += ('0' - zone[4]) * 60;
5045                                 if (zone[0] == '-')
5046                                         tz = -tz;
5048                                 time -= tz;
5049                         }
5051                         gmtime_r(&time, &commit->time);
5052                 }
5053                 break;
5054         }
5055         default:
5056                 /* Fill in the commit title if it has not already been set. */
5057                 if (commit->title[0])
5058                         break;
5060                 /* Require titles to start with a non-space character at the
5061                  * offset used by git log. */
5062                 if (strncmp(line, "    ", 4))
5063                         break;
5064                 line += 4;
5065                 /* Well, if the title starts with a whitespace character,
5066                  * try to be forgiving.  Otherwise we end up with no title. */
5067                 while (isspace(*line))
5068                         line++;
5069                 if (*line == '\0')
5070                         break;
5071                 /* FIXME: More graceful handling of titles; append "..." to
5072                  * shortened titles, etc. */
5074                 string_ncopy(commit->title, line, strlen(line));
5075         }
5077         return TRUE;
5080 static enum request
5081 main_request(struct view *view, enum request request, struct line *line)
5083         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5085         if (request == REQ_ENTER)
5086                 open_view(view, REQ_VIEW_DIFF, flags);
5087         else
5088                 return request;
5090         return REQ_NONE;
5093 static bool
5094 main_grep(struct view *view, struct line *line)
5096         struct commit *commit = line->data;
5097         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5098         char buf[DATE_COLS + 1];
5099         regmatch_t pmatch;
5101         for (state = S_TITLE; state < S_END; state++) {
5102                 char *text;
5104                 switch (state) {
5105                 case S_TITLE:   text = commit->title;   break;
5106                 case S_AUTHOR:  text = commit->author;  break;
5107                 case S_DATE:
5108                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5109                                 continue;
5110                         text = buf;
5111                         break;
5113                 default:
5114                         return FALSE;
5115                 }
5117                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5118                         return TRUE;
5119         }
5121         return FALSE;
5124 static void
5125 main_select(struct view *view, struct line *line)
5127         struct commit *commit = line->data;
5129         string_copy_rev(view->ref, commit->id);
5130         string_copy_rev(ref_commit, view->ref);
5133 static struct view_ops main_ops = {
5134         "commit",
5135         NULL,
5136         main_read,
5137         main_draw,
5138         main_request,
5139         main_grep,
5140         main_select,
5141 };
5144 /*
5145  * Unicode / UTF-8 handling
5146  *
5147  * NOTE: Much of the following code for dealing with unicode is derived from
5148  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5149  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5150  */
5152 /* I've (over)annotated a lot of code snippets because I am not entirely
5153  * confident that the approach taken by this small UTF-8 interface is correct.
5154  * --jonas */
5156 static inline int
5157 unicode_width(unsigned long c)
5159         if (c >= 0x1100 &&
5160            (c <= 0x115f                         /* Hangul Jamo */
5161             || c == 0x2329
5162             || c == 0x232a
5163             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5164                                                 /* CJK ... Yi */
5165             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5166             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5167             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5168             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5169             || (c >= 0xffe0  && c <= 0xffe6)
5170             || (c >= 0x20000 && c <= 0x2fffd)
5171             || (c >= 0x30000 && c <= 0x3fffd)))
5172                 return 2;
5174         if (c == '\t')
5175                 return opt_tab_size;
5177         return 1;
5180 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5181  * Illegal bytes are set one. */
5182 static const unsigned char utf8_bytes[256] = {
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         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,
5188         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,
5189         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,
5190         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,
5191 };
5193 /* Decode UTF-8 multi-byte representation into a unicode character. */
5194 static inline unsigned long
5195 utf8_to_unicode(const char *string, size_t length)
5197         unsigned long unicode;
5199         switch (length) {
5200         case 1:
5201                 unicode  =   string[0];
5202                 break;
5203         case 2:
5204                 unicode  =  (string[0] & 0x1f) << 6;
5205                 unicode +=  (string[1] & 0x3f);
5206                 break;
5207         case 3:
5208                 unicode  =  (string[0] & 0x0f) << 12;
5209                 unicode += ((string[1] & 0x3f) << 6);
5210                 unicode +=  (string[2] & 0x3f);
5211                 break;
5212         case 4:
5213                 unicode  =  (string[0] & 0x0f) << 18;
5214                 unicode += ((string[1] & 0x3f) << 12);
5215                 unicode += ((string[2] & 0x3f) << 6);
5216                 unicode +=  (string[3] & 0x3f);
5217                 break;
5218         case 5:
5219                 unicode  =  (string[0] & 0x0f) << 24;
5220                 unicode += ((string[1] & 0x3f) << 18);
5221                 unicode += ((string[2] & 0x3f) << 12);
5222                 unicode += ((string[3] & 0x3f) << 6);
5223                 unicode +=  (string[4] & 0x3f);
5224                 break;
5225         case 6:
5226                 unicode  =  (string[0] & 0x01) << 30;
5227                 unicode += ((string[1] & 0x3f) << 24);
5228                 unicode += ((string[2] & 0x3f) << 18);
5229                 unicode += ((string[3] & 0x3f) << 12);
5230                 unicode += ((string[4] & 0x3f) << 6);
5231                 unicode +=  (string[5] & 0x3f);
5232                 break;
5233         default:
5234                 die("Invalid unicode length");
5235         }
5237         /* Invalid characters could return the special 0xfffd value but NUL
5238          * should be just as good. */
5239         return unicode > 0xffff ? 0 : unicode;
5242 /* Calculates how much of string can be shown within the given maximum width
5243  * and sets trimmed parameter to non-zero value if all of string could not be
5244  * shown. If the reserve flag is TRUE, it will reserve at least one
5245  * trailing character, which can be useful when drawing a delimiter.
5246  *
5247  * Returns the number of bytes to output from string to satisfy max_width. */
5248 static size_t
5249 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5251         const char *start = string;
5252         const char *end = strchr(string, '\0');
5253         unsigned char last_bytes = 0;
5254         size_t width = 0;
5256         *trimmed = 0;
5258         while (string < end) {
5259                 int c = *(unsigned char *) string;
5260                 unsigned char bytes = utf8_bytes[c];
5261                 size_t ucwidth;
5262                 unsigned long unicode;
5264                 if (string + bytes > end)
5265                         break;
5267                 /* Change representation to figure out whether
5268                  * it is a single- or double-width character. */
5270                 unicode = utf8_to_unicode(string, bytes);
5271                 /* FIXME: Graceful handling of invalid unicode character. */
5272                 if (!unicode)
5273                         break;
5275                 ucwidth = unicode_width(unicode);
5276                 width  += ucwidth;
5277                 if (width > max_width) {
5278                         *trimmed = 1;
5279                         if (reserve && width - ucwidth == max_width) {
5280                                 string -= last_bytes;
5281                         }
5282                         break;
5283                 }
5285                 string  += bytes;
5286                 last_bytes = bytes;
5287         }
5289         return string - start;
5293 /*
5294  * Status management
5295  */
5297 /* Whether or not the curses interface has been initialized. */
5298 static bool cursed = FALSE;
5300 /* The status window is used for polling keystrokes. */
5301 static WINDOW *status_win;
5303 static bool status_empty = TRUE;
5305 /* Update status and title window. */
5306 static void
5307 report(const char *msg, ...)
5309         struct view *view = display[current_view];
5311         if (input_mode)
5312                 return;
5314         if (!view) {
5315                 char buf[SIZEOF_STR];
5316                 va_list args;
5318                 va_start(args, msg);
5319                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5320                         buf[sizeof(buf) - 1] = 0;
5321                         buf[sizeof(buf) - 2] = '.';
5322                         buf[sizeof(buf) - 3] = '.';
5323                         buf[sizeof(buf) - 4] = '.';
5324                 }
5325                 va_end(args);
5326                 die("%s", buf);
5327         }
5329         if (!status_empty || *msg) {
5330                 va_list args;
5332                 va_start(args, msg);
5334                 wmove(status_win, 0, 0);
5335                 if (*msg) {
5336                         vwprintw(status_win, msg, args);
5337                         status_empty = FALSE;
5338                 } else {
5339                         status_empty = TRUE;
5340                 }
5341                 wclrtoeol(status_win);
5342                 wrefresh(status_win);
5344                 va_end(args);
5345         }
5347         update_view_title(view);
5348         update_display_cursor(view);
5351 /* Controls when nodelay should be in effect when polling user input. */
5352 static void
5353 set_nonblocking_input(bool loading)
5355         static unsigned int loading_views;
5357         if ((loading == FALSE && loading_views-- == 1) ||
5358             (loading == TRUE  && loading_views++ == 0))
5359                 nodelay(status_win, loading);
5362 static void
5363 init_display(void)
5365         int x, y;
5367         /* Initialize the curses library */
5368         if (isatty(STDIN_FILENO)) {
5369                 cursed = !!initscr();
5370         } else {
5371                 /* Leave stdin and stdout alone when acting as a pager. */
5372                 FILE *io = fopen("/dev/tty", "r+");
5374                 if (!io)
5375                         die("Failed to open /dev/tty");
5376                 cursed = !!newterm(NULL, io, io);
5377         }
5379         if (!cursed)
5380                 die("Failed to initialize curses");
5382         nonl();         /* Tell curses not to do NL->CR/NL on output */
5383         cbreak();       /* Take input chars one at a time, no wait for \n */
5384         noecho();       /* Don't echo input */
5385         leaveok(stdscr, TRUE);
5387         if (has_colors())
5388                 init_colors();
5390         getmaxyx(stdscr, y, x);
5391         status_win = newwin(1, 0, y - 1, 0);
5392         if (!status_win)
5393                 die("Failed to create status window");
5395         /* Enable keyboard mapping */
5396         keypad(status_win, TRUE);
5397         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5400 static char *
5401 read_prompt(const char *prompt)
5403         enum { READING, STOP, CANCEL } status = READING;
5404         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5405         int pos = 0;
5407         while (status == READING) {
5408                 struct view *view;
5409                 int i, key;
5411                 input_mode = TRUE;
5413                 foreach_view (view, i)
5414                         update_view(view);
5416                 input_mode = FALSE;
5418                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5419                 wclrtoeol(status_win);
5421                 /* Refresh, accept single keystroke of input */
5422                 key = wgetch(status_win);
5423                 switch (key) {
5424                 case KEY_RETURN:
5425                 case KEY_ENTER:
5426                 case '\n':
5427                         status = pos ? STOP : CANCEL;
5428                         break;
5430                 case KEY_BACKSPACE:
5431                         if (pos > 0)
5432                                 pos--;
5433                         else
5434                                 status = CANCEL;
5435                         break;
5437                 case KEY_ESC:
5438                         status = CANCEL;
5439                         break;
5441                 case ERR:
5442                         break;
5444                 default:
5445                         if (pos >= sizeof(buf)) {
5446                                 report("Input string too long");
5447                                 return NULL;
5448                         }
5450                         if (isprint(key))
5451                                 buf[pos++] = (char) key;
5452                 }
5453         }
5455         /* Clear the status window */
5456         status_empty = FALSE;
5457         report("");
5459         if (status == CANCEL)
5460                 return NULL;
5462         buf[pos++] = 0;
5464         return buf;
5467 /*
5468  * Repository references
5469  */
5471 static struct ref *refs = NULL;
5472 static size_t refs_alloc = 0;
5473 static size_t refs_size = 0;
5475 /* Id <-> ref store */
5476 static struct ref ***id_refs = NULL;
5477 static size_t id_refs_alloc = 0;
5478 static size_t id_refs_size = 0;
5480 static struct ref **
5481 get_refs(char *id)
5483         struct ref ***tmp_id_refs;
5484         struct ref **ref_list = NULL;
5485         size_t ref_list_alloc = 0;
5486         size_t ref_list_size = 0;
5487         size_t i;
5489         for (i = 0; i < id_refs_size; i++)
5490                 if (!strcmp(id, id_refs[i][0]->id))
5491                         return id_refs[i];
5493         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5494                                     sizeof(*id_refs));
5495         if (!tmp_id_refs)
5496                 return NULL;
5498         id_refs = tmp_id_refs;
5500         for (i = 0; i < refs_size; i++) {
5501                 struct ref **tmp;
5503                 if (strcmp(id, refs[i].id))
5504                         continue;
5506                 tmp = realloc_items(ref_list, &ref_list_alloc,
5507                                     ref_list_size + 1, sizeof(*ref_list));
5508                 if (!tmp) {
5509                         if (ref_list)
5510                                 free(ref_list);
5511                         return NULL;
5512                 }
5514                 ref_list = tmp;
5515                 if (ref_list_size > 0)
5516                         ref_list[ref_list_size - 1]->next = 1;
5517                 ref_list[ref_list_size] = &refs[i];
5519                 /* XXX: The properties of the commit chains ensures that we can
5520                  * safely modify the shared ref. The repo references will
5521                  * always be similar for the same id. */
5522                 ref_list[ref_list_size]->next = 0;
5523                 ref_list_size++;
5524         }
5526         if (ref_list)
5527                 id_refs[id_refs_size++] = ref_list;
5529         return ref_list;
5532 static int
5533 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5535         struct ref *ref;
5536         bool tag = FALSE;
5537         bool ltag = FALSE;
5538         bool remote = FALSE;
5539         bool tracked = FALSE;
5540         bool check_replace = FALSE;
5541         bool head = FALSE;
5543         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5544                 if (!strcmp(name + namelen - 3, "^{}")) {
5545                         namelen -= 3;
5546                         name[namelen] = 0;
5547                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5548                                 check_replace = TRUE;
5549                 } else {
5550                         ltag = TRUE;
5551                 }
5553                 tag = TRUE;
5554                 namelen -= STRING_SIZE("refs/tags/");
5555                 name    += STRING_SIZE("refs/tags/");
5557         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5558                 remote = TRUE;
5559                 namelen -= STRING_SIZE("refs/remotes/");
5560                 name    += STRING_SIZE("refs/remotes/");
5561                 tracked  = !strcmp(opt_remote, name);
5563         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5564                 namelen -= STRING_SIZE("refs/heads/");
5565                 name    += STRING_SIZE("refs/heads/");
5566                 head     = !strncmp(opt_head, name, namelen);
5568         } else if (!strcmp(name, "HEAD")) {
5569                 opt_no_head = FALSE;
5570                 return OK;
5571         }
5573         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5574                 /* it's an annotated tag, replace the previous sha1 with the
5575                  * resolved commit id; relies on the fact git-ls-remote lists
5576                  * the commit id of an annotated tag right beofre the commit id
5577                  * it points to. */
5578                 refs[refs_size - 1].ltag = ltag;
5579                 string_copy_rev(refs[refs_size - 1].id, id);
5581                 return OK;
5582         }
5583         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5584         if (!refs)
5585                 return ERR;
5587         ref = &refs[refs_size++];
5588         ref->name = malloc(namelen + 1);
5589         if (!ref->name)
5590                 return ERR;
5592         strncpy(ref->name, name, namelen);
5593         ref->name[namelen] = 0;
5594         ref->head = head;
5595         ref->tag = tag;
5596         ref->ltag = ltag;
5597         ref->remote = remote;
5598         ref->tracked = tracked;
5599         string_copy_rev(ref->id, id);
5601         return OK;
5604 static int
5605 load_refs(void)
5607         const char *cmd_env = getenv("TIG_LS_REMOTE");
5608         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5610         return read_properties(popen(cmd, "r"), "\t", read_ref);
5613 static int
5614 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5616         if (!strcmp(name, "i18n.commitencoding"))
5617                 string_ncopy(opt_encoding, value, valuelen);
5619         if (!strcmp(name, "core.editor"))
5620                 string_ncopy(opt_editor, value, valuelen);
5622         /* branch.<head>.remote */
5623         if (*opt_head &&
5624             !strncmp(name, "branch.", 7) &&
5625             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5626             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5627                 string_ncopy(opt_remote, value, valuelen);
5629         if (*opt_head && *opt_remote &&
5630             !strncmp(name, "branch.", 7) &&
5631             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5632             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5633                 size_t from = strlen(opt_remote);
5635                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5636                         value += STRING_SIZE("refs/heads/");
5637                         valuelen -= STRING_SIZE("refs/heads/");
5638                 }
5640                 if (!string_format_from(opt_remote, &from, "/%s", value))
5641                         opt_remote[0] = 0;
5642         }
5644         return OK;
5647 static int
5648 load_git_config(void)
5650         return read_properties(popen(GIT_CONFIG " --list", "r"),
5651                                "=", read_repo_config_option);
5654 static int
5655 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5657         if (!opt_git_dir[0]) {
5658                 string_ncopy(opt_git_dir, name, namelen);
5660         } else if (opt_is_inside_work_tree == -1) {
5661                 /* This can be 3 different values depending on the
5662                  * version of git being used. If git-rev-parse does not
5663                  * understand --is-inside-work-tree it will simply echo
5664                  * the option else either "true" or "false" is printed.
5665                  * Default to true for the unknown case. */
5666                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5668         } else if (opt_cdup[0] == ' ') {
5669                 string_ncopy(opt_cdup, name, namelen);
5670         } else {
5671                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5672                         namelen -= STRING_SIZE("refs/heads/");
5673                         name    += STRING_SIZE("refs/heads/");
5674                         string_ncopy(opt_head, name, namelen);
5675                 }
5676         }
5678         return OK;
5681 static int
5682 load_repo_info(void)
5684         int result;
5685         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5686                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5688         /* XXX: The line outputted by "--show-cdup" can be empty so
5689          * initialize it to something invalid to make it possible to
5690          * detect whether it has been set or not. */
5691         opt_cdup[0] = ' ';
5693         result = read_properties(pipe, "=", read_repo_info);
5694         if (opt_cdup[0] == ' ')
5695                 opt_cdup[0] = 0;
5697         return result;
5700 static int
5701 read_properties(FILE *pipe, const char *separators,
5702                 int (*read_property)(char *, size_t, char *, size_t))
5704         char buffer[BUFSIZ];
5705         char *name;
5706         int state = OK;
5708         if (!pipe)
5709                 return ERR;
5711         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5712                 char *value;
5713                 size_t namelen;
5714                 size_t valuelen;
5716                 name = chomp_string(name);
5717                 namelen = strcspn(name, separators);
5719                 if (name[namelen]) {
5720                         name[namelen] = 0;
5721                         value = chomp_string(name + namelen + 1);
5722                         valuelen = strlen(value);
5724                 } else {
5725                         value = "";
5726                         valuelen = 0;
5727                 }
5729                 state = read_property(name, namelen, value, valuelen);
5730         }
5732         if (state != ERR && ferror(pipe))
5733                 state = ERR;
5735         pclose(pipe);
5737         return state;
5741 /*
5742  * Main
5743  */
5745 static void __NORETURN
5746 quit(int sig)
5748         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5749         if (cursed)
5750                 endwin();
5751         exit(0);
5754 static void __NORETURN
5755 die(const char *err, ...)
5757         va_list args;
5759         endwin();
5761         va_start(args, err);
5762         fputs("tig: ", stderr);
5763         vfprintf(stderr, err, args);
5764         fputs("\n", stderr);
5765         va_end(args);
5767         exit(1);
5770 static void
5771 warn(const char *msg, ...)
5773         va_list args;
5775         va_start(args, msg);
5776         fputs("tig warning: ", stderr);
5777         vfprintf(stderr, msg, args);
5778         fputs("\n", stderr);
5779         va_end(args);
5782 int
5783 main(int argc, char *argv[])
5785         struct view *view;
5786         enum request request;
5787         size_t i;
5789         signal(SIGINT, quit);
5791         if (setlocale(LC_ALL, "")) {
5792                 char *codeset = nl_langinfo(CODESET);
5794                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5795         }
5797         if (load_repo_info() == ERR)
5798                 die("Failed to load repo info.");
5800         if (load_options() == ERR)
5801                 die("Failed to load user config.");
5803         if (load_git_config() == ERR)
5804                 die("Failed to load repo config.");
5806         if (!parse_options(argc, argv))
5807                 return 0;
5809         /* Require a git repository unless when running in pager mode. */
5810         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5811                 die("Not a git repository");
5813         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5814                 opt_utf8 = FALSE;
5816         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5817                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5818                 if (opt_iconv == ICONV_NONE)
5819                         die("Failed to initialize character set conversion");
5820         }
5822         if (*opt_git_dir && load_refs() == ERR)
5823                 die("Failed to load refs.");
5825         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5826                 view->cmd_env = getenv(view->cmd_env);
5828         request = opt_request;
5830         init_display();
5832         while (view_driver(display[current_view], request)) {
5833                 int key;
5834                 int i;
5836                 foreach_view (view, i)
5837                         update_view(view);
5839                 /* Refresh, accept single keystroke of input */
5840                 key = wgetch(status_win);
5842                 /* wgetch() with nodelay() enabled returns ERR when there's no
5843                  * input. */
5844                 if (key == ERR) {
5845                         request = REQ_NONE;
5846                         continue;
5847                 }
5849                 request = get_keybinding(display[current_view]->keymap, key);
5851                 /* Some low-level request handling. This keeps access to
5852                  * status_win restricted. */
5853                 switch (request) {
5854                 case REQ_PROMPT:
5855                 {
5856                         char *cmd = read_prompt(":");
5858                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5859                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5860                                         opt_request = REQ_VIEW_DIFF;
5861                                 } else {
5862                                         opt_request = REQ_VIEW_PAGER;
5863                                 }
5864                                 break;
5865                         }
5867                         request = REQ_NONE;
5868                         break;
5869                 }
5870                 case REQ_SEARCH:
5871                 case REQ_SEARCH_BACK:
5872                 {
5873                         const char *prompt = request == REQ_SEARCH
5874                                            ? "/" : "?";
5875                         char *search = read_prompt(prompt);
5877                         if (search)
5878                                 string_ncopy(opt_search, search, strlen(search));
5879                         else
5880                                 request = REQ_NONE;
5881                         break;
5882                 }
5883                 case REQ_SCREEN_RESIZE:
5884                 {
5885                         int height, width;
5887                         getmaxyx(stdscr, height, width);
5889                         /* Resize the status view and let the view driver take
5890                          * care of resizing the displayed views. */
5891                         wresize(status_win, 1, width);
5892                         mvwin(status_win, height - 1, 0);
5893                         wrefresh(status_win);
5894                         break;
5895                 }
5896                 default:
5897                         break;
5898                 }
5899         }
5901         quit(0);
5903         return 0;