Code

Share the line number colors between blame view and others
[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(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
591 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
592 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
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_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
611 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
612 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
613 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
615 enum line_type {
616 #define LINE(type, line, fg, bg, attr) \
617         LINE_##type
618         LINE_INFO
619 #undef  LINE
620 };
622 struct line_info {
623         const char *name;       /* Option name. */
624         int namelen;            /* Size of option name. */
625         const char *line;       /* The start of line to match. */
626         int linelen;            /* Size of string to match. */
627         int fg, bg, attr;       /* Color and text attributes for the lines. */
628 };
630 static struct line_info line_info[] = {
631 #define LINE(type, line, fg, bg, attr) \
632         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
633         LINE_INFO
634 #undef  LINE
635 };
637 static enum line_type
638 get_line_type(char *line)
640         int linelen = strlen(line);
641         enum line_type type;
643         for (type = 0; type < ARRAY_SIZE(line_info); type++)
644                 /* Case insensitive search matches Signed-off-by lines better. */
645                 if (linelen >= line_info[type].linelen &&
646                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
647                         return type;
649         return LINE_DEFAULT;
652 static inline int
653 get_line_attr(enum line_type type)
655         assert(type < ARRAY_SIZE(line_info));
656         return COLOR_PAIR(type) | line_info[type].attr;
659 static struct line_info *
660 get_line_info(char *name)
662         size_t namelen = strlen(name);
663         enum line_type type;
665         for (type = 0; type < ARRAY_SIZE(line_info); type++)
666                 if (namelen == line_info[type].namelen &&
667                     !string_enum_compare(line_info[type].name, name, namelen))
668                         return &line_info[type];
670         return NULL;
673 static void
674 init_colors(void)
676         int default_bg = line_info[LINE_DEFAULT].bg;
677         int default_fg = line_info[LINE_DEFAULT].fg;
678         enum line_type type;
680         start_color();
682         if (assume_default_colors(default_fg, default_bg) == ERR) {
683                 default_bg = COLOR_BLACK;
684                 default_fg = COLOR_WHITE;
685         }
687         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
688                 struct line_info *info = &line_info[type];
689                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
690                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
692                 init_pair(type, fg, bg);
693         }
696 struct line {
697         enum line_type type;
699         /* State flags */
700         unsigned int selected:1;
701         unsigned int dirty:1;
703         void *data;             /* User data */
704 };
707 /*
708  * Keys
709  */
711 struct keybinding {
712         int alias;
713         enum request request;
714         struct keybinding *next;
715 };
717 static struct keybinding default_keybindings[] = {
718         /* View switching */
719         { 'm',          REQ_VIEW_MAIN },
720         { 'd',          REQ_VIEW_DIFF },
721         { 'l',          REQ_VIEW_LOG },
722         { 't',          REQ_VIEW_TREE },
723         { 'f',          REQ_VIEW_BLOB },
724         { 'B',          REQ_VIEW_BLAME },
725         { 'p',          REQ_VIEW_PAGER },
726         { 'h',          REQ_VIEW_HELP },
727         { 'S',          REQ_VIEW_STATUS },
728         { 'c',          REQ_VIEW_STAGE },
730         /* View manipulation */
731         { 'q',          REQ_VIEW_CLOSE },
732         { KEY_TAB,      REQ_VIEW_NEXT },
733         { KEY_RETURN,   REQ_ENTER },
734         { KEY_UP,       REQ_PREVIOUS },
735         { KEY_DOWN,     REQ_NEXT },
736         { 'R',          REQ_REFRESH },
737         { 'M',          REQ_MAXIMIZE },
739         /* Cursor navigation */
740         { 'k',          REQ_MOVE_UP },
741         { 'j',          REQ_MOVE_DOWN },
742         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
743         { KEY_END,      REQ_MOVE_LAST_LINE },
744         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
745         { ' ',          REQ_MOVE_PAGE_DOWN },
746         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
747         { 'b',          REQ_MOVE_PAGE_UP },
748         { '-',          REQ_MOVE_PAGE_UP },
750         /* Scrolling */
751         { KEY_IC,       REQ_SCROLL_LINE_UP },
752         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
753         { 'w',          REQ_SCROLL_PAGE_UP },
754         { 's',          REQ_SCROLL_PAGE_DOWN },
756         /* Searching */
757         { '/',          REQ_SEARCH },
758         { '?',          REQ_SEARCH_BACK },
759         { 'n',          REQ_FIND_NEXT },
760         { 'N',          REQ_FIND_PREV },
762         /* Misc */
763         { 'Q',          REQ_QUIT },
764         { 'z',          REQ_STOP_LOADING },
765         { 'v',          REQ_SHOW_VERSION },
766         { 'r',          REQ_SCREEN_REDRAW },
767         { '.',          REQ_TOGGLE_LINENO },
768         { 'D',          REQ_TOGGLE_DATE },
769         { 'A',          REQ_TOGGLE_AUTHOR },
770         { 'g',          REQ_TOGGLE_REV_GRAPH },
771         { 'F',          REQ_TOGGLE_REFS },
772         { ':',          REQ_PROMPT },
773         { 'u',          REQ_STATUS_UPDATE },
774         { 'M',          REQ_STATUS_MERGE },
775         { ',',          REQ_TREE_PARENT },
776         { 'e',          REQ_EDIT },
778         /* Using the ncurses SIGWINCH handler. */
779         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
780 };
782 #define KEYMAP_INFO \
783         KEYMAP_(GENERIC), \
784         KEYMAP_(MAIN), \
785         KEYMAP_(DIFF), \
786         KEYMAP_(LOG), \
787         KEYMAP_(TREE), \
788         KEYMAP_(BLOB), \
789         KEYMAP_(BLAME), \
790         KEYMAP_(PAGER), \
791         KEYMAP_(HELP), \
792         KEYMAP_(STATUS), \
793         KEYMAP_(STAGE)
795 enum keymap {
796 #define KEYMAP_(name) KEYMAP_##name
797         KEYMAP_INFO
798 #undef  KEYMAP_
799 };
801 static struct int_map keymap_table[] = {
802 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
803         KEYMAP_INFO
804 #undef  KEYMAP_
805 };
807 #define set_keymap(map, name) \
808         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
810 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
812 static void
813 add_keybinding(enum keymap keymap, enum request request, int key)
815         struct keybinding *keybinding;
817         keybinding = calloc(1, sizeof(*keybinding));
818         if (!keybinding)
819                 die("Failed to allocate keybinding");
821         keybinding->alias = key;
822         keybinding->request = request;
823         keybinding->next = keybindings[keymap];
824         keybindings[keymap] = keybinding;
827 /* Looks for a key binding first in the given map, then in the generic map, and
828  * lastly in the default keybindings. */
829 static enum request
830 get_keybinding(enum keymap keymap, int key)
832         struct keybinding *kbd;
833         int i;
835         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
836                 if (kbd->alias == key)
837                         return kbd->request;
839         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
840                 if (kbd->alias == key)
841                         return kbd->request;
843         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
844                 if (default_keybindings[i].alias == key)
845                         return default_keybindings[i].request;
847         return (enum request) key;
851 struct key {
852         char *name;
853         int value;
854 };
856 static struct key key_table[] = {
857         { "Enter",      KEY_RETURN },
858         { "Space",      ' ' },
859         { "Backspace",  KEY_BACKSPACE },
860         { "Tab",        KEY_TAB },
861         { "Escape",     KEY_ESC },
862         { "Left",       KEY_LEFT },
863         { "Right",      KEY_RIGHT },
864         { "Up",         KEY_UP },
865         { "Down",       KEY_DOWN },
866         { "Insert",     KEY_IC },
867         { "Delete",     KEY_DC },
868         { "Hash",       '#' },
869         { "Home",       KEY_HOME },
870         { "End",        KEY_END },
871         { "PageUp",     KEY_PPAGE },
872         { "PageDown",   KEY_NPAGE },
873         { "F1",         KEY_F(1) },
874         { "F2",         KEY_F(2) },
875         { "F3",         KEY_F(3) },
876         { "F4",         KEY_F(4) },
877         { "F5",         KEY_F(5) },
878         { "F6",         KEY_F(6) },
879         { "F7",         KEY_F(7) },
880         { "F8",         KEY_F(8) },
881         { "F9",         KEY_F(9) },
882         { "F10",        KEY_F(10) },
883         { "F11",        KEY_F(11) },
884         { "F12",        KEY_F(12) },
885 };
887 static int
888 get_key_value(const char *name)
890         int i;
892         for (i = 0; i < ARRAY_SIZE(key_table); i++)
893                 if (!strcasecmp(key_table[i].name, name))
894                         return key_table[i].value;
896         if (strlen(name) == 1 && isprint(*name))
897                 return (int) *name;
899         return ERR;
902 static char *
903 get_key_name(int key_value)
905         static char key_char[] = "'X'";
906         char *seq = NULL;
907         int key;
909         for (key = 0; key < ARRAY_SIZE(key_table); key++)
910                 if (key_table[key].value == key_value)
911                         seq = key_table[key].name;
913         if (seq == NULL &&
914             key_value < 127 &&
915             isprint(key_value)) {
916                 key_char[1] = (char) key_value;
917                 seq = key_char;
918         }
920         return seq ? seq : "'?'";
923 static char *
924 get_key(enum request request)
926         static char buf[BUFSIZ];
927         size_t pos = 0;
928         char *sep = "";
929         int i;
931         buf[pos] = 0;
933         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
934                 struct keybinding *keybinding = &default_keybindings[i];
936                 if (keybinding->request != request)
937                         continue;
939                 if (!string_format_from(buf, &pos, "%s%s", sep,
940                                         get_key_name(keybinding->alias)))
941                         return "Too many keybindings!";
942                 sep = ", ";
943         }
945         return buf;
948 struct run_request {
949         enum keymap keymap;
950         int key;
951         char cmd[SIZEOF_STR];
952 };
954 static struct run_request *run_request;
955 static size_t run_requests;
957 static enum request
958 add_run_request(enum keymap keymap, int key, int argc, char **argv)
960         struct run_request *tmp;
961         struct run_request req = { keymap, key };
962         size_t bufpos;
964         for (bufpos = 0; argc > 0; argc--, argv++)
965                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
966                         return REQ_NONE;
968         req.cmd[bufpos - 1] = 0;
970         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
971         if (!tmp)
972                 return REQ_NONE;
974         run_request = tmp;
975         run_request[run_requests++] = req;
977         return REQ_NONE + run_requests;
980 static struct run_request *
981 get_run_request(enum request request)
983         if (request <= REQ_NONE)
984                 return NULL;
985         return &run_request[request - REQ_NONE - 1];
988 static void
989 add_builtin_run_requests(void)
991         struct {
992                 enum keymap keymap;
993                 int key;
994                 char *argv[1];
995         } reqs[] = {
996                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
997                 { KEYMAP_GENERIC, 'G', { "git gc" } },
998         };
999         int i;
1001         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1002                 enum request req;
1004                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1005                 if (req != REQ_NONE)
1006                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1007         }
1010 /*
1011  * User config file handling.
1012  */
1014 static struct int_map color_map[] = {
1015 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1016         COLOR_MAP(DEFAULT),
1017         COLOR_MAP(BLACK),
1018         COLOR_MAP(BLUE),
1019         COLOR_MAP(CYAN),
1020         COLOR_MAP(GREEN),
1021         COLOR_MAP(MAGENTA),
1022         COLOR_MAP(RED),
1023         COLOR_MAP(WHITE),
1024         COLOR_MAP(YELLOW),
1025 };
1027 #define set_color(color, name) \
1028         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1030 static struct int_map attr_map[] = {
1031 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1032         ATTR_MAP(NORMAL),
1033         ATTR_MAP(BLINK),
1034         ATTR_MAP(BOLD),
1035         ATTR_MAP(DIM),
1036         ATTR_MAP(REVERSE),
1037         ATTR_MAP(STANDOUT),
1038         ATTR_MAP(UNDERLINE),
1039 };
1041 #define set_attribute(attr, name) \
1042         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1044 static int   config_lineno;
1045 static bool  config_errors;
1046 static char *config_msg;
1048 /* Wants: object fgcolor bgcolor [attr] */
1049 static int
1050 option_color_command(int argc, char *argv[])
1052         struct line_info *info;
1054         if (argc != 3 && argc != 4) {
1055                 config_msg = "Wrong number of arguments given to color command";
1056                 return ERR;
1057         }
1059         info = get_line_info(argv[0]);
1060         if (!info) {
1061                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1062                         info = get_line_info("delimiter");
1064                 } else {
1065                         config_msg = "Unknown color name";
1066                         return ERR;
1067                 }
1068         }
1070         if (set_color(&info->fg, argv[1]) == ERR ||
1071             set_color(&info->bg, argv[2]) == ERR) {
1072                 config_msg = "Unknown color";
1073                 return ERR;
1074         }
1076         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1077                 config_msg = "Unknown attribute";
1078                 return ERR;
1079         }
1081         return OK;
1084 static bool parse_bool(const char *s)
1086         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1087                 !strcmp(s, "yes")) ? TRUE : FALSE;
1090 /* Wants: name = value */
1091 static int
1092 option_set_command(int argc, char *argv[])
1094         if (argc != 3) {
1095                 config_msg = "Wrong number of arguments given to set command";
1096                 return ERR;
1097         }
1099         if (strcmp(argv[1], "=")) {
1100                 config_msg = "No value assigned";
1101                 return ERR;
1102         }
1104         if (!strcmp(argv[0], "show-author")) {
1105                 opt_author = parse_bool(argv[2]);
1106                 return OK;
1107         }
1109         if (!strcmp(argv[0], "show-date")) {
1110                 opt_date = parse_bool(argv[2]);
1111                 return OK;
1112         }
1114         if (!strcmp(argv[0], "show-rev-graph")) {
1115                 opt_rev_graph = parse_bool(argv[2]);
1116                 return OK;
1117         }
1119         if (!strcmp(argv[0], "show-refs")) {
1120                 opt_show_refs = parse_bool(argv[2]);
1121                 return OK;
1122         }
1124         if (!strcmp(argv[0], "show-line-numbers")) {
1125                 opt_line_number = parse_bool(argv[2]);
1126                 return OK;
1127         }
1129         if (!strcmp(argv[0], "line-number-interval")) {
1130                 opt_num_interval = atoi(argv[2]);
1131                 return OK;
1132         }
1134         if (!strcmp(argv[0], "tab-size")) {
1135                 opt_tab_size = atoi(argv[2]);
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "commit-encoding")) {
1140                 char *arg = argv[2];
1141                 int delimiter = *arg;
1142                 int i;
1144                 switch (delimiter) {
1145                 case '"':
1146                 case '\'':
1147                         for (arg++, i = 0; arg[i]; i++)
1148                                 if (arg[i] == delimiter) {
1149                                         arg[i] = 0;
1150                                         break;
1151                                 }
1152                 default:
1153                         string_ncopy(opt_encoding, arg, strlen(arg));
1154                         return OK;
1155                 }
1156         }
1158         config_msg = "Unknown variable name";
1159         return ERR;
1162 /* Wants: mode request key */
1163 static int
1164 option_bind_command(int argc, char *argv[])
1166         enum request request;
1167         int keymap;
1168         int key;
1170         if (argc < 3) {
1171                 config_msg = "Wrong number of arguments given to bind command";
1172                 return ERR;
1173         }
1175         if (set_keymap(&keymap, argv[0]) == ERR) {
1176                 config_msg = "Unknown key map";
1177                 return ERR;
1178         }
1180         key = get_key_value(argv[1]);
1181         if (key == ERR) {
1182                 config_msg = "Unknown key";
1183                 return ERR;
1184         }
1186         request = get_request(argv[2]);
1187         if (request == REQ_NONE) {
1188                 const char *obsolete[] = { "cherry-pick" };
1189                 size_t namelen = strlen(argv[2]);
1190                 int i;
1192                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1193                         if (namelen == strlen(obsolete[i]) &&
1194                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1195                                 config_msg = "Obsolete request name";
1196                                 return ERR;
1197                         }
1198                 }
1199         }
1200         if (request == REQ_NONE && *argv[2]++ == '!')
1201                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1202         if (request == REQ_NONE) {
1203                 config_msg = "Unknown request name";
1204                 return ERR;
1205         }
1207         add_keybinding(keymap, request, key);
1209         return OK;
1212 static int
1213 set_option(char *opt, char *value)
1215         char *argv[16];
1216         int valuelen;
1217         int argc = 0;
1219         /* Tokenize */
1220         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1221                 argv[argc++] = value;
1222                 value += valuelen;
1224                 /* Nothing more to tokenize or last available token. */
1225                 if (!*value || argc >= ARRAY_SIZE(argv))
1226                         break;
1228                 *value++ = 0;
1229                 while (isspace(*value))
1230                         value++;
1231         }
1233         if (!strcmp(opt, "color"))
1234                 return option_color_command(argc, argv);
1236         if (!strcmp(opt, "set"))
1237                 return option_set_command(argc, argv);
1239         if (!strcmp(opt, "bind"))
1240                 return option_bind_command(argc, argv);
1242         config_msg = "Unknown option command";
1243         return ERR;
1246 static int
1247 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1249         int status = OK;
1251         config_lineno++;
1252         config_msg = "Internal error";
1254         /* Check for comment markers, since read_properties() will
1255          * only ensure opt and value are split at first " \t". */
1256         optlen = strcspn(opt, "#");
1257         if (optlen == 0)
1258                 return OK;
1260         if (opt[optlen] != 0) {
1261                 config_msg = "No option value";
1262                 status = ERR;
1264         }  else {
1265                 /* Look for comment endings in the value. */
1266                 size_t len = strcspn(value, "#");
1268                 if (len < valuelen) {
1269                         valuelen = len;
1270                         value[valuelen] = 0;
1271                 }
1273                 status = set_option(opt, value);
1274         }
1276         if (status == ERR) {
1277                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1278                         config_lineno, (int) optlen, opt, config_msg);
1279                 config_errors = TRUE;
1280         }
1282         /* Always keep going if errors are encountered. */
1283         return OK;
1286 static void
1287 load_option_file(const char *path)
1289         FILE *file;
1291         /* It's ok that the file doesn't exist. */
1292         file = fopen(path, "r");
1293         if (!file)
1294                 return;
1296         config_lineno = 0;
1297         config_errors = FALSE;
1299         if (read_properties(file, " \t", read_option) == ERR ||
1300             config_errors == TRUE)
1301                 fprintf(stderr, "Errors while loading %s.\n", path);
1304 static int
1305 load_options(void)
1307         char *home = getenv("HOME");
1308         char *tigrc_user = getenv("TIGRC_USER");
1309         char *tigrc_system = getenv("TIGRC_SYSTEM");
1310         char buf[SIZEOF_STR];
1312         add_builtin_run_requests();
1314         if (!tigrc_system) {
1315                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1316                         return ERR;
1317                 tigrc_system = buf;
1318         }
1319         load_option_file(tigrc_system);
1321         if (!tigrc_user) {
1322                 if (!home || !string_format(buf, "%s/.tigrc", home))
1323                         return ERR;
1324                 tigrc_user = buf;
1325         }
1326         load_option_file(tigrc_user);
1328         return OK;
1332 /*
1333  * The viewer
1334  */
1336 struct view;
1337 struct view_ops;
1339 /* The display array of active views and the index of the current view. */
1340 static struct view *display[2];
1341 static unsigned int current_view;
1343 /* Reading from the prompt? */
1344 static bool input_mode = FALSE;
1346 #define foreach_displayed_view(view, i) \
1347         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1349 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1351 /* Current head and commit ID */
1352 static char ref_blob[SIZEOF_REF]        = "";
1353 static char ref_commit[SIZEOF_REF]      = "HEAD";
1354 static char ref_head[SIZEOF_REF]        = "HEAD";
1356 struct view {
1357         const char *name;       /* View name */
1358         const char *cmd_fmt;    /* Default command line format */
1359         const char *cmd_env;    /* Command line set via environment */
1360         const char *id;         /* Points to either of ref_{head,commit,blob} */
1362         struct view_ops *ops;   /* View operations */
1364         enum keymap keymap;     /* What keymap does this view have */
1365         bool git_dir;           /* Whether the view requires a git directory. */
1367         char cmd[SIZEOF_STR];   /* Command buffer */
1368         char ref[SIZEOF_REF];   /* Hovered commit reference */
1369         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1371         int height, width;      /* The width and height of the main window */
1372         WINDOW *win;            /* The main window */
1373         WINDOW *title;          /* The title window living below the main window */
1375         /* Navigation */
1376         unsigned long offset;   /* Offset of the window top */
1377         unsigned long lineno;   /* Current line number */
1379         /* Searching */
1380         char grep[SIZEOF_STR];  /* Search string */
1381         regex_t *regex;         /* Pre-compiled regex */
1383         /* If non-NULL, points to the view that opened this view. If this view
1384          * is closed tig will switch back to the parent view. */
1385         struct view *parent;
1387         /* Buffering */
1388         size_t lines;           /* Total number of lines */
1389         struct line *line;      /* Line index */
1390         size_t line_alloc;      /* Total number of allocated lines */
1391         size_t line_size;       /* Total number of used lines */
1392         unsigned int digits;    /* Number of digits in the lines member. */
1394         /* Loading */
1395         FILE *pipe;
1396         time_t start_time;
1397 };
1399 struct view_ops {
1400         /* What type of content being displayed. Used in the title bar. */
1401         const char *type;
1402         /* Open and reads in all view content. */
1403         bool (*open)(struct view *view);
1404         /* Read one line; updates view->line. */
1405         bool (*read)(struct view *view, char *data);
1406         /* Draw one line; @lineno must be < view->height. */
1407         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1408         /* Depending on view handle a special requests. */
1409         enum request (*request)(struct view *view, enum request request, struct line *line);
1410         /* Search for regex in a line. */
1411         bool (*grep)(struct view *view, struct line *line);
1412         /* Select line */
1413         void (*select)(struct view *view, struct line *line);
1414 };
1416 static struct view_ops pager_ops;
1417 static struct view_ops main_ops;
1418 static struct view_ops tree_ops;
1419 static struct view_ops blob_ops;
1420 static struct view_ops blame_ops;
1421 static struct view_ops help_ops;
1422 static struct view_ops status_ops;
1423 static struct view_ops stage_ops;
1425 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1426         { name, cmd, #env, ref, ops, map, git }
1428 #define VIEW_(id, name, ops, git, ref) \
1429         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1432 static struct view views[] = {
1433         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1434         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1435         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1436         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1437         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1438         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1439         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1440         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1441         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1442         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1443 };
1445 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1446 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1448 #define foreach_view(view, i) \
1449         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1451 #define view_is_displayed(view) \
1452         (view == display[0] || view == display[1])
1454 static int
1455 draw_text(struct view *view, const char *string, int max_len,
1456           bool use_tilde, bool selected)
1458         int len = 0;
1459         int trimmed = FALSE;
1461         if (max_len <= 0)
1462                 return 0;
1464         if (opt_utf8) {
1465                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1466         } else {
1467                 len = strlen(string);
1468                 if (len > max_len) {
1469                         if (use_tilde) {
1470                                 max_len -= 1;
1471                         }
1472                         len = max_len;
1473                         trimmed = TRUE;
1474                 }
1475         }
1477         waddnstr(view->win, string, len);
1478         if (trimmed && use_tilde) {
1479                 if (!selected)
1480                         wattrset(view->win, get_line_attr(LINE_DELIMITER));
1481                 waddch(view->win, '~');
1482                 len++;
1483         }
1485         return len;
1488 static int
1489 draw_lineno(struct view *view, unsigned int lineno, int max, bool selected)
1491         static char fmt[] = "%1ld";
1492         char number[10] = "          ";
1493         int max_number = MIN(view->digits, STRING_SIZE(number));
1494         bool showtrimmed = FALSE;
1495         int col;
1497         lineno += view->offset + 1;
1498         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1499                 if (view->digits <= 9)
1500                         fmt[1] = '0' + view->digits;
1502                 if (!string_format(number, fmt, lineno))
1503                         number[0] = 0;
1504                 showtrimmed = TRUE;
1505         }
1507         if (max < max_number)
1508                 max_number = max;
1510         if (!selected)
1511                 wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
1512         col = draw_text(view, number, max_number, showtrimmed, selected);
1513         if (col < max) {
1514                 if (!selected)
1515                         wattrset(view->win, A_NORMAL);
1516                 waddch(view->win, ACS_VLINE);
1517                 col++;
1518         }
1519         if (col < max) {
1520                 waddch(view->win, ' ');
1521                 col++;
1522         }
1524         return col;
1527 static bool
1528 draw_view_line(struct view *view, unsigned int lineno)
1530         struct line *line;
1531         bool selected = (view->offset + lineno == view->lineno);
1532         bool draw_ok;
1534         assert(view_is_displayed(view));
1536         if (view->offset + lineno >= view->lines)
1537                 return FALSE;
1539         line = &view->line[view->offset + lineno];
1541         if (selected) {
1542                 line->selected = TRUE;
1543                 view->ops->select(view, line);
1544         } else if (line->selected) {
1545                 line->selected = FALSE;
1546                 wmove(view->win, lineno, 0);
1547                 wclrtoeol(view->win);
1548         }
1550         scrollok(view->win, FALSE);
1551         draw_ok = view->ops->draw(view, line, lineno, selected);
1552         scrollok(view->win, TRUE);
1554         return draw_ok;
1557 static void
1558 redraw_view_dirty(struct view *view)
1560         bool dirty = FALSE;
1561         int lineno;
1563         for (lineno = 0; lineno < view->height; lineno++) {
1564                 struct line *line = &view->line[view->offset + lineno];
1566                 if (!line->dirty)
1567                         continue;
1568                 line->dirty = 0;
1569                 dirty = TRUE;
1570                 if (!draw_view_line(view, lineno))
1571                         break;
1572         }
1574         if (!dirty)
1575                 return;
1576         redrawwin(view->win);
1577         if (input_mode)
1578                 wnoutrefresh(view->win);
1579         else
1580                 wrefresh(view->win);
1583 static void
1584 redraw_view_from(struct view *view, int lineno)
1586         assert(0 <= lineno && lineno < view->height);
1588         for (; lineno < view->height; lineno++) {
1589                 if (!draw_view_line(view, lineno))
1590                         break;
1591         }
1593         redrawwin(view->win);
1594         if (input_mode)
1595                 wnoutrefresh(view->win);
1596         else
1597                 wrefresh(view->win);
1600 static void
1601 redraw_view(struct view *view)
1603         wclear(view->win);
1604         redraw_view_from(view, 0);
1608 static void
1609 update_view_title(struct view *view)
1611         char buf[SIZEOF_STR];
1612         char state[SIZEOF_STR];
1613         size_t bufpos = 0, statelen = 0;
1615         assert(view_is_displayed(view));
1617         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1618                 unsigned int view_lines = view->offset + view->height;
1619                 unsigned int lines = view->lines
1620                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1621                                    : 0;
1623                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1624                                    view->ops->type,
1625                                    view->lineno + 1,
1626                                    view->lines,
1627                                    lines);
1629                 if (view->pipe) {
1630                         time_t secs = time(NULL) - view->start_time;
1632                         /* Three git seconds are a long time ... */
1633                         if (secs > 2)
1634                                 string_format_from(state, &statelen, " %lds", secs);
1635                 }
1636         }
1638         string_format_from(buf, &bufpos, "[%s]", view->name);
1639         if (*view->ref && bufpos < view->width) {
1640                 size_t refsize = strlen(view->ref);
1641                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1643                 if (minsize < view->width)
1644                         refsize = view->width - minsize + 7;
1645                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1646         }
1648         if (statelen && bufpos < view->width) {
1649                 string_format_from(buf, &bufpos, " %s", state);
1650         }
1652         if (view == display[current_view])
1653                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1654         else
1655                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1657         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1658         wclrtoeol(view->title);
1659         wmove(view->title, 0, view->width - 1);
1661         if (input_mode)
1662                 wnoutrefresh(view->title);
1663         else
1664                 wrefresh(view->title);
1667 static void
1668 resize_display(void)
1670         int offset, i;
1671         struct view *base = display[0];
1672         struct view *view = display[1] ? display[1] : display[0];
1674         /* Setup window dimensions */
1676         getmaxyx(stdscr, base->height, base->width);
1678         /* Make room for the status window. */
1679         base->height -= 1;
1681         if (view != base) {
1682                 /* Horizontal split. */
1683                 view->width   = base->width;
1684                 view->height  = SCALE_SPLIT_VIEW(base->height);
1685                 base->height -= view->height;
1687                 /* Make room for the title bar. */
1688                 view->height -= 1;
1689         }
1691         /* Make room for the title bar. */
1692         base->height -= 1;
1694         offset = 0;
1696         foreach_displayed_view (view, i) {
1697                 if (!view->win) {
1698                         view->win = newwin(view->height, 0, offset, 0);
1699                         if (!view->win)
1700                                 die("Failed to create %s view", view->name);
1702                         scrollok(view->win, TRUE);
1704                         view->title = newwin(1, 0, offset + view->height, 0);
1705                         if (!view->title)
1706                                 die("Failed to create title window");
1708                 } else {
1709                         wresize(view->win, view->height, view->width);
1710                         mvwin(view->win,   offset, 0);
1711                         mvwin(view->title, offset + view->height, 0);
1712                 }
1714                 offset += view->height + 1;
1715         }
1718 static void
1719 redraw_display(void)
1721         struct view *view;
1722         int i;
1724         foreach_displayed_view (view, i) {
1725                 redraw_view(view);
1726                 update_view_title(view);
1727         }
1730 static void
1731 update_display_cursor(struct view *view)
1733         /* Move the cursor to the right-most column of the cursor line.
1734          *
1735          * XXX: This could turn out to be a bit expensive, but it ensures that
1736          * the cursor does not jump around. */
1737         if (view->lines) {
1738                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1739                 wrefresh(view->win);
1740         }
1743 /*
1744  * Navigation
1745  */
1747 /* Scrolling backend */
1748 static void
1749 do_scroll_view(struct view *view, int lines)
1751         bool redraw_current_line = FALSE;
1753         /* The rendering expects the new offset. */
1754         view->offset += lines;
1756         assert(0 <= view->offset && view->offset < view->lines);
1757         assert(lines);
1759         /* Move current line into the view. */
1760         if (view->lineno < view->offset) {
1761                 view->lineno = view->offset;
1762                 redraw_current_line = TRUE;
1763         } else if (view->lineno >= view->offset + view->height) {
1764                 view->lineno = view->offset + view->height - 1;
1765                 redraw_current_line = TRUE;
1766         }
1768         assert(view->offset <= view->lineno && view->lineno < view->lines);
1770         /* Redraw the whole screen if scrolling is pointless. */
1771         if (view->height < ABS(lines)) {
1772                 redraw_view(view);
1774         } else {
1775                 int line = lines > 0 ? view->height - lines : 0;
1776                 int end = line + ABS(lines);
1778                 wscrl(view->win, lines);
1780                 for (; line < end; line++) {
1781                         if (!draw_view_line(view, line))
1782                                 break;
1783                 }
1785                 if (redraw_current_line)
1786                         draw_view_line(view, view->lineno - view->offset);
1787         }
1789         redrawwin(view->win);
1790         wrefresh(view->win);
1791         report("");
1794 /* Scroll frontend */
1795 static void
1796 scroll_view(struct view *view, enum request request)
1798         int lines = 1;
1800         assert(view_is_displayed(view));
1802         switch (request) {
1803         case REQ_SCROLL_PAGE_DOWN:
1804                 lines = view->height;
1805         case REQ_SCROLL_LINE_DOWN:
1806                 if (view->offset + lines > view->lines)
1807                         lines = view->lines - view->offset;
1809                 if (lines == 0 || view->offset + view->height >= view->lines) {
1810                         report("Cannot scroll beyond the last line");
1811                         return;
1812                 }
1813                 break;
1815         case REQ_SCROLL_PAGE_UP:
1816                 lines = view->height;
1817         case REQ_SCROLL_LINE_UP:
1818                 if (lines > view->offset)
1819                         lines = view->offset;
1821                 if (lines == 0) {
1822                         report("Cannot scroll beyond the first line");
1823                         return;
1824                 }
1826                 lines = -lines;
1827                 break;
1829         default:
1830                 die("request %d not handled in switch", request);
1831         }
1833         do_scroll_view(view, lines);
1836 /* Cursor moving */
1837 static void
1838 move_view(struct view *view, enum request request)
1840         int scroll_steps = 0;
1841         int steps;
1843         switch (request) {
1844         case REQ_MOVE_FIRST_LINE:
1845                 steps = -view->lineno;
1846                 break;
1848         case REQ_MOVE_LAST_LINE:
1849                 steps = view->lines - view->lineno - 1;
1850                 break;
1852         case REQ_MOVE_PAGE_UP:
1853                 steps = view->height > view->lineno
1854                       ? -view->lineno : -view->height;
1855                 break;
1857         case REQ_MOVE_PAGE_DOWN:
1858                 steps = view->lineno + view->height >= view->lines
1859                       ? view->lines - view->lineno - 1 : view->height;
1860                 break;
1862         case REQ_MOVE_UP:
1863                 steps = -1;
1864                 break;
1866         case REQ_MOVE_DOWN:
1867                 steps = 1;
1868                 break;
1870         default:
1871                 die("request %d not handled in switch", request);
1872         }
1874         if (steps <= 0 && view->lineno == 0) {
1875                 report("Cannot move beyond the first line");
1876                 return;
1878         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1879                 report("Cannot move beyond the last line");
1880                 return;
1881         }
1883         /* Move the current line */
1884         view->lineno += steps;
1885         assert(0 <= view->lineno && view->lineno < view->lines);
1887         /* Check whether the view needs to be scrolled */
1888         if (view->lineno < view->offset ||
1889             view->lineno >= view->offset + view->height) {
1890                 scroll_steps = steps;
1891                 if (steps < 0 && -steps > view->offset) {
1892                         scroll_steps = -view->offset;
1894                 } else if (steps > 0) {
1895                         if (view->lineno == view->lines - 1 &&
1896                             view->lines > view->height) {
1897                                 scroll_steps = view->lines - view->offset - 1;
1898                                 if (scroll_steps >= view->height)
1899                                         scroll_steps -= view->height - 1;
1900                         }
1901                 }
1902         }
1904         if (!view_is_displayed(view)) {
1905                 view->offset += scroll_steps;
1906                 assert(0 <= view->offset && view->offset < view->lines);
1907                 view->ops->select(view, &view->line[view->lineno]);
1908                 return;
1909         }
1911         /* Repaint the old "current" line if we be scrolling */
1912         if (ABS(steps) < view->height)
1913                 draw_view_line(view, view->lineno - steps - view->offset);
1915         if (scroll_steps) {
1916                 do_scroll_view(view, scroll_steps);
1917                 return;
1918         }
1920         /* Draw the current line */
1921         draw_view_line(view, view->lineno - view->offset);
1923         redrawwin(view->win);
1924         wrefresh(view->win);
1925         report("");
1929 /*
1930  * Searching
1931  */
1933 static void search_view(struct view *view, enum request request);
1935 static bool
1936 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1938         assert(view_is_displayed(view));
1940         if (!view->ops->grep(view, line))
1941                 return FALSE;
1943         if (lineno - view->offset >= view->height) {
1944                 view->offset = lineno;
1945                 view->lineno = lineno;
1946                 redraw_view(view);
1948         } else {
1949                 unsigned long old_lineno = view->lineno - view->offset;
1951                 view->lineno = lineno;
1952                 draw_view_line(view, old_lineno);
1954                 draw_view_line(view, view->lineno - view->offset);
1955                 redrawwin(view->win);
1956                 wrefresh(view->win);
1957         }
1959         report("Line %ld matches '%s'", lineno + 1, view->grep);
1960         return TRUE;
1963 static void
1964 find_next(struct view *view, enum request request)
1966         unsigned long lineno = view->lineno;
1967         int direction;
1969         if (!*view->grep) {
1970                 if (!*opt_search)
1971                         report("No previous search");
1972                 else
1973                         search_view(view, request);
1974                 return;
1975         }
1977         switch (request) {
1978         case REQ_SEARCH:
1979         case REQ_FIND_NEXT:
1980                 direction = 1;
1981                 break;
1983         case REQ_SEARCH_BACK:
1984         case REQ_FIND_PREV:
1985                 direction = -1;
1986                 break;
1988         default:
1989                 return;
1990         }
1992         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1993                 lineno += direction;
1995         /* Note, lineno is unsigned long so will wrap around in which case it
1996          * will become bigger than view->lines. */
1997         for (; lineno < view->lines; lineno += direction) {
1998                 struct line *line = &view->line[lineno];
2000                 if (find_next_line(view, lineno, line))
2001                         return;
2002         }
2004         report("No match found for '%s'", view->grep);
2007 static void
2008 search_view(struct view *view, enum request request)
2010         int regex_err;
2012         if (view->regex) {
2013                 regfree(view->regex);
2014                 *view->grep = 0;
2015         } else {
2016                 view->regex = calloc(1, sizeof(*view->regex));
2017                 if (!view->regex)
2018                         return;
2019         }
2021         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2022         if (regex_err != 0) {
2023                 char buf[SIZEOF_STR] = "unknown error";
2025                 regerror(regex_err, view->regex, buf, sizeof(buf));
2026                 report("Search failed: %s", buf);
2027                 return;
2028         }
2030         string_copy(view->grep, opt_search);
2032         find_next(view, request);
2035 /*
2036  * Incremental updating
2037  */
2039 static void
2040 end_update(struct view *view)
2042         if (!view->pipe)
2043                 return;
2044         set_nonblocking_input(FALSE);
2045         if (view->pipe == stdin)
2046                 fclose(view->pipe);
2047         else
2048                 pclose(view->pipe);
2049         view->pipe = NULL;
2052 static bool
2053 begin_update(struct view *view)
2055         if (view->pipe)
2056                 end_update(view);
2058         if (opt_cmd[0]) {
2059                 string_copy(view->cmd, opt_cmd);
2060                 opt_cmd[0] = 0;
2061                 /* When running random commands, initially show the
2062                  * command in the title. However, it maybe later be
2063                  * overwritten if a commit line is selected. */
2064                 if (view == VIEW(REQ_VIEW_PAGER))
2065                         string_copy(view->ref, view->cmd);
2066                 else
2067                         view->ref[0] = 0;
2069         } else if (view == VIEW(REQ_VIEW_TREE)) {
2070                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2071                 char path[SIZEOF_STR];
2073                 if (strcmp(view->vid, view->id))
2074                         opt_path[0] = path[0] = 0;
2075                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2076                         return FALSE;
2078                 if (!string_format(view->cmd, format, view->id, path))
2079                         return FALSE;
2081         } else {
2082                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2083                 const char *id = view->id;
2085                 if (!string_format(view->cmd, format, id, id, id, id, id))
2086                         return FALSE;
2088                 /* Put the current ref_* value to the view title ref
2089                  * member. This is needed by the blob view. Most other
2090                  * views sets it automatically after loading because the
2091                  * first line is a commit line. */
2092                 string_copy_rev(view->ref, view->id);
2093         }
2095         /* Special case for the pager view. */
2096         if (opt_pipe) {
2097                 view->pipe = opt_pipe;
2098                 opt_pipe = NULL;
2099         } else {
2100                 view->pipe = popen(view->cmd, "r");
2101         }
2103         if (!view->pipe)
2104                 return FALSE;
2106         set_nonblocking_input(TRUE);
2108         view->offset = 0;
2109         view->lines  = 0;
2110         view->lineno = 0;
2111         string_copy_rev(view->vid, view->id);
2113         if (view->line) {
2114                 int i;
2116                 for (i = 0; i < view->lines; i++)
2117                         if (view->line[i].data)
2118                                 free(view->line[i].data);
2120                 free(view->line);
2121                 view->line = NULL;
2122         }
2124         view->start_time = time(NULL);
2126         return TRUE;
2129 #define ITEM_CHUNK_SIZE 256
2130 static void *
2131 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2133         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2134         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2136         if (mem == NULL || num_chunks != num_chunks_new) {
2137                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2138                 mem = realloc(mem, *size * item_size);
2139         }
2141         return mem;
2144 static struct line *
2145 realloc_lines(struct view *view, size_t line_size)
2147         size_t alloc = view->line_alloc;
2148         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2149                                          sizeof(*view->line));
2151         if (!tmp)
2152                 return NULL;
2154         view->line = tmp;
2155         view->line_alloc = alloc;
2156         view->line_size = line_size;
2157         return view->line;
2160 static bool
2161 update_view(struct view *view)
2163         char in_buffer[BUFSIZ];
2164         char out_buffer[BUFSIZ * 2];
2165         char *line;
2166         /* The number of lines to read. If too low it will cause too much
2167          * redrawing (and possible flickering), if too high responsiveness
2168          * will suffer. */
2169         unsigned long lines = view->height;
2170         int redraw_from = -1;
2172         if (!view->pipe)
2173                 return TRUE;
2175         /* Only redraw if lines are visible. */
2176         if (view->offset + view->height >= view->lines)
2177                 redraw_from = view->lines - view->offset;
2179         /* FIXME: This is probably not perfect for backgrounded views. */
2180         if (!realloc_lines(view, view->lines + lines))
2181                 goto alloc_error;
2183         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2184                 size_t linelen = strlen(line);
2186                 if (linelen)
2187                         line[linelen - 1] = 0;
2189                 if (opt_iconv != ICONV_NONE) {
2190                         ICONV_CONST char *inbuf = line;
2191                         size_t inlen = linelen;
2193                         char *outbuf = out_buffer;
2194                         size_t outlen = sizeof(out_buffer);
2196                         size_t ret;
2198                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2199                         if (ret != (size_t) -1) {
2200                                 line = out_buffer;
2201                                 linelen = strlen(out_buffer);
2202                         }
2203                 }
2205                 if (!view->ops->read(view, line))
2206                         goto alloc_error;
2208                 if (lines-- == 1)
2209                         break;
2210         }
2212         {
2213                 int digits;
2215                 lines = view->lines;
2216                 for (digits = 0; lines; digits++)
2217                         lines /= 10;
2219                 /* Keep the displayed view in sync with line number scaling. */
2220                 if (digits != view->digits) {
2221                         view->digits = digits;
2222                         redraw_from = 0;
2223                 }
2224         }
2226         if (!view_is_displayed(view))
2227                 goto check_pipe;
2229         if (view == VIEW(REQ_VIEW_TREE)) {
2230                 /* Clear the view and redraw everything since the tree sorting
2231                  * might have rearranged things. */
2232                 redraw_view(view);
2234         } else if (redraw_from >= 0) {
2235                 /* If this is an incremental update, redraw the previous line
2236                  * since for commits some members could have changed when
2237                  * loading the main view. */
2238                 if (redraw_from > 0)
2239                         redraw_from--;
2241                 /* Since revision graph visualization requires knowledge
2242                  * about the parent commit, it causes a further one-off
2243                  * needed to be redrawn for incremental updates. */
2244                 if (redraw_from > 0 && opt_rev_graph)
2245                         redraw_from--;
2247                 /* Incrementally draw avoids flickering. */
2248                 redraw_view_from(view, redraw_from);
2249         }
2251         if (view == VIEW(REQ_VIEW_BLAME))
2252                 redraw_view_dirty(view);
2254         /* Update the title _after_ the redraw so that if the redraw picks up a
2255          * commit reference in view->ref it'll be available here. */
2256         update_view_title(view);
2258 check_pipe:
2259         if (ferror(view->pipe)) {
2260                 report("Failed to read: %s", strerror(errno));
2261                 goto end;
2263         } else if (feof(view->pipe)) {
2264                 report("");
2265                 goto end;
2266         }
2268         return TRUE;
2270 alloc_error:
2271         report("Allocation failure");
2273 end:
2274         if (view->ops->read(view, NULL))
2275                 end_update(view);
2276         return FALSE;
2279 static struct line *
2280 add_line_data(struct view *view, void *data, enum line_type type)
2282         struct line *line = &view->line[view->lines++];
2284         memset(line, 0, sizeof(*line));
2285         line->type = type;
2286         line->data = data;
2288         return line;
2291 static struct line *
2292 add_line_text(struct view *view, char *data, enum line_type type)
2294         if (data)
2295                 data = strdup(data);
2297         return data ? add_line_data(view, data, type) : NULL;
2301 /*
2302  * View opening
2303  */
2305 enum open_flags {
2306         OPEN_DEFAULT = 0,       /* Use default view switching. */
2307         OPEN_SPLIT = 1,         /* Split current view. */
2308         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2309         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2310 };
2312 static void
2313 open_view(struct view *prev, enum request request, enum open_flags flags)
2315         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2316         bool split = !!(flags & OPEN_SPLIT);
2317         bool reload = !!(flags & OPEN_RELOAD);
2318         struct view *view = VIEW(request);
2319         int nviews = displayed_views();
2320         struct view *base_view = display[0];
2322         if (view == prev && nviews == 1 && !reload) {
2323                 report("Already in %s view", view->name);
2324                 return;
2325         }
2327         if (view->git_dir && !opt_git_dir[0]) {
2328                 report("The %s view is disabled in pager view", view->name);
2329                 return;
2330         }
2332         if (split) {
2333                 display[1] = view;
2334                 if (!backgrounded)
2335                         current_view = 1;
2336         } else {
2337                 /* Maximize the current view. */
2338                 memset(display, 0, sizeof(display));
2339                 current_view = 0;
2340                 display[current_view] = view;
2341         }
2343         /* Resize the view when switching between split- and full-screen,
2344          * or when switching between two different full-screen views. */
2345         if (nviews != displayed_views() ||
2346             (nviews == 1 && base_view != display[0]))
2347                 resize_display();
2349         if (view->ops->open) {
2350                 if (!view->ops->open(view)) {
2351                         report("Failed to load %s view", view->name);
2352                         return;
2353                 }
2355         } else if ((reload || strcmp(view->vid, view->id)) &&
2356                    !begin_update(view)) {
2357                 report("Failed to load %s view", view->name);
2358                 return;
2359         }
2361         if (split && prev->lineno - prev->offset >= prev->height) {
2362                 /* Take the title line into account. */
2363                 int lines = prev->lineno - prev->offset - prev->height + 1;
2365                 /* Scroll the view that was split if the current line is
2366                  * outside the new limited view. */
2367                 do_scroll_view(prev, lines);
2368         }
2370         if (prev && view != prev) {
2371                 if (split && !backgrounded) {
2372                         /* "Blur" the previous view. */
2373                         update_view_title(prev);
2374                 }
2376                 view->parent = prev;
2377         }
2379         if (view->pipe && view->lines == 0) {
2380                 /* Clear the old view and let the incremental updating refill
2381                  * the screen. */
2382                 wclear(view->win);
2383                 report("");
2384         } else {
2385                 redraw_view(view);
2386                 report("");
2387         }
2389         /* If the view is backgrounded the above calls to report()
2390          * won't redraw the view title. */
2391         if (backgrounded)
2392                 update_view_title(view);
2395 static void
2396 open_external_viewer(const char *cmd)
2398         def_prog_mode();           /* save current tty modes */
2399         endwin();                  /* restore original tty modes */
2400         system(cmd);
2401         fprintf(stderr, "Press Enter to continue");
2402         getc(stdin);
2403         reset_prog_mode();
2404         redraw_display();
2407 static void
2408 open_mergetool(const char *file)
2410         char cmd[SIZEOF_STR];
2411         char file_sq[SIZEOF_STR];
2413         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2414             string_format(cmd, "git mergetool %s", file_sq)) {
2415                 open_external_viewer(cmd);
2416         }
2419 static void
2420 open_editor(bool from_root, const char *file)
2422         char cmd[SIZEOF_STR];
2423         char file_sq[SIZEOF_STR];
2424         char *editor;
2425         char *prefix = from_root ? opt_cdup : "";
2427         editor = getenv("GIT_EDITOR");
2428         if (!editor && *opt_editor)
2429                 editor = opt_editor;
2430         if (!editor)
2431                 editor = getenv("VISUAL");
2432         if (!editor)
2433                 editor = getenv("EDITOR");
2434         if (!editor)
2435                 editor = "vi";
2437         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2438             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2439                 open_external_viewer(cmd);
2440         }
2443 static void
2444 open_run_request(enum request request)
2446         struct run_request *req = get_run_request(request);
2447         char buf[SIZEOF_STR * 2];
2448         size_t bufpos;
2449         char *cmd;
2451         if (!req) {
2452                 report("Unknown run request");
2453                 return;
2454         }
2456         bufpos = 0;
2457         cmd = req->cmd;
2459         while (cmd) {
2460                 char *next = strstr(cmd, "%(");
2461                 int len = next - cmd;
2462                 char *value;
2464                 if (!next) {
2465                         len = strlen(cmd);
2466                         value = "";
2468                 } else if (!strncmp(next, "%(head)", 7)) {
2469                         value = ref_head;
2471                 } else if (!strncmp(next, "%(commit)", 9)) {
2472                         value = ref_commit;
2474                 } else if (!strncmp(next, "%(blob)", 7)) {
2475                         value = ref_blob;
2477                 } else {
2478                         report("Unknown replacement in run request: `%s`", req->cmd);
2479                         return;
2480                 }
2482                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2483                         return;
2485                 if (next)
2486                         next = strchr(next, ')') + 1;
2487                 cmd = next;
2488         }
2490         open_external_viewer(buf);
2493 /*
2494  * User request switch noodle
2495  */
2497 static int
2498 view_driver(struct view *view, enum request request)
2500         int i;
2502         if (request == REQ_NONE) {
2503                 doupdate();
2504                 return TRUE;
2505         }
2507         if (request > REQ_NONE) {
2508                 open_run_request(request);
2509                 return TRUE;
2510         }
2512         if (view && view->lines) {
2513                 request = view->ops->request(view, request, &view->line[view->lineno]);
2514                 if (request == REQ_NONE)
2515                         return TRUE;
2516         }
2518         switch (request) {
2519         case REQ_MOVE_UP:
2520         case REQ_MOVE_DOWN:
2521         case REQ_MOVE_PAGE_UP:
2522         case REQ_MOVE_PAGE_DOWN:
2523         case REQ_MOVE_FIRST_LINE:
2524         case REQ_MOVE_LAST_LINE:
2525                 move_view(view, request);
2526                 break;
2528         case REQ_SCROLL_LINE_DOWN:
2529         case REQ_SCROLL_LINE_UP:
2530         case REQ_SCROLL_PAGE_DOWN:
2531         case REQ_SCROLL_PAGE_UP:
2532                 scroll_view(view, request);
2533                 break;
2535         case REQ_VIEW_BLAME:
2536                 if (!opt_file[0]) {
2537                         report("No file chosen, press %s to open tree view",
2538                                get_key(REQ_VIEW_TREE));
2539                         break;
2540                 }
2541                 open_view(view, request, OPEN_DEFAULT);
2542                 break;
2544         case REQ_VIEW_BLOB:
2545                 if (!ref_blob[0]) {
2546                         report("No file chosen, press %s to open tree view",
2547                                get_key(REQ_VIEW_TREE));
2548                         break;
2549                 }
2550                 open_view(view, request, OPEN_DEFAULT);
2551                 break;
2553         case REQ_VIEW_PAGER:
2554                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2555                         report("No pager content, press %s to run command from prompt",
2556                                get_key(REQ_PROMPT));
2557                         break;
2558                 }
2559                 open_view(view, request, OPEN_DEFAULT);
2560                 break;
2562         case REQ_VIEW_STAGE:
2563                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2564                         report("No stage content, press %s to open the status view and choose file",
2565                                get_key(REQ_VIEW_STATUS));
2566                         break;
2567                 }
2568                 open_view(view, request, OPEN_DEFAULT);
2569                 break;
2571         case REQ_VIEW_STATUS:
2572                 if (opt_is_inside_work_tree == FALSE) {
2573                         report("The status view requires a working tree");
2574                         break;
2575                 }
2576                 open_view(view, request, OPEN_DEFAULT);
2577                 break;
2579         case REQ_VIEW_MAIN:
2580         case REQ_VIEW_DIFF:
2581         case REQ_VIEW_LOG:
2582         case REQ_VIEW_TREE:
2583         case REQ_VIEW_HELP:
2584                 open_view(view, request, OPEN_DEFAULT);
2585                 break;
2587         case REQ_NEXT:
2588         case REQ_PREVIOUS:
2589                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2591                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2592                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2593                    (view == VIEW(REQ_VIEW_DIFF) &&
2594                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2595                    (view == VIEW(REQ_VIEW_STAGE) &&
2596                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2597                    (view == VIEW(REQ_VIEW_BLOB) &&
2598                      view->parent == VIEW(REQ_VIEW_TREE))) {
2599                         int line;
2601                         view = view->parent;
2602                         line = view->lineno;
2603                         move_view(view, request);
2604                         if (view_is_displayed(view))
2605                                 update_view_title(view);
2606                         if (line != view->lineno)
2607                                 view->ops->request(view, REQ_ENTER,
2608                                                    &view->line[view->lineno]);
2610                 } else {
2611                         move_view(view, request);
2612                 }
2613                 break;
2615         case REQ_VIEW_NEXT:
2616         {
2617                 int nviews = displayed_views();
2618                 int next_view = (current_view + 1) % nviews;
2620                 if (next_view == current_view) {
2621                         report("Only one view is displayed");
2622                         break;
2623                 }
2625                 current_view = next_view;
2626                 /* Blur out the title of the previous view. */
2627                 update_view_title(view);
2628                 report("");
2629                 break;
2630         }
2631         case REQ_REFRESH:
2632                 report("Refreshing is not yet supported for the %s view", view->name);
2633                 break;
2635         case REQ_MAXIMIZE:
2636                 if (displayed_views() == 2)
2637                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2638                 break;
2640         case REQ_TOGGLE_LINENO:
2641                 opt_line_number = !opt_line_number;
2642                 redraw_display();
2643                 break;
2645         case REQ_TOGGLE_DATE:
2646                 opt_date = !opt_date;
2647                 redraw_display();
2648                 break;
2650         case REQ_TOGGLE_AUTHOR:
2651                 opt_author = !opt_author;
2652                 redraw_display();
2653                 break;
2655         case REQ_TOGGLE_REV_GRAPH:
2656                 opt_rev_graph = !opt_rev_graph;
2657                 redraw_display();
2658                 break;
2660         case REQ_TOGGLE_REFS:
2661                 opt_show_refs = !opt_show_refs;
2662                 redraw_display();
2663                 break;
2665         case REQ_PROMPT:
2666                 /* Always reload^Wrerun commands from the prompt. */
2667                 open_view(view, opt_request, OPEN_RELOAD);
2668                 break;
2670         case REQ_SEARCH:
2671         case REQ_SEARCH_BACK:
2672                 search_view(view, request);
2673                 break;
2675         case REQ_FIND_NEXT:
2676         case REQ_FIND_PREV:
2677                 find_next(view, request);
2678                 break;
2680         case REQ_STOP_LOADING:
2681                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2682                         view = &views[i];
2683                         if (view->pipe)
2684                                 report("Stopped loading the %s view", view->name),
2685                         end_update(view);
2686                 }
2687                 break;
2689         case REQ_SHOW_VERSION:
2690                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2691                 return TRUE;
2693         case REQ_SCREEN_RESIZE:
2694                 resize_display();
2695                 /* Fall-through */
2696         case REQ_SCREEN_REDRAW:
2697                 redraw_display();
2698                 break;
2700         case REQ_EDIT:
2701                 report("Nothing to edit");
2702                 break;
2705         case REQ_ENTER:
2706                 report("Nothing to enter");
2707                 break;
2710         case REQ_VIEW_CLOSE:
2711                 /* XXX: Mark closed views by letting view->parent point to the
2712                  * view itself. Parents to closed view should never be
2713                  * followed. */
2714                 if (view->parent &&
2715                     view->parent->parent != view->parent) {
2716                         memset(display, 0, sizeof(display));
2717                         current_view = 0;
2718                         display[current_view] = view->parent;
2719                         view->parent = view;
2720                         resize_display();
2721                         redraw_display();
2722                         break;
2723                 }
2724                 /* Fall-through */
2725         case REQ_QUIT:
2726                 return FALSE;
2728         default:
2729                 /* An unknown key will show most commonly used commands. */
2730                 report("Unknown key, press 'h' for help");
2731                 return TRUE;
2732         }
2734         return TRUE;
2738 /*
2739  * Pager backend
2740  */
2742 static bool
2743 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2745         static char spaces[] = "                    ";
2746         char *text = line->data;
2747         enum line_type type = line->type;
2748         int attr = A_NORMAL;
2749         int col = 0;
2751         wmove(view->win, lineno, 0);
2753         if (selected) {
2754                 type = LINE_CURSOR;
2755                 wchgat(view->win, -1, 0, type, NULL);
2756                 attr = get_line_attr(type);
2757         }
2758         wattrset(view->win, attr);
2760         if (opt_line_number) {
2761                 col += draw_lineno(view, lineno, view->width, selected);
2762                 if (col >= view->width)
2763                         return TRUE;
2764         }
2766         if (!selected) {
2767                 attr = get_line_attr(type);
2768                 wattrset(view->win, attr);
2769         }
2770         if (opt_tab_size < TABSIZE) {
2771                 int col_offset = col;
2773                 col = 0;
2774                 while (text && col_offset + col < view->width) {
2775                         int cols_max = view->width - col_offset - col;
2776                         char *pos = text;
2777                         int cols;
2779                         if (*text == '\t') {
2780                                 text++;
2781                                 assert(sizeof(spaces) > TABSIZE);
2782                                 pos = spaces;
2783                                 cols = opt_tab_size - (col % opt_tab_size);
2785                         } else {
2786                                 text = strchr(text, '\t');
2787                                 cols = line ? text - pos : strlen(pos);
2788                         }
2790                         waddnstr(view->win, pos, MIN(cols, cols_max));
2791                         col += cols;
2792                 }
2794         } else {
2795                 draw_text(view, text, view->width - col, TRUE, selected);
2796         }
2798         return TRUE;
2801 static bool
2802 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2804         char refbuf[SIZEOF_STR];
2805         char *ref = NULL;
2806         FILE *pipe;
2808         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2809                 return TRUE;
2811         pipe = popen(refbuf, "r");
2812         if (!pipe)
2813                 return TRUE;
2815         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2816                 ref = chomp_string(ref);
2817         pclose(pipe);
2819         if (!ref || !*ref)
2820                 return TRUE;
2822         /* This is the only fatal call, since it can "corrupt" the buffer. */
2823         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2824                 return FALSE;
2826         return TRUE;
2829 static void
2830 add_pager_refs(struct view *view, struct line *line)
2832         char buf[SIZEOF_STR];
2833         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2834         struct ref **refs;
2835         size_t bufpos = 0, refpos = 0;
2836         const char *sep = "Refs: ";
2837         bool is_tag = FALSE;
2839         assert(line->type == LINE_COMMIT);
2841         refs = get_refs(commit_id);
2842         if (!refs) {
2843                 if (view == VIEW(REQ_VIEW_DIFF))
2844                         goto try_add_describe_ref;
2845                 return;
2846         }
2848         do {
2849                 struct ref *ref = refs[refpos];
2850                 char *fmt = ref->tag    ? "%s[%s]" :
2851                             ref->remote ? "%s<%s>" : "%s%s";
2853                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2854                         return;
2855                 sep = ", ";
2856                 if (ref->tag)
2857                         is_tag = TRUE;
2858         } while (refs[refpos++]->next);
2860         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2861 try_add_describe_ref:
2862                 /* Add <tag>-g<commit_id> "fake" reference. */
2863                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2864                         return;
2865         }
2867         if (bufpos == 0)
2868                 return;
2870         if (!realloc_lines(view, view->line_size + 1))
2871                 return;
2873         add_line_text(view, buf, LINE_PP_REFS);
2876 static bool
2877 pager_read(struct view *view, char *data)
2879         struct line *line;
2881         if (!data)
2882                 return TRUE;
2884         line = add_line_text(view, data, get_line_type(data));
2885         if (!line)
2886                 return FALSE;
2888         if (line->type == LINE_COMMIT &&
2889             (view == VIEW(REQ_VIEW_DIFF) ||
2890              view == VIEW(REQ_VIEW_LOG)))
2891                 add_pager_refs(view, line);
2893         return TRUE;
2896 static enum request
2897 pager_request(struct view *view, enum request request, struct line *line)
2899         int split = 0;
2901         if (request != REQ_ENTER)
2902                 return request;
2904         if (line->type == LINE_COMMIT &&
2905            (view == VIEW(REQ_VIEW_LOG) ||
2906             view == VIEW(REQ_VIEW_PAGER))) {
2907                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2908                 split = 1;
2909         }
2911         /* Always scroll the view even if it was split. That way
2912          * you can use Enter to scroll through the log view and
2913          * split open each commit diff. */
2914         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2916         /* FIXME: A minor workaround. Scrolling the view will call report("")
2917          * but if we are scrolling a non-current view this won't properly
2918          * update the view title. */
2919         if (split)
2920                 update_view_title(view);
2922         return REQ_NONE;
2925 static bool
2926 pager_grep(struct view *view, struct line *line)
2928         regmatch_t pmatch;
2929         char *text = line->data;
2931         if (!*text)
2932                 return FALSE;
2934         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2935                 return FALSE;
2937         return TRUE;
2940 static void
2941 pager_select(struct view *view, struct line *line)
2943         if (line->type == LINE_COMMIT) {
2944                 char *text = (char *)line->data + STRING_SIZE("commit ");
2946                 if (view != VIEW(REQ_VIEW_PAGER))
2947                         string_copy_rev(view->ref, text);
2948                 string_copy_rev(ref_commit, text);
2949         }
2952 static struct view_ops pager_ops = {
2953         "line",
2954         NULL,
2955         pager_read,
2956         pager_draw,
2957         pager_request,
2958         pager_grep,
2959         pager_select,
2960 };
2963 /*
2964  * Help backend
2965  */
2967 static bool
2968 help_open(struct view *view)
2970         char buf[BUFSIZ];
2971         int lines = ARRAY_SIZE(req_info) + 2;
2972         int i;
2974         if (view->lines > 0)
2975                 return TRUE;
2977         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2978                 if (!req_info[i].request)
2979                         lines++;
2981         lines += run_requests + 1;
2983         view->line = calloc(lines, sizeof(*view->line));
2984         if (!view->line)
2985                 return FALSE;
2987         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2989         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2990                 char *key;
2992                 if (req_info[i].request == REQ_NONE)
2993                         continue;
2995                 if (!req_info[i].request) {
2996                         add_line_text(view, "", LINE_DEFAULT);
2997                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2998                         continue;
2999                 }
3001                 key = get_key(req_info[i].request);
3002                 if (!*key)
3003                         key = "(no key defined)";
3005                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3006                         continue;
3008                 add_line_text(view, buf, LINE_DEFAULT);
3009         }
3011         if (run_requests) {
3012                 add_line_text(view, "", LINE_DEFAULT);
3013                 add_line_text(view, "External commands:", LINE_DEFAULT);
3014         }
3016         for (i = 0; i < run_requests; i++) {
3017                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3018                 char *key;
3020                 if (!req)
3021                         continue;
3023                 key = get_key_name(req->key);
3024                 if (!*key)
3025                         key = "(no key defined)";
3027                 if (!string_format(buf, "    %-10s %-14s `%s`",
3028                                    keymap_table[req->keymap].name,
3029                                    key, req->cmd))
3030                         continue;
3032                 add_line_text(view, buf, LINE_DEFAULT);
3033         }
3035         return TRUE;
3038 static struct view_ops help_ops = {
3039         "line",
3040         help_open,
3041         NULL,
3042         pager_draw,
3043         pager_request,
3044         pager_grep,
3045         pager_select,
3046 };
3049 /*
3050  * Tree backend
3051  */
3053 struct tree_stack_entry {
3054         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3055         unsigned long lineno;           /* Line number to restore */
3056         char *name;                     /* Position of name in opt_path */
3057 };
3059 /* The top of the path stack. */
3060 static struct tree_stack_entry *tree_stack = NULL;
3061 unsigned long tree_lineno = 0;
3063 static void
3064 pop_tree_stack_entry(void)
3066         struct tree_stack_entry *entry = tree_stack;
3068         tree_lineno = entry->lineno;
3069         entry->name[0] = 0;
3070         tree_stack = entry->prev;
3071         free(entry);
3074 static void
3075 push_tree_stack_entry(char *name, unsigned long lineno)
3077         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3078         size_t pathlen = strlen(opt_path);
3080         if (!entry)
3081                 return;
3083         entry->prev = tree_stack;
3084         entry->name = opt_path + pathlen;
3085         tree_stack = entry;
3087         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3088                 pop_tree_stack_entry();
3089                 return;
3090         }
3092         /* Move the current line to the first tree entry. */
3093         tree_lineno = 1;
3094         entry->lineno = lineno;
3097 /* Parse output from git-ls-tree(1):
3098  *
3099  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3100  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3101  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3102  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3103  */
3105 #define SIZEOF_TREE_ATTR \
3106         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3108 #define TREE_UP_FORMAT "040000 tree %s\t.."
3110 static int
3111 tree_compare_entry(enum line_type type1, char *name1,
3112                    enum line_type type2, char *name2)
3114         if (type1 != type2) {
3115                 if (type1 == LINE_TREE_DIR)
3116                         return -1;
3117                 return 1;
3118         }
3120         return strcmp(name1, name2);
3123 static char *
3124 tree_path(struct line *line)
3126         char *path = line->data;
3128         return path + SIZEOF_TREE_ATTR;
3131 static bool
3132 tree_read(struct view *view, char *text)
3134         size_t textlen = text ? strlen(text) : 0;
3135         char buf[SIZEOF_STR];
3136         unsigned long pos;
3137         enum line_type type;
3138         bool first_read = view->lines == 0;
3140         if (!text)
3141                 return TRUE;
3142         if (textlen <= SIZEOF_TREE_ATTR)
3143                 return FALSE;
3145         type = text[STRING_SIZE("100644 ")] == 't'
3146              ? LINE_TREE_DIR : LINE_TREE_FILE;
3148         if (first_read) {
3149                 /* Add path info line */
3150                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3151                     !realloc_lines(view, view->line_size + 1) ||
3152                     !add_line_text(view, buf, LINE_DEFAULT))
3153                         return FALSE;
3155                 /* Insert "link" to parent directory. */
3156                 if (*opt_path) {
3157                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3158                             !realloc_lines(view, view->line_size + 1) ||
3159                             !add_line_text(view, buf, LINE_TREE_DIR))
3160                                 return FALSE;
3161                 }
3162         }
3164         /* Strip the path part ... */
3165         if (*opt_path) {
3166                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3167                 size_t striplen = strlen(opt_path);
3168                 char *path = text + SIZEOF_TREE_ATTR;
3170                 if (pathlen > striplen)
3171                         memmove(path, path + striplen,
3172                                 pathlen - striplen + 1);
3173         }
3175         /* Skip "Directory ..." and ".." line. */
3176         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3177                 struct line *line = &view->line[pos];
3178                 char *path1 = tree_path(line);
3179                 char *path2 = text + SIZEOF_TREE_ATTR;
3180                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3182                 if (cmp <= 0)
3183                         continue;
3185                 text = strdup(text);
3186                 if (!text)
3187                         return FALSE;
3189                 if (view->lines > pos)
3190                         memmove(&view->line[pos + 1], &view->line[pos],
3191                                 (view->lines - pos) * sizeof(*line));
3193                 line = &view->line[pos];
3194                 line->data = text;
3195                 line->type = type;
3196                 view->lines++;
3197                 return TRUE;
3198         }
3200         if (!add_line_text(view, text, type))
3201                 return FALSE;
3203         if (tree_lineno > view->lineno) {
3204                 view->lineno = tree_lineno;
3205                 tree_lineno = 0;
3206         }
3208         return TRUE;
3211 static enum request
3212 tree_request(struct view *view, enum request request, struct line *line)
3214         enum open_flags flags;
3216         if (request == REQ_VIEW_BLAME) {
3217                 char *filename = tree_path(line);
3219                 if (line->type == LINE_TREE_DIR) {
3220                         report("Cannot show blame for directory %s", opt_path);
3221                         return REQ_NONE;
3222                 }
3224                 string_copy(opt_ref, view->vid);
3225                 string_format(opt_file, "%s%s", opt_path, filename);
3226                 return request;
3227         }
3228         if (request == REQ_TREE_PARENT) {
3229                 if (*opt_path) {
3230                         /* fake 'cd  ..' */
3231                         request = REQ_ENTER;
3232                         line = &view->line[1];
3233                 } else {
3234                         /* quit view if at top of tree */
3235                         return REQ_VIEW_CLOSE;
3236                 }
3237         }
3238         if (request != REQ_ENTER)
3239                 return request;
3241         /* Cleanup the stack if the tree view is at a different tree. */
3242         while (!*opt_path && tree_stack)
3243                 pop_tree_stack_entry();
3245         switch (line->type) {
3246         case LINE_TREE_DIR:
3247                 /* Depending on whether it is a subdir or parent (updir?) link
3248                  * mangle the path buffer. */
3249                 if (line == &view->line[1] && *opt_path) {
3250                         pop_tree_stack_entry();
3252                 } else {
3253                         char *basename = tree_path(line);
3255                         push_tree_stack_entry(basename, view->lineno);
3256                 }
3258                 /* Trees and subtrees share the same ID, so they are not not
3259                  * unique like blobs. */
3260                 flags = OPEN_RELOAD;
3261                 request = REQ_VIEW_TREE;
3262                 break;
3264         case LINE_TREE_FILE:
3265                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3266                 request = REQ_VIEW_BLOB;
3267                 break;
3269         default:
3270                 return TRUE;
3271         }
3273         open_view(view, request, flags);
3274         if (request == REQ_VIEW_TREE) {
3275                 view->lineno = tree_lineno;
3276         }
3278         return REQ_NONE;
3281 static void
3282 tree_select(struct view *view, struct line *line)
3284         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3286         if (line->type == LINE_TREE_FILE) {
3287                 string_copy_rev(ref_blob, text);
3289         } else if (line->type != LINE_TREE_DIR) {
3290                 return;
3291         }
3293         string_copy_rev(view->ref, text);
3296 static struct view_ops tree_ops = {
3297         "file",
3298         NULL,
3299         tree_read,
3300         pager_draw,
3301         tree_request,
3302         pager_grep,
3303         tree_select,
3304 };
3306 static bool
3307 blob_read(struct view *view, char *line)
3309         if (!line)
3310                 return TRUE;
3311         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3314 static struct view_ops blob_ops = {
3315         "line",
3316         NULL,
3317         blob_read,
3318         pager_draw,
3319         pager_request,
3320         pager_grep,
3321         pager_select,
3322 };
3324 /*
3325  * Blame backend
3326  *
3327  * Loading the blame view is a two phase job:
3328  *
3329  *  1. File content is read either using opt_file from the
3330  *     filesystem or using git-cat-file.
3331  *  2. Then blame information is incrementally added by
3332  *     reading output from git-blame.
3333  */
3335 struct blame_commit {
3336         char id[SIZEOF_REV];            /* SHA1 ID. */
3337         char title[128];                /* First line of the commit message. */
3338         char author[75];                /* Author of the commit. */
3339         struct tm time;                 /* Date from the author ident. */
3340         char filename[128];             /* Name of file. */
3341 };
3343 struct blame {
3344         struct blame_commit *commit;
3345         unsigned int header:1;
3346         char text[1];
3347 };
3349 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3350 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3352 static bool
3353 blame_open(struct view *view)
3355         char path[SIZEOF_STR];
3356         char ref[SIZEOF_STR] = "";
3358         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3359                 return FALSE;
3361         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3362                 return FALSE;
3364         if (*opt_ref) {
3365                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3366                         return FALSE;
3367         } else {
3368                 view->pipe = fopen(opt_file, "r");
3369                 if (!view->pipe &&
3370                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3371                         return FALSE;
3372         }
3374         if (!view->pipe)
3375                 view->pipe = popen(view->cmd, "r");
3376         if (!view->pipe)
3377                 return FALSE;
3379         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3380                 return FALSE;
3382         string_format(view->ref, "%s ...", opt_file);
3383         string_copy_rev(view->vid, opt_file);
3384         set_nonblocking_input(TRUE);
3386         if (view->line) {
3387                 int i;
3389                 for (i = 0; i < view->lines; i++)
3390                         free(view->line[i].data);
3391                 free(view->line);
3392         }
3394         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3395         view->offset = view->lines  = view->lineno = 0;
3396         view->line = NULL;
3397         view->start_time = time(NULL);
3399         return TRUE;
3402 static struct blame_commit *
3403 get_blame_commit(struct view *view, const char *id)
3405         size_t i;
3407         for (i = 0; i < view->lines; i++) {
3408                 struct blame *blame = view->line[i].data;
3410                 if (!blame->commit)
3411                         continue;
3413                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3414                         return blame->commit;
3415         }
3417         {
3418                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3420                 if (commit)
3421                         string_ncopy(commit->id, id, SIZEOF_REV);
3422                 return commit;
3423         }
3426 static bool
3427 parse_number(char **posref, size_t *number, size_t min, size_t max)
3429         char *pos = *posref;
3431         *posref = NULL;
3432         pos = strchr(pos + 1, ' ');
3433         if (!pos || !isdigit(pos[1]))
3434                 return FALSE;
3435         *number = atoi(pos + 1);
3436         if (*number < min || *number > max)
3437                 return FALSE;
3439         *posref = pos;
3440         return TRUE;
3443 static struct blame_commit *
3444 parse_blame_commit(struct view *view, char *text, int *blamed)
3446         struct blame_commit *commit;
3447         struct blame *blame;
3448         char *pos = text + SIZEOF_REV - 1;
3449         size_t lineno;
3450         size_t group;
3452         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3453                 return NULL;
3455         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3456             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3457                 return NULL;
3459         commit = get_blame_commit(view, text);
3460         if (!commit)
3461                 return NULL;
3463         *blamed += group;
3464         while (group--) {
3465                 struct line *line = &view->line[lineno + group - 1];
3467                 blame = line->data;
3468                 blame->commit = commit;
3469                 blame->header = !group;
3470                 line->dirty = 1;
3471         }
3473         return commit;
3476 static bool
3477 blame_read_file(struct view *view, char *line)
3479         if (!line) {
3480                 FILE *pipe = NULL;
3482                 if (view->lines > 0)
3483                         pipe = popen(view->cmd, "r");
3484                 view->cmd[0] = 0;
3485                 if (!pipe) {
3486                         report("Failed to load blame data");
3487                         return TRUE;
3488                 }
3490                 fclose(view->pipe);
3491                 view->pipe = pipe;
3492                 return FALSE;
3494         } else {
3495                 size_t linelen = strlen(line);
3496                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3498                 if (!line)
3499                         return FALSE;
3501                 blame->commit = NULL;
3502                 strncpy(blame->text, line, linelen);
3503                 blame->text[linelen] = 0;
3504                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3505         }
3508 static bool
3509 match_blame_header(const char *name, char **line)
3511         size_t namelen = strlen(name);
3512         bool matched = !strncmp(name, *line, namelen);
3514         if (matched)
3515                 *line += namelen;
3517         return matched;
3520 static bool
3521 blame_read(struct view *view, char *line)
3523         static struct blame_commit *commit = NULL;
3524         static int blamed = 0;
3525         static time_t author_time;
3527         if (*view->cmd)
3528                 return blame_read_file(view, line);
3530         if (!line) {
3531                 /* Reset all! */
3532                 commit = NULL;
3533                 blamed = 0;
3534                 string_format(view->ref, "%s", view->vid);
3535                 if (view_is_displayed(view)) {
3536                         update_view_title(view);
3537                         redraw_view_from(view, 0);
3538                 }
3539                 return TRUE;
3540         }
3542         if (!commit) {
3543                 commit = parse_blame_commit(view, line, &blamed);
3544                 string_format(view->ref, "%s %2d%%", view->vid,
3545                               blamed * 100 / view->lines);
3547         } else if (match_blame_header("author ", &line)) {
3548                 string_ncopy(commit->author, line, strlen(line));
3550         } else if (match_blame_header("author-time ", &line)) {
3551                 author_time = (time_t) atol(line);
3553         } else if (match_blame_header("author-tz ", &line)) {
3554                 long tz;
3556                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3557                 tz += ('0' - line[2]) * 60 * 60;
3558                 tz += ('0' - line[3]) * 60;
3559                 tz += ('0' - line[4]) * 60;
3561                 if (line[0] == '-')
3562                         tz = -tz;
3564                 author_time -= tz;
3565                 gmtime_r(&author_time, &commit->time);
3567         } else if (match_blame_header("summary ", &line)) {
3568                 string_ncopy(commit->title, line, strlen(line));
3570         } else if (match_blame_header("filename ", &line)) {
3571                 string_ncopy(commit->filename, line, strlen(line));
3572                 commit = NULL;
3573         }
3575         return TRUE;
3578 static bool
3579 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3581         struct blame *blame = line->data;
3582         int col = 0;
3584         wmove(view->win, lineno, 0);
3586         if (selected) {
3587                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3588                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3589         } else {
3590                 wattrset(view->win, A_NORMAL);
3591         }
3593         if (opt_date) {
3594                 int n;
3596                 if (!selected)
3597                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3598                 if (blame->commit) {
3599                         char buf[DATE_COLS + 1];
3600                         int timelen;
3602                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3603                         n = draw_text(view, buf, view->width - col, FALSE, selected);
3604                         draw_text(view, " ", view->width - col - n, FALSE, selected);
3605                 }
3607                 col += DATE_COLS;
3608                 wmove(view->win, lineno, col);
3609                 if (col >= view->width)
3610                         return TRUE;
3611         }
3613         if (opt_author) {
3614                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3616                 if (!selected)
3617                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3618                 if (blame->commit)
3619                         draw_text(view, blame->commit->author, max, TRUE, selected);
3620                 col += AUTHOR_COLS;
3621                 if (col >= view->width)
3622                         return TRUE;
3623                 wmove(view->win, lineno, col);
3624         }
3626         {
3627                 int max = MIN(ID_COLS - 1, view->width - col);
3629                 if (!selected)
3630                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3631                 if (blame->commit)
3632                         draw_text(view, blame->commit->id, max, FALSE, -1);
3633                 col += ID_COLS;
3634                 if (col >= view->width)
3635                         return TRUE;
3636                 wmove(view->win, lineno, col);
3637         }
3639         {
3640                 col += draw_lineno(view, lineno, view->width - col, selected);
3641                 if (col >= view->width)
3642                         return TRUE;
3643         }
3645         col += draw_text(view, blame->text, view->width - col, TRUE, selected);
3647         return TRUE;
3650 static enum request
3651 blame_request(struct view *view, enum request request, struct line *line)
3653         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3654         struct blame *blame = line->data;
3656         switch (request) {
3657         case REQ_ENTER:
3658                 if (!blame->commit) {
3659                         report("No commit loaded yet");
3660                         break;
3661                 }
3663                 if (!strcmp(blame->commit->id, NULL_ID)) {
3664                         char path[SIZEOF_STR];
3666                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3667                                 break;
3668                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3669                 }
3671                 open_view(view, REQ_VIEW_DIFF, flags);
3672                 break;
3674         default:
3675                 return request;
3676         }
3678         return REQ_NONE;
3681 static bool
3682 blame_grep(struct view *view, struct line *line)
3684         struct blame *blame = line->data;
3685         struct blame_commit *commit = blame->commit;
3686         regmatch_t pmatch;
3688 #define MATCH(text) \
3689         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3691         if (commit) {
3692                 char buf[DATE_COLS + 1];
3694                 if (MATCH(commit->title) ||
3695                     MATCH(commit->author) ||
3696                     MATCH(commit->id))
3697                         return TRUE;
3699                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3700                     MATCH(buf))
3701                         return TRUE;
3702         }
3704         return MATCH(blame->text);
3706 #undef MATCH
3709 static void
3710 blame_select(struct view *view, struct line *line)
3712         struct blame *blame = line->data;
3713         struct blame_commit *commit = blame->commit;
3715         if (!commit)
3716                 return;
3718         if (!strcmp(commit->id, NULL_ID))
3719                 string_ncopy(ref_commit, "HEAD", 4);
3720         else
3721                 string_copy_rev(ref_commit, commit->id);
3724 static struct view_ops blame_ops = {
3725         "line",
3726         blame_open,
3727         blame_read,
3728         blame_draw,
3729         blame_request,
3730         blame_grep,
3731         blame_select,
3732 };
3734 /*
3735  * Status backend
3736  */
3738 struct status {
3739         char status;
3740         struct {
3741                 mode_t mode;
3742                 char rev[SIZEOF_REV];
3743                 char name[SIZEOF_STR];
3744         } old;
3745         struct {
3746                 mode_t mode;
3747                 char rev[SIZEOF_REV];
3748                 char name[SIZEOF_STR];
3749         } new;
3750 };
3752 static char status_onbranch[SIZEOF_STR];
3753 static struct status stage_status;
3754 static enum line_type stage_line_type;
3756 /* Get fields from the diff line:
3757  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3758  */
3759 static inline bool
3760 status_get_diff(struct status *file, char *buf, size_t bufsize)
3762         char *old_mode = buf +  1;
3763         char *new_mode = buf +  8;
3764         char *old_rev  = buf + 15;
3765         char *new_rev  = buf + 56;
3766         char *status   = buf + 97;
3768         if (bufsize < 99 ||
3769             old_mode[-1] != ':' ||
3770             new_mode[-1] != ' ' ||
3771             old_rev[-1]  != ' ' ||
3772             new_rev[-1]  != ' ' ||
3773             status[-1]   != ' ')
3774                 return FALSE;
3776         file->status = *status;
3778         string_copy_rev(file->old.rev, old_rev);
3779         string_copy_rev(file->new.rev, new_rev);
3781         file->old.mode = strtoul(old_mode, NULL, 8);
3782         file->new.mode = strtoul(new_mode, NULL, 8);
3784         file->old.name[0] = file->new.name[0] = 0;
3786         return TRUE;
3789 static bool
3790 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3792         struct status *file = NULL;
3793         struct status *unmerged = NULL;
3794         char buf[SIZEOF_STR * 4];
3795         size_t bufsize = 0;
3796         FILE *pipe;
3798         pipe = popen(cmd, "r");
3799         if (!pipe)
3800                 return FALSE;
3802         add_line_data(view, NULL, type);
3804         while (!feof(pipe) && !ferror(pipe)) {
3805                 char *sep;
3806                 size_t readsize;
3808                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3809                 if (!readsize)
3810                         break;
3811                 bufsize += readsize;
3813                 /* Process while we have NUL chars. */
3814                 while ((sep = memchr(buf, 0, bufsize))) {
3815                         size_t sepsize = sep - buf + 1;
3817                         if (!file) {
3818                                 if (!realloc_lines(view, view->line_size + 1))
3819                                         goto error_out;
3821                                 file = calloc(1, sizeof(*file));
3822                                 if (!file)
3823                                         goto error_out;
3825                                 add_line_data(view, file, type);
3826                         }
3828                         /* Parse diff info part. */
3829                         if (status) {
3830                                 file->status = status;
3831                                 if (status == 'A')
3832                                         string_copy(file->old.rev, NULL_ID);
3834                         } else if (!file->status) {
3835                                 if (!status_get_diff(file, buf, sepsize))
3836                                         goto error_out;
3838                                 bufsize -= sepsize;
3839                                 memmove(buf, sep + 1, bufsize);
3841                                 sep = memchr(buf, 0, bufsize);
3842                                 if (!sep)
3843                                         break;
3844                                 sepsize = sep - buf + 1;
3846                                 /* Collapse all 'M'odified entries that
3847                                  * follow a associated 'U'nmerged entry.
3848                                  */
3849                                 if (file->status == 'U') {
3850                                         unmerged = file;
3852                                 } else if (unmerged) {
3853                                         int collapse = !strcmp(buf, unmerged->new.name);
3855                                         unmerged = NULL;
3856                                         if (collapse) {
3857                                                 free(file);
3858                                                 view->lines--;
3859                                                 continue;
3860                                         }
3861                                 }
3862                         }
3864                         /* Grab the old name for rename/copy. */
3865                         if (!*file->old.name &&
3866                             (file->status == 'R' || file->status == 'C')) {
3867                                 sepsize = sep - buf + 1;
3868                                 string_ncopy(file->old.name, buf, sepsize);
3869                                 bufsize -= sepsize;
3870                                 memmove(buf, sep + 1, bufsize);
3872                                 sep = memchr(buf, 0, bufsize);
3873                                 if (!sep)
3874                                         break;
3875                                 sepsize = sep - buf + 1;
3876                         }
3878                         /* git-ls-files just delivers a NUL separated
3879                          * list of file names similar to the second half
3880                          * of the git-diff-* output. */
3881                         string_ncopy(file->new.name, buf, sepsize);
3882                         if (!*file->old.name)
3883                                 string_copy(file->old.name, file->new.name);
3884                         bufsize -= sepsize;
3885                         memmove(buf, sep + 1, bufsize);
3886                         file = NULL;
3887                 }
3888         }
3890         if (ferror(pipe)) {
3891 error_out:
3892                 pclose(pipe);
3893                 return FALSE;
3894         }
3896         if (!view->line[view->lines - 1].data)
3897                 add_line_data(view, NULL, LINE_STAT_NONE);
3899         pclose(pipe);
3900         return TRUE;
3903 /* Don't show unmerged entries in the staged section. */
3904 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3905 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3906 #define STATUS_LIST_OTHER_CMD \
3907         "git ls-files -z --others --exclude-per-directory=.gitignore"
3908 #define STATUS_LIST_NO_HEAD_CMD \
3909         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3911 #define STATUS_DIFF_INDEX_SHOW_CMD \
3912         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3914 #define STATUS_DIFF_FILES_SHOW_CMD \
3915         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3917 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3918         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3920 /* First parse staged info using git-diff-index(1), then parse unstaged
3921  * info using git-diff-files(1), and finally untracked files using
3922  * git-ls-files(1). */
3923 static bool
3924 status_open(struct view *view)
3926         struct stat statbuf;
3927         char exclude[SIZEOF_STR];
3928         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3929         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3930         unsigned long prev_lineno = view->lineno;
3931         char indexstatus = 0;
3932         size_t i;
3934         for (i = 0; i < view->lines; i++)
3935                 free(view->line[i].data);
3936         free(view->line);
3937         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3938         view->line = NULL;
3940         if (!realloc_lines(view, view->line_size + 7))
3941                 return FALSE;
3943         add_line_data(view, NULL, LINE_STAT_HEAD);
3944         if (opt_no_head)
3945                 string_copy(status_onbranch, "Initial commit");
3946         else if (!*opt_head)
3947                 string_copy(status_onbranch, "Not currently on any branch");
3948         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3949                 return FALSE;
3951         if (opt_no_head) {
3952                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3953                 indexstatus = 'A';
3954         }
3956         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3957                 return FALSE;
3959         if (stat(exclude, &statbuf) >= 0) {
3960                 size_t cmdsize = strlen(othercmd);
3962                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3963                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3964                         return FALSE;
3966                 cmdsize = strlen(indexcmd);
3967                 if (opt_no_head &&
3968                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3969                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
3970                         return FALSE;
3971         }
3973         system("git update-index -q --refresh");
3975         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3976             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3977             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
3978                 return FALSE;
3980         /* If all went well restore the previous line number to stay in
3981          * the context or select a line with something that can be
3982          * updated. */
3983         if (prev_lineno >= view->lines)
3984                 prev_lineno = view->lines - 1;
3985         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3986                 prev_lineno++;
3987         while (prev_lineno > 0 && !view->line[prev_lineno].data)
3988                 prev_lineno--;
3990         /* If the above fails, always skip the "On branch" line. */
3991         if (prev_lineno < view->lines)
3992                 view->lineno = prev_lineno;
3993         else
3994                 view->lineno = 1;
3996         if (view->lineno < view->offset)
3997                 view->offset = view->lineno;
3998         else if (view->offset + view->height <= view->lineno)
3999                 view->offset = view->lineno - view->height + 1;
4001         return TRUE;
4004 static bool
4005 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4007         struct status *status = line->data;
4008         char *text;
4009         int col = 0;
4011         wmove(view->win, lineno, 0);
4013         if (selected) {
4014                 wattrset(view->win, get_line_attr(LINE_CURSOR));
4015                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4017         } else if (line->type == LINE_STAT_HEAD) {
4018                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4019                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4021         } else if (!status && line->type != LINE_STAT_NONE) {
4022                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4023                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4025         } else {
4026                 wattrset(view->win, get_line_attr(line->type));
4027         }
4029         if (!status) {
4030                 switch (line->type) {
4031                 case LINE_STAT_STAGED:
4032                         text = "Changes to be committed:";
4033                         break;
4035                 case LINE_STAT_UNSTAGED:
4036                         text = "Changed but not updated:";
4037                         break;
4039                 case LINE_STAT_UNTRACKED:
4040                         text = "Untracked files:";
4041                         break;
4043                 case LINE_STAT_NONE:
4044                         text = "    (no files)";
4045                         break;
4047                 case LINE_STAT_HEAD:
4048                         text = status_onbranch;
4049                         break;
4051                 default:
4052                         return FALSE;
4053                 }
4054         } else {
4055                 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4057                 col += draw_text(view, buf, view->width, TRUE, selected);
4058                 if (!selected)
4059                         wattrset(view->win, A_NORMAL);
4060                 text = status->new.name;
4061         }
4063         draw_text(view, text, view->width - col, TRUE, selected);
4064         return TRUE;
4067 static enum request
4068 status_enter(struct view *view, struct line *line)
4070         struct status *status = line->data;
4071         char oldpath[SIZEOF_STR] = "";
4072         char newpath[SIZEOF_STR] = "";
4073         char *info;
4074         size_t cmdsize = 0;
4076         if (line->type == LINE_STAT_NONE ||
4077             (!status && line[1].type == LINE_STAT_NONE)) {
4078                 report("No file to diff");
4079                 return REQ_NONE;
4080         }
4082         if (status) {
4083                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4084                         return REQ_QUIT;
4085                 /* Diffs for unmerged entries are empty when pasing the
4086                  * new path, so leave it empty. */
4087                 if (status->status != 'U' &&
4088                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4089                         return REQ_QUIT;
4090         }
4092         if (opt_cdup[0] &&
4093             line->type != LINE_STAT_UNTRACKED &&
4094             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4095                 return REQ_QUIT;
4097         switch (line->type) {
4098         case LINE_STAT_STAGED:
4099                 if (opt_no_head) {
4100                         if (!string_format_from(opt_cmd, &cmdsize,
4101                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4102                                                 newpath))
4103                                 return REQ_QUIT;
4104                 } else {
4105                         if (!string_format_from(opt_cmd, &cmdsize,
4106                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4107                                                 oldpath, newpath))
4108                                 return REQ_QUIT;
4109                 }
4111                 if (status)
4112                         info = "Staged changes to %s";
4113                 else
4114                         info = "Staged changes";
4115                 break;
4117         case LINE_STAT_UNSTAGED:
4118                 if (!string_format_from(opt_cmd, &cmdsize,
4119                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4120                         return REQ_QUIT;
4121                 if (status)
4122                         info = "Unstaged changes to %s";
4123                 else
4124                         info = "Unstaged changes";
4125                 break;
4127         case LINE_STAT_UNTRACKED:
4128                 if (opt_pipe)
4129                         return REQ_QUIT;
4131                 if (!status) {
4132                         report("No file to show");
4133                         return REQ_NONE;
4134                 }
4136                 opt_pipe = fopen(status->new.name, "r");
4137                 info = "Untracked file %s";
4138                 break;
4140         case LINE_STAT_HEAD:
4141                 return REQ_NONE;
4143         default:
4144                 die("line type %d not handled in switch", line->type);
4145         }
4147         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4148         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4149                 if (status) {
4150                         stage_status = *status;
4151                 } else {
4152                         memset(&stage_status, 0, sizeof(stage_status));
4153                 }
4155                 stage_line_type = line->type;
4156                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4157         }
4159         return REQ_NONE;
4163 static FILE *
4164 status_update_prepare(enum line_type type)
4166         char cmd[SIZEOF_STR];
4167         size_t cmdsize = 0;
4169         if (opt_cdup[0] &&
4170             type != LINE_STAT_UNTRACKED &&
4171             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4172                 return NULL;
4174         switch (type) {
4175         case LINE_STAT_STAGED:
4176                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4177                 break;
4179         case LINE_STAT_UNSTAGED:
4180         case LINE_STAT_UNTRACKED:
4181                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4182                 break;
4184         default:
4185                 die("line type %d not handled in switch", type);
4186         }
4188         return popen(cmd, "w");
4191 static bool
4192 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4194         char buf[SIZEOF_STR];
4195         size_t bufsize = 0;
4196         size_t written = 0;
4198         switch (type) {
4199         case LINE_STAT_STAGED:
4200                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4201                                         status->old.mode,
4202                                         status->old.rev,
4203                                         status->old.name, 0))
4204                         return FALSE;
4205                 break;
4207         case LINE_STAT_UNSTAGED:
4208         case LINE_STAT_UNTRACKED:
4209                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4210                         return FALSE;
4211                 break;
4213         default:
4214                 die("line type %d not handled in switch", type);
4215         }
4217         while (!ferror(pipe) && written < bufsize) {
4218                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4219         }
4221         return written == bufsize;
4224 static bool
4225 status_update_file(struct status *status, enum line_type type)
4227         FILE *pipe = status_update_prepare(type);
4228         bool result;
4230         if (!pipe)
4231                 return FALSE;
4233         result = status_update_write(pipe, status, type);
4234         pclose(pipe);
4235         return result;
4238 static bool
4239 status_update_files(struct view *view, struct line *line)
4241         FILE *pipe = status_update_prepare(line->type);
4242         bool result = TRUE;
4243         struct line *pos = view->line + view->lines;
4244         int files = 0;
4245         int file, done;
4247         if (!pipe)
4248                 return FALSE;
4250         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4251                 files++;
4253         for (file = 0, done = 0; result && file < files; line++, file++) {
4254                 int almost_done = file * 100 / files;
4256                 if (almost_done > done) {
4257                         done = almost_done;
4258                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4259                                       file, files, done);
4260                         update_view_title(view);
4261                 }
4262                 result = status_update_write(pipe, line->data, line->type);
4263         }
4265         pclose(pipe);
4266         return result;
4269 static bool
4270 status_update(struct view *view)
4272         struct line *line = &view->line[view->lineno];
4274         assert(view->lines);
4276         if (!line->data) {
4277                 /* This should work even for the "On branch" line. */
4278                 if (line < view->line + view->lines && !line[1].data) {
4279                         report("Nothing to update");
4280                         return FALSE;
4281                 }
4283                 if (!status_update_files(view, line + 1))
4284                         report("Failed to update file status");
4286         } else if (!status_update_file(line->data, line->type)) {
4287                 report("Failed to update file status");
4288         }
4290         return TRUE;
4293 static enum request
4294 status_request(struct view *view, enum request request, struct line *line)
4296         struct status *status = line->data;
4298         switch (request) {
4299         case REQ_STATUS_UPDATE:
4300                 if (!status_update(view))
4301                         return REQ_NONE;
4302                 break;
4304         case REQ_STATUS_MERGE:
4305                 if (!status || status->status != 'U') {
4306                         report("Merging only possible for files with unmerged status ('U').");
4307                         return REQ_NONE;
4308                 }
4309                 open_mergetool(status->new.name);
4310                 break;
4312         case REQ_EDIT:
4313                 if (!status)
4314                         return request;
4316                 open_editor(status->status != '?', status->new.name);
4317                 break;
4319         case REQ_VIEW_BLAME:
4320                 if (status) {
4321                         string_copy(opt_file, status->new.name);
4322                         opt_ref[0] = 0;
4323                 }
4324                 return request;
4326         case REQ_ENTER:
4327                 /* After returning the status view has been split to
4328                  * show the stage view. No further reloading is
4329                  * necessary. */
4330                 status_enter(view, line);
4331                 return REQ_NONE;
4333         case REQ_REFRESH:
4334                 /* Simply reload the view. */
4335                 break;
4337         default:
4338                 return request;
4339         }
4341         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4343         return REQ_NONE;
4346 static void
4347 status_select(struct view *view, struct line *line)
4349         struct status *status = line->data;
4350         char file[SIZEOF_STR] = "all files";
4351         char *text;
4352         char *key;
4354         if (status && !string_format(file, "'%s'", status->new.name))
4355                 return;
4357         if (!status && line[1].type == LINE_STAT_NONE)
4358                 line++;
4360         switch (line->type) {
4361         case LINE_STAT_STAGED:
4362                 text = "Press %s to unstage %s for commit";
4363                 break;
4365         case LINE_STAT_UNSTAGED:
4366                 text = "Press %s to stage %s for commit";
4367                 break;
4369         case LINE_STAT_UNTRACKED:
4370                 text = "Press %s to stage %s for addition";
4371                 break;
4373         case LINE_STAT_HEAD:
4374         case LINE_STAT_NONE:
4375                 text = "Nothing to update";
4376                 break;
4378         default:
4379                 die("line type %d not handled in switch", line->type);
4380         }
4382         if (status && status->status == 'U') {
4383                 text = "Press %s to resolve conflict in %s";
4384                 key = get_key(REQ_STATUS_MERGE);
4386         } else {
4387                 key = get_key(REQ_STATUS_UPDATE);
4388         }
4390         string_format(view->ref, text, key, file);
4393 static bool
4394 status_grep(struct view *view, struct line *line)
4396         struct status *status = line->data;
4397         enum { S_STATUS, S_NAME, S_END } state;
4398         char buf[2] = "?";
4399         regmatch_t pmatch;
4401         if (!status)
4402                 return FALSE;
4404         for (state = S_STATUS; state < S_END; state++) {
4405                 char *text;
4407                 switch (state) {
4408                 case S_NAME:    text = status->new.name;        break;
4409                 case S_STATUS:
4410                         buf[0] = status->status;
4411                         text = buf;
4412                         break;
4414                 default:
4415                         return FALSE;
4416                 }
4418                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4419                         return TRUE;
4420         }
4422         return FALSE;
4425 static struct view_ops status_ops = {
4426         "file",
4427         status_open,
4428         NULL,
4429         status_draw,
4430         status_request,
4431         status_grep,
4432         status_select,
4433 };
4436 static bool
4437 stage_diff_line(FILE *pipe, struct line *line)
4439         char *buf = line->data;
4440         size_t bufsize = strlen(buf);
4441         size_t written = 0;
4443         while (!ferror(pipe) && written < bufsize) {
4444                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4445         }
4447         fputc('\n', pipe);
4449         return written == bufsize;
4452 static struct line *
4453 stage_diff_hdr(struct view *view, struct line *line)
4455         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4456         struct line *diff_hdr;
4458         if (line->type == LINE_DIFF_CHUNK)
4459                 diff_hdr = line - 1;
4460         else
4461                 diff_hdr = view->line + 1;
4463         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4464                 if (diff_hdr->type == LINE_DIFF_HEADER)
4465                         return diff_hdr;
4467                 diff_hdr += diff_hdr_dir;
4468         }
4470         return NULL;
4473 static bool
4474 stage_update_chunk(struct view *view, struct line *line)
4476         char cmd[SIZEOF_STR];
4477         size_t cmdsize = 0;
4478         struct line *diff_hdr, *diff_chunk, *diff_end;
4479         FILE *pipe;
4481         diff_hdr = stage_diff_hdr(view, line);
4482         if (!diff_hdr)
4483                 return FALSE;
4485         if (opt_cdup[0] &&
4486             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4487                 return FALSE;
4489         if (!string_format_from(cmd, &cmdsize,
4490                                 "git apply --whitespace=nowarn --cached %s - && "
4491                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4492                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4493                 return FALSE;
4495         pipe = popen(cmd, "w");
4496         if (!pipe)
4497                 return FALSE;
4499         diff_end = view->line + view->lines;
4500         if (line->type != LINE_DIFF_CHUNK) {
4501                 diff_chunk = diff_hdr;
4503         } else {
4504                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4505                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
4506                             diff_chunk->type == LINE_DIFF_HEADER)
4507                                 diff_end = diff_chunk;
4509                 diff_chunk = line;
4511                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4512                         switch (diff_hdr->type) {
4513                         case LINE_DIFF_HEADER:
4514                         case LINE_DIFF_INDEX:
4515                         case LINE_DIFF_ADD:
4516                         case LINE_DIFF_DEL:
4517                                 break;
4519                         default:
4520                                 diff_hdr++;
4521                                 continue;
4522                         }
4524                         if (!stage_diff_line(pipe, diff_hdr++)) {
4525                                 pclose(pipe);
4526                                 return FALSE;
4527                         }
4528                 }
4529         }
4531         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4532                 diff_chunk++;
4534         pclose(pipe);
4536         if (diff_chunk != diff_end)
4537                 return FALSE;
4539         return TRUE;
4542 static void
4543 stage_update(struct view *view, struct line *line)
4545         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED &&
4546             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4547                 if (!stage_update_chunk(view, line)) {
4548                         report("Failed to apply chunk");
4549                         return;
4550                 }
4552         } else if (!status_update_file(&stage_status, stage_line_type)) {
4553                 report("Failed to update file");
4554                 return;
4555         }
4557         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4559         view = VIEW(REQ_VIEW_STATUS);
4560         if (view_is_displayed(view))
4561                 status_enter(view, &view->line[view->lineno]);
4564 static enum request
4565 stage_request(struct view *view, enum request request, struct line *line)
4567         switch (request) {
4568         case REQ_STATUS_UPDATE:
4569                 stage_update(view, line);
4570                 break;
4572         case REQ_EDIT:
4573                 if (!stage_status.new.name[0])
4574                         return request;
4576                 open_editor(stage_status.status != '?', stage_status.new.name);
4577                 break;
4579         case REQ_VIEW_BLAME:
4580                 if (stage_status.new.name[0]) {
4581                         string_copy(opt_file, stage_status.new.name);
4582                         opt_ref[0] = 0;
4583                 }
4584                 return request;
4586         case REQ_ENTER:
4587                 pager_request(view, request, line);
4588                 break;
4590         default:
4591                 return request;
4592         }
4594         return REQ_NONE;
4597 static struct view_ops stage_ops = {
4598         "line",
4599         NULL,
4600         pager_read,
4601         pager_draw,
4602         stage_request,
4603         pager_grep,
4604         pager_select,
4605 };
4608 /*
4609  * Revision graph
4610  */
4612 struct commit {
4613         char id[SIZEOF_REV];            /* SHA1 ID. */
4614         char title[128];                /* First line of the commit message. */
4615         char author[75];                /* Author of the commit. */
4616         struct tm time;                 /* Date from the author ident. */
4617         struct ref **refs;              /* Repository references. */
4618         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4619         size_t graph_size;              /* The width of the graph array. */
4620         bool has_parents;               /* Rewritten --parents seen. */
4621 };
4623 /* Size of rev graph with no  "padding" columns */
4624 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4626 struct rev_graph {
4627         struct rev_graph *prev, *next, *parents;
4628         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4629         size_t size;
4630         struct commit *commit;
4631         size_t pos;
4632         unsigned int boundary:1;
4633 };
4635 /* Parents of the commit being visualized. */
4636 static struct rev_graph graph_parents[4];
4638 /* The current stack of revisions on the graph. */
4639 static struct rev_graph graph_stacks[4] = {
4640         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4641         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4642         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4643         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4644 };
4646 static inline bool
4647 graph_parent_is_merge(struct rev_graph *graph)
4649         return graph->parents->size > 1;
4652 static inline void
4653 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4655         struct commit *commit = graph->commit;
4657         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4658                 commit->graph[commit->graph_size++] = symbol;
4661 static void
4662 done_rev_graph(struct rev_graph *graph)
4664         if (graph_parent_is_merge(graph) &&
4665             graph->pos < graph->size - 1 &&
4666             graph->next->size == graph->size + graph->parents->size - 1) {
4667                 size_t i = graph->pos + graph->parents->size - 1;
4669                 graph->commit->graph_size = i * 2;
4670                 while (i < graph->next->size - 1) {
4671                         append_to_rev_graph(graph, ' ');
4672                         append_to_rev_graph(graph, '\\');
4673                         i++;
4674                 }
4675         }
4677         graph->size = graph->pos = 0;
4678         graph->commit = NULL;
4679         memset(graph->parents, 0, sizeof(*graph->parents));
4682 static void
4683 push_rev_graph(struct rev_graph *graph, char *parent)
4685         int i;
4687         /* "Collapse" duplicate parents lines.
4688          *
4689          * FIXME: This needs to also update update the drawn graph but
4690          * for now it just serves as a method for pruning graph lines. */
4691         for (i = 0; i < graph->size; i++)
4692                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4693                         return;
4695         if (graph->size < SIZEOF_REVITEMS) {
4696                 string_copy_rev(graph->rev[graph->size++], parent);
4697         }
4700 static chtype
4701 get_rev_graph_symbol(struct rev_graph *graph)
4703         chtype symbol;
4705         if (graph->boundary)
4706                 symbol = REVGRAPH_BOUND;
4707         else if (graph->parents->size == 0)
4708                 symbol = REVGRAPH_INIT;
4709         else if (graph_parent_is_merge(graph))
4710                 symbol = REVGRAPH_MERGE;
4711         else if (graph->pos >= graph->size)
4712                 symbol = REVGRAPH_BRANCH;
4713         else
4714                 symbol = REVGRAPH_COMMIT;
4716         return symbol;
4719 static void
4720 draw_rev_graph(struct rev_graph *graph)
4722         struct rev_filler {
4723                 chtype separator, line;
4724         };
4725         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4726         static struct rev_filler fillers[] = {
4727                 { ' ',  REVGRAPH_LINE },
4728                 { '`',  '.' },
4729                 { '\'', ' ' },
4730                 { '/',  ' ' },
4731         };
4732         chtype symbol = get_rev_graph_symbol(graph);
4733         struct rev_filler *filler;
4734         size_t i;
4736         filler = &fillers[DEFAULT];
4738         for (i = 0; i < graph->pos; i++) {
4739                 append_to_rev_graph(graph, filler->line);
4740                 if (graph_parent_is_merge(graph->prev) &&
4741                     graph->prev->pos == i)
4742                         filler = &fillers[RSHARP];
4744                 append_to_rev_graph(graph, filler->separator);
4745         }
4747         /* Place the symbol for this revision. */
4748         append_to_rev_graph(graph, symbol);
4750         if (graph->prev->size > graph->size)
4751                 filler = &fillers[RDIAG];
4752         else
4753                 filler = &fillers[DEFAULT];
4755         i++;
4757         for (; i < graph->size; i++) {
4758                 append_to_rev_graph(graph, filler->separator);
4759                 append_to_rev_graph(graph, filler->line);
4760                 if (graph_parent_is_merge(graph->prev) &&
4761                     i < graph->prev->pos + graph->parents->size)
4762                         filler = &fillers[RSHARP];
4763                 if (graph->prev->size > graph->size)
4764                         filler = &fillers[LDIAG];
4765         }
4767         if (graph->prev->size > graph->size) {
4768                 append_to_rev_graph(graph, filler->separator);
4769                 if (filler->line != ' ')
4770                         append_to_rev_graph(graph, filler->line);
4771         }
4774 /* Prepare the next rev graph */
4775 static void
4776 prepare_rev_graph(struct rev_graph *graph)
4778         size_t i;
4780         /* First, traverse all lines of revisions up to the active one. */
4781         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4782                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4783                         break;
4785                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4786         }
4788         /* Interleave the new revision parent(s). */
4789         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4790                 push_rev_graph(graph->next, graph->parents->rev[i]);
4792         /* Lastly, put any remaining revisions. */
4793         for (i = graph->pos + 1; i < graph->size; i++)
4794                 push_rev_graph(graph->next, graph->rev[i]);
4797 static void
4798 update_rev_graph(struct rev_graph *graph)
4800         /* If this is the finalizing update ... */
4801         if (graph->commit)
4802                 prepare_rev_graph(graph);
4804         /* Graph visualization needs a one rev look-ahead,
4805          * so the first update doesn't visualize anything. */
4806         if (!graph->prev->commit)
4807                 return;
4809         draw_rev_graph(graph->prev);
4810         done_rev_graph(graph->prev->prev);
4814 /*
4815  * Main view backend
4816  */
4818 static bool
4819 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4821         char buf[DATE_COLS + 1];
4822         struct commit *commit = line->data;
4823         enum line_type type;
4824         int col = 0;
4825         size_t timelen;
4826         int space;
4828         if (!*commit->author)
4829                 return FALSE;
4831         space = view->width;
4832         wmove(view->win, lineno, col);
4834         if (selected) {
4835                 type = LINE_CURSOR;
4836                 wattrset(view->win, get_line_attr(type));
4837                 wchgat(view->win, -1, 0, type, NULL);
4838         } else {
4839                 type = LINE_MAIN_COMMIT;
4840                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4841         }
4843         if (opt_date) {
4844                 int n;
4846                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4847                 n = draw_text(view, buf, view->width - col, FALSE, selected);
4848                 draw_text(view, " ", view->width - col - n, FALSE, selected);
4850                 col += DATE_COLS;
4851                 wmove(view->win, lineno, col);
4852                 if (col >= view->width)
4853                         return TRUE;
4854         }
4855         if (type != LINE_CURSOR)
4856                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4858         if (opt_author) {
4859                 int max_len;
4861                 max_len = view->width - col;
4862                 if (max_len > AUTHOR_COLS - 1)
4863                         max_len = AUTHOR_COLS - 1;
4864                 draw_text(view, commit->author, max_len, TRUE, selected);
4865                 col += AUTHOR_COLS;
4866                 if (col >= view->width)
4867                         return TRUE;
4868         }
4870         if (opt_rev_graph && commit->graph_size) {
4871                 size_t graph_size = view->width - col;
4872                 size_t i;
4874                 if (type != LINE_CURSOR)
4875                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4876                 wmove(view->win, lineno, col);
4877                 if (graph_size > commit->graph_size)
4878                         graph_size = commit->graph_size;
4879                 /* Using waddch() instead of waddnstr() ensures that
4880                  * they'll be rendered correctly for the cursor line. */
4881                 for (i = 0; i < graph_size; i++)
4882                         waddch(view->win, commit->graph[i]);
4884                 col += commit->graph_size + 1;
4885                 if (col >= view->width)
4886                         return TRUE;
4887                 waddch(view->win, ' ');
4888         }
4889         if (type != LINE_CURSOR)
4890                 wattrset(view->win, A_NORMAL);
4892         wmove(view->win, lineno, col);
4894         if (opt_show_refs && commit->refs) {
4895                 size_t i = 0;
4897                 do {
4898                         if (type == LINE_CURSOR)
4899                                 ;
4900                         else if (commit->refs[i]->head)
4901                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4902                         else if (commit->refs[i]->ltag)
4903                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4904                         else if (commit->refs[i]->tag)
4905                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4906                         else if (commit->refs[i]->tracked)
4907                                 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
4908                         else if (commit->refs[i]->remote)
4909                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4910                         else
4911                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4913                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4914                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4915                                          TRUE, selected);
4916                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4917                         if (type != LINE_CURSOR)
4918                                 wattrset(view->win, A_NORMAL);
4919                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4920                         if (col >= view->width)
4921                                 return TRUE;
4922                 } while (commit->refs[i++]->next);
4923         }
4925         if (type != LINE_CURSOR)
4926                 wattrset(view->win, get_line_attr(type));
4928         draw_text(view, commit->title, view->width - col, TRUE, selected);
4929         return TRUE;
4932 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4933 static bool
4934 main_read(struct view *view, char *line)
4936         static struct rev_graph *graph = graph_stacks;
4937         enum line_type type;
4938         struct commit *commit;
4940         if (!line) {
4941                 update_rev_graph(graph);
4942                 return TRUE;
4943         }
4945         type = get_line_type(line);
4946         if (type == LINE_COMMIT) {
4947                 commit = calloc(1, sizeof(struct commit));
4948                 if (!commit)
4949                         return FALSE;
4951                 line += STRING_SIZE("commit ");
4952                 if (*line == '-') {
4953                         graph->boundary = 1;
4954                         line++;
4955                 }
4957                 string_copy_rev(commit->id, line);
4958                 commit->refs = get_refs(commit->id);
4959                 graph->commit = commit;
4960                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4962                 while ((line = strchr(line, ' '))) {
4963                         line++;
4964                         push_rev_graph(graph->parents, line);
4965                         commit->has_parents = TRUE;
4966                 }
4967                 return TRUE;
4968         }
4970         if (!view->lines)
4971                 return TRUE;
4972         commit = view->line[view->lines - 1].data;
4974         switch (type) {
4975         case LINE_PARENT:
4976                 if (commit->has_parents)
4977                         break;
4978                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4979                 break;
4981         case LINE_AUTHOR:
4982         {
4983                 /* Parse author lines where the name may be empty:
4984                  *      author  <email@address.tld> 1138474660 +0100
4985                  */
4986                 char *ident = line + STRING_SIZE("author ");
4987                 char *nameend = strchr(ident, '<');
4988                 char *emailend = strchr(ident, '>');
4990                 if (!nameend || !emailend)
4991                         break;
4993                 update_rev_graph(graph);
4994                 graph = graph->next;
4996                 *nameend = *emailend = 0;
4997                 ident = chomp_string(ident);
4998                 if (!*ident) {
4999                         ident = chomp_string(nameend + 1);
5000                         if (!*ident)
5001                                 ident = "Unknown";
5002                 }
5004                 string_ncopy(commit->author, ident, strlen(ident));
5006                 /* Parse epoch and timezone */
5007                 if (emailend[1] == ' ') {
5008                         char *secs = emailend + 2;
5009                         char *zone = strchr(secs, ' ');
5010                         time_t time = (time_t) atol(secs);
5012                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5013                                 long tz;
5015                                 zone++;
5016                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5017                                 tz += ('0' - zone[2]) * 60 * 60;
5018                                 tz += ('0' - zone[3]) * 60;
5019                                 tz += ('0' - zone[4]) * 60;
5021                                 if (zone[0] == '-')
5022                                         tz = -tz;
5024                                 time -= tz;
5025                         }
5027                         gmtime_r(&time, &commit->time);
5028                 }
5029                 break;
5030         }
5031         default:
5032                 /* Fill in the commit title if it has not already been set. */
5033                 if (commit->title[0])
5034                         break;
5036                 /* Require titles to start with a non-space character at the
5037                  * offset used by git log. */
5038                 if (strncmp(line, "    ", 4))
5039                         break;
5040                 line += 4;
5041                 /* Well, if the title starts with a whitespace character,
5042                  * try to be forgiving.  Otherwise we end up with no title. */
5043                 while (isspace(*line))
5044                         line++;
5045                 if (*line == '\0')
5046                         break;
5047                 /* FIXME: More graceful handling of titles; append "..." to
5048                  * shortened titles, etc. */
5050                 string_ncopy(commit->title, line, strlen(line));
5051         }
5053         return TRUE;
5056 static enum request
5057 main_request(struct view *view, enum request request, struct line *line)
5059         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5061         if (request == REQ_ENTER)
5062                 open_view(view, REQ_VIEW_DIFF, flags);
5063         else
5064                 return request;
5066         return REQ_NONE;
5069 static bool
5070 main_grep(struct view *view, struct line *line)
5072         struct commit *commit = line->data;
5073         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5074         char buf[DATE_COLS + 1];
5075         regmatch_t pmatch;
5077         for (state = S_TITLE; state < S_END; state++) {
5078                 char *text;
5080                 switch (state) {
5081                 case S_TITLE:   text = commit->title;   break;
5082                 case S_AUTHOR:  text = commit->author;  break;
5083                 case S_DATE:
5084                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5085                                 continue;
5086                         text = buf;
5087                         break;
5089                 default:
5090                         return FALSE;
5091                 }
5093                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5094                         return TRUE;
5095         }
5097         return FALSE;
5100 static void
5101 main_select(struct view *view, struct line *line)
5103         struct commit *commit = line->data;
5105         string_copy_rev(view->ref, commit->id);
5106         string_copy_rev(ref_commit, view->ref);
5109 static struct view_ops main_ops = {
5110         "commit",
5111         NULL,
5112         main_read,
5113         main_draw,
5114         main_request,
5115         main_grep,
5116         main_select,
5117 };
5120 /*
5121  * Unicode / UTF-8 handling
5122  *
5123  * NOTE: Much of the following code for dealing with unicode is derived from
5124  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5125  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5126  */
5128 /* I've (over)annotated a lot of code snippets because I am not entirely
5129  * confident that the approach taken by this small UTF-8 interface is correct.
5130  * --jonas */
5132 static inline int
5133 unicode_width(unsigned long c)
5135         if (c >= 0x1100 &&
5136            (c <= 0x115f                         /* Hangul Jamo */
5137             || c == 0x2329
5138             || c == 0x232a
5139             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5140                                                 /* CJK ... Yi */
5141             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5142             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5143             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5144             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5145             || (c >= 0xffe0  && c <= 0xffe6)
5146             || (c >= 0x20000 && c <= 0x2fffd)
5147             || (c >= 0x30000 && c <= 0x3fffd)))
5148                 return 2;
5150         if (c == '\t')
5151                 return opt_tab_size;
5153         return 1;
5156 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5157  * Illegal bytes are set one. */
5158 static const unsigned char utf8_bytes[256] = {
5159         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,
5160         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,
5161         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,
5162         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,
5163         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,
5164         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,
5165         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,
5166         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,
5167 };
5169 /* Decode UTF-8 multi-byte representation into a unicode character. */
5170 static inline unsigned long
5171 utf8_to_unicode(const char *string, size_t length)
5173         unsigned long unicode;
5175         switch (length) {
5176         case 1:
5177                 unicode  =   string[0];
5178                 break;
5179         case 2:
5180                 unicode  =  (string[0] & 0x1f) << 6;
5181                 unicode +=  (string[1] & 0x3f);
5182                 break;
5183         case 3:
5184                 unicode  =  (string[0] & 0x0f) << 12;
5185                 unicode += ((string[1] & 0x3f) << 6);
5186                 unicode +=  (string[2] & 0x3f);
5187                 break;
5188         case 4:
5189                 unicode  =  (string[0] & 0x0f) << 18;
5190                 unicode += ((string[1] & 0x3f) << 12);
5191                 unicode += ((string[2] & 0x3f) << 6);
5192                 unicode +=  (string[3] & 0x3f);
5193                 break;
5194         case 5:
5195                 unicode  =  (string[0] & 0x0f) << 24;
5196                 unicode += ((string[1] & 0x3f) << 18);
5197                 unicode += ((string[2] & 0x3f) << 12);
5198                 unicode += ((string[3] & 0x3f) << 6);
5199                 unicode +=  (string[4] & 0x3f);
5200                 break;
5201         case 6:
5202                 unicode  =  (string[0] & 0x01) << 30;
5203                 unicode += ((string[1] & 0x3f) << 24);
5204                 unicode += ((string[2] & 0x3f) << 18);
5205                 unicode += ((string[3] & 0x3f) << 12);
5206                 unicode += ((string[4] & 0x3f) << 6);
5207                 unicode +=  (string[5] & 0x3f);
5208                 break;
5209         default:
5210                 die("Invalid unicode length");
5211         }
5213         /* Invalid characters could return the special 0xfffd value but NUL
5214          * should be just as good. */
5215         return unicode > 0xffff ? 0 : unicode;
5218 /* Calculates how much of string can be shown within the given maximum width
5219  * and sets trimmed parameter to non-zero value if all of string could not be
5220  * shown. If the reserve flag is TRUE, it will reserve at least one
5221  * trailing character, which can be useful when drawing a delimiter.
5222  *
5223  * Returns the number of bytes to output from string to satisfy max_width. */
5224 static size_t
5225 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5227         const char *start = string;
5228         const char *end = strchr(string, '\0');
5229         unsigned char last_bytes = 0;
5230         size_t width = 0;
5232         *trimmed = 0;
5234         while (string < end) {
5235                 int c = *(unsigned char *) string;
5236                 unsigned char bytes = utf8_bytes[c];
5237                 size_t ucwidth;
5238                 unsigned long unicode;
5240                 if (string + bytes > end)
5241                         break;
5243                 /* Change representation to figure out whether
5244                  * it is a single- or double-width character. */
5246                 unicode = utf8_to_unicode(string, bytes);
5247                 /* FIXME: Graceful handling of invalid unicode character. */
5248                 if (!unicode)
5249                         break;
5251                 ucwidth = unicode_width(unicode);
5252                 width  += ucwidth;
5253                 if (width > max_width) {
5254                         *trimmed = 1;
5255                         if (reserve && width - ucwidth == max_width) {
5256                                 string -= last_bytes;
5257                         }
5258                         break;
5259                 }
5261                 string  += bytes;
5262                 last_bytes = bytes;
5263         }
5265         return string - start;
5269 /*
5270  * Status management
5271  */
5273 /* Whether or not the curses interface has been initialized. */
5274 static bool cursed = FALSE;
5276 /* The status window is used for polling keystrokes. */
5277 static WINDOW *status_win;
5279 static bool status_empty = TRUE;
5281 /* Update status and title window. */
5282 static void
5283 report(const char *msg, ...)
5285         struct view *view = display[current_view];
5287         if (input_mode)
5288                 return;
5290         if (!view) {
5291                 char buf[SIZEOF_STR];
5292                 va_list args;
5294                 va_start(args, msg);
5295                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5296                         buf[sizeof(buf) - 1] = 0;
5297                         buf[sizeof(buf) - 2] = '.';
5298                         buf[sizeof(buf) - 3] = '.';
5299                         buf[sizeof(buf) - 4] = '.';
5300                 }
5301                 va_end(args);
5302                 die("%s", buf);
5303         }
5305         if (!status_empty || *msg) {
5306                 va_list args;
5308                 va_start(args, msg);
5310                 wmove(status_win, 0, 0);
5311                 if (*msg) {
5312                         vwprintw(status_win, msg, args);
5313                         status_empty = FALSE;
5314                 } else {
5315                         status_empty = TRUE;
5316                 }
5317                 wclrtoeol(status_win);
5318                 wrefresh(status_win);
5320                 va_end(args);
5321         }
5323         update_view_title(view);
5324         update_display_cursor(view);
5327 /* Controls when nodelay should be in effect when polling user input. */
5328 static void
5329 set_nonblocking_input(bool loading)
5331         static unsigned int loading_views;
5333         if ((loading == FALSE && loading_views-- == 1) ||
5334             (loading == TRUE  && loading_views++ == 0))
5335                 nodelay(status_win, loading);
5338 static void
5339 init_display(void)
5341         int x, y;
5343         /* Initialize the curses library */
5344         if (isatty(STDIN_FILENO)) {
5345                 cursed = !!initscr();
5346         } else {
5347                 /* Leave stdin and stdout alone when acting as a pager. */
5348                 FILE *io = fopen("/dev/tty", "r+");
5350                 if (!io)
5351                         die("Failed to open /dev/tty");
5352                 cursed = !!newterm(NULL, io, io);
5353         }
5355         if (!cursed)
5356                 die("Failed to initialize curses");
5358         nonl();         /* Tell curses not to do NL->CR/NL on output */
5359         cbreak();       /* Take input chars one at a time, no wait for \n */
5360         noecho();       /* Don't echo input */
5361         leaveok(stdscr, TRUE);
5363         if (has_colors())
5364                 init_colors();
5366         getmaxyx(stdscr, y, x);
5367         status_win = newwin(1, 0, y - 1, 0);
5368         if (!status_win)
5369                 die("Failed to create status window");
5371         /* Enable keyboard mapping */
5372         keypad(status_win, TRUE);
5373         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5376 static char *
5377 read_prompt(const char *prompt)
5379         enum { READING, STOP, CANCEL } status = READING;
5380         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5381         int pos = 0;
5383         while (status == READING) {
5384                 struct view *view;
5385                 int i, key;
5387                 input_mode = TRUE;
5389                 foreach_view (view, i)
5390                         update_view(view);
5392                 input_mode = FALSE;
5394                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5395                 wclrtoeol(status_win);
5397                 /* Refresh, accept single keystroke of input */
5398                 key = wgetch(status_win);
5399                 switch (key) {
5400                 case KEY_RETURN:
5401                 case KEY_ENTER:
5402                 case '\n':
5403                         status = pos ? STOP : CANCEL;
5404                         break;
5406                 case KEY_BACKSPACE:
5407                         if (pos > 0)
5408                                 pos--;
5409                         else
5410                                 status = CANCEL;
5411                         break;
5413                 case KEY_ESC:
5414                         status = CANCEL;
5415                         break;
5417                 case ERR:
5418                         break;
5420                 default:
5421                         if (pos >= sizeof(buf)) {
5422                                 report("Input string too long");
5423                                 return NULL;
5424                         }
5426                         if (isprint(key))
5427                                 buf[pos++] = (char) key;
5428                 }
5429         }
5431         /* Clear the status window */
5432         status_empty = FALSE;
5433         report("");
5435         if (status == CANCEL)
5436                 return NULL;
5438         buf[pos++] = 0;
5440         return buf;
5443 /*
5444  * Repository references
5445  */
5447 static struct ref *refs = NULL;
5448 static size_t refs_alloc = 0;
5449 static size_t refs_size = 0;
5451 /* Id <-> ref store */
5452 static struct ref ***id_refs = NULL;
5453 static size_t id_refs_alloc = 0;
5454 static size_t id_refs_size = 0;
5456 static struct ref **
5457 get_refs(char *id)
5459         struct ref ***tmp_id_refs;
5460         struct ref **ref_list = NULL;
5461         size_t ref_list_alloc = 0;
5462         size_t ref_list_size = 0;
5463         size_t i;
5465         for (i = 0; i < id_refs_size; i++)
5466                 if (!strcmp(id, id_refs[i][0]->id))
5467                         return id_refs[i];
5469         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5470                                     sizeof(*id_refs));
5471         if (!tmp_id_refs)
5472                 return NULL;
5474         id_refs = tmp_id_refs;
5476         for (i = 0; i < refs_size; i++) {
5477                 struct ref **tmp;
5479                 if (strcmp(id, refs[i].id))
5480                         continue;
5482                 tmp = realloc_items(ref_list, &ref_list_alloc,
5483                                     ref_list_size + 1, sizeof(*ref_list));
5484                 if (!tmp) {
5485                         if (ref_list)
5486                                 free(ref_list);
5487                         return NULL;
5488                 }
5490                 ref_list = tmp;
5491                 if (ref_list_size > 0)
5492                         ref_list[ref_list_size - 1]->next = 1;
5493                 ref_list[ref_list_size] = &refs[i];
5495                 /* XXX: The properties of the commit chains ensures that we can
5496                  * safely modify the shared ref. The repo references will
5497                  * always be similar for the same id. */
5498                 ref_list[ref_list_size]->next = 0;
5499                 ref_list_size++;
5500         }
5502         if (ref_list)
5503                 id_refs[id_refs_size++] = ref_list;
5505         return ref_list;
5508 static int
5509 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5511         struct ref *ref;
5512         bool tag = FALSE;
5513         bool ltag = FALSE;
5514         bool remote = FALSE;
5515         bool tracked = FALSE;
5516         bool check_replace = FALSE;
5517         bool head = FALSE;
5519         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5520                 if (!strcmp(name + namelen - 3, "^{}")) {
5521                         namelen -= 3;
5522                         name[namelen] = 0;
5523                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5524                                 check_replace = TRUE;
5525                 } else {
5526                         ltag = TRUE;
5527                 }
5529                 tag = TRUE;
5530                 namelen -= STRING_SIZE("refs/tags/");
5531                 name    += STRING_SIZE("refs/tags/");
5533         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5534                 remote = TRUE;
5535                 namelen -= STRING_SIZE("refs/remotes/");
5536                 name    += STRING_SIZE("refs/remotes/");
5537                 tracked  = !strcmp(opt_remote, name);
5539         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5540                 namelen -= STRING_SIZE("refs/heads/");
5541                 name    += STRING_SIZE("refs/heads/");
5542                 head     = !strncmp(opt_head, name, namelen);
5544         } else if (!strcmp(name, "HEAD")) {
5545                 opt_no_head = FALSE;
5546                 return OK;
5547         }
5549         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5550                 /* it's an annotated tag, replace the previous sha1 with the
5551                  * resolved commit id; relies on the fact git-ls-remote lists
5552                  * the commit id of an annotated tag right beofre the commit id
5553                  * it points to. */
5554                 refs[refs_size - 1].ltag = ltag;
5555                 string_copy_rev(refs[refs_size - 1].id, id);
5557                 return OK;
5558         }
5559         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5560         if (!refs)
5561                 return ERR;
5563         ref = &refs[refs_size++];
5564         ref->name = malloc(namelen + 1);
5565         if (!ref->name)
5566                 return ERR;
5568         strncpy(ref->name, name, namelen);
5569         ref->name[namelen] = 0;
5570         ref->head = head;
5571         ref->tag = tag;
5572         ref->ltag = ltag;
5573         ref->remote = remote;
5574         ref->tracked = tracked;
5575         string_copy_rev(ref->id, id);
5577         return OK;
5580 static int
5581 load_refs(void)
5583         const char *cmd_env = getenv("TIG_LS_REMOTE");
5584         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5586         return read_properties(popen(cmd, "r"), "\t", read_ref);
5589 static int
5590 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5592         if (!strcmp(name, "i18n.commitencoding"))
5593                 string_ncopy(opt_encoding, value, valuelen);
5595         if (!strcmp(name, "core.editor"))
5596                 string_ncopy(opt_editor, value, valuelen);
5598         /* branch.<head>.remote */
5599         if (*opt_head &&
5600             !strncmp(name, "branch.", 7) &&
5601             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5602             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5603                 string_ncopy(opt_remote, value, valuelen);
5605         if (*opt_head && *opt_remote &&
5606             !strncmp(name, "branch.", 7) &&
5607             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5608             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5609                 size_t from = strlen(opt_remote);
5611                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5612                         value += STRING_SIZE("refs/heads/");
5613                         valuelen -= STRING_SIZE("refs/heads/");
5614                 }
5616                 if (!string_format_from(opt_remote, &from, "/%s", value))
5617                         opt_remote[0] = 0;
5618         }
5620         return OK;
5623 static int
5624 load_git_config(void)
5626         return read_properties(popen(GIT_CONFIG " --list", "r"),
5627                                "=", read_repo_config_option);
5630 static int
5631 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5633         if (!opt_git_dir[0]) {
5634                 string_ncopy(opt_git_dir, name, namelen);
5636         } else if (opt_is_inside_work_tree == -1) {
5637                 /* This can be 3 different values depending on the
5638                  * version of git being used. If git-rev-parse does not
5639                  * understand --is-inside-work-tree it will simply echo
5640                  * the option else either "true" or "false" is printed.
5641                  * Default to true for the unknown case. */
5642                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5644         } else if (opt_cdup[0] == ' ') {
5645                 string_ncopy(opt_cdup, name, namelen);
5646         } else {
5647                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5648                         namelen -= STRING_SIZE("refs/heads/");
5649                         name    += STRING_SIZE("refs/heads/");
5650                         string_ncopy(opt_head, name, namelen);
5651                 }
5652         }
5654         return OK;
5657 static int
5658 load_repo_info(void)
5660         int result;
5661         FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
5662                            " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
5664         /* XXX: The line outputted by "--show-cdup" can be empty so
5665          * initialize it to something invalid to make it possible to
5666          * detect whether it has been set or not. */
5667         opt_cdup[0] = ' ';
5669         result = read_properties(pipe, "=", read_repo_info);
5670         if (opt_cdup[0] == ' ')
5671                 opt_cdup[0] = 0;
5673         return result;
5676 static int
5677 read_properties(FILE *pipe, const char *separators,
5678                 int (*read_property)(char *, size_t, char *, size_t))
5680         char buffer[BUFSIZ];
5681         char *name;
5682         int state = OK;
5684         if (!pipe)
5685                 return ERR;
5687         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5688                 char *value;
5689                 size_t namelen;
5690                 size_t valuelen;
5692                 name = chomp_string(name);
5693                 namelen = strcspn(name, separators);
5695                 if (name[namelen]) {
5696                         name[namelen] = 0;
5697                         value = chomp_string(name + namelen + 1);
5698                         valuelen = strlen(value);
5700                 } else {
5701                         value = "";
5702                         valuelen = 0;
5703                 }
5705                 state = read_property(name, namelen, value, valuelen);
5706         }
5708         if (state != ERR && ferror(pipe))
5709                 state = ERR;
5711         pclose(pipe);
5713         return state;
5717 /*
5718  * Main
5719  */
5721 static void __NORETURN
5722 quit(int sig)
5724         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5725         if (cursed)
5726                 endwin();
5727         exit(0);
5730 static void __NORETURN
5731 die(const char *err, ...)
5733         va_list args;
5735         endwin();
5737         va_start(args, err);
5738         fputs("tig: ", stderr);
5739         vfprintf(stderr, err, args);
5740         fputs("\n", stderr);
5741         va_end(args);
5743         exit(1);
5746 static void
5747 warn(const char *msg, ...)
5749         va_list args;
5751         va_start(args, msg);
5752         fputs("tig warning: ", stderr);
5753         vfprintf(stderr, msg, args);
5754         fputs("\n", stderr);
5755         va_end(args);
5758 int
5759 main(int argc, char *argv[])
5761         struct view *view;
5762         enum request request;
5763         size_t i;
5765         signal(SIGINT, quit);
5767         if (setlocale(LC_ALL, "")) {
5768                 char *codeset = nl_langinfo(CODESET);
5770                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5771         }
5773         if (load_repo_info() == ERR)
5774                 die("Failed to load repo info.");
5776         if (load_options() == ERR)
5777                 die("Failed to load user config.");
5779         if (load_git_config() == ERR)
5780                 die("Failed to load repo config.");
5782         if (!parse_options(argc, argv))
5783                 return 0;
5785         /* Require a git repository unless when running in pager mode. */
5786         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5787                 die("Not a git repository");
5789         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5790                 opt_utf8 = FALSE;
5792         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5793                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5794                 if (opt_iconv == ICONV_NONE)
5795                         die("Failed to initialize character set conversion");
5796         }
5798         if (*opt_git_dir && load_refs() == ERR)
5799                 die("Failed to load refs.");
5801         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5802                 view->cmd_env = getenv(view->cmd_env);
5804         request = opt_request;
5806         init_display();
5808         while (view_driver(display[current_view], request)) {
5809                 int key;
5810                 int i;
5812                 foreach_view (view, i)
5813                         update_view(view);
5815                 /* Refresh, accept single keystroke of input */
5816                 key = wgetch(status_win);
5818                 /* wgetch() with nodelay() enabled returns ERR when there's no
5819                  * input. */
5820                 if (key == ERR) {
5821                         request = REQ_NONE;
5822                         continue;
5823                 }
5825                 request = get_keybinding(display[current_view]->keymap, key);
5827                 /* Some low-level request handling. This keeps access to
5828                  * status_win restricted. */
5829                 switch (request) {
5830                 case REQ_PROMPT:
5831                 {
5832                         char *cmd = read_prompt(":");
5834                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5835                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5836                                         opt_request = REQ_VIEW_DIFF;
5837                                 } else {
5838                                         opt_request = REQ_VIEW_PAGER;
5839                                 }
5840                                 break;
5841                         }
5843                         request = REQ_NONE;
5844                         break;
5845                 }
5846                 case REQ_SEARCH:
5847                 case REQ_SEARCH_BACK:
5848                 {
5849                         const char *prompt = request == REQ_SEARCH
5850                                            ? "/" : "?";
5851                         char *search = read_prompt(prompt);
5853                         if (search)
5854                                 string_ncopy(opt_search, search, strlen(search));
5855                         else
5856                                 request = REQ_NONE;
5857                         break;
5858                 }
5859                 case REQ_SCREEN_RESIZE:
5860                 {
5861                         int height, width;
5863                         getmaxyx(stdscr, height, width);
5865                         /* Resize the status view and let the view driver take
5866                          * care of resizing the displayed views. */
5867                         wresize(status_win, 1, width);
5868                         mvwin(status_win, height - 1, 0);
5869                         wrefresh(status_win);
5870                         break;
5871                 }
5872                 default:
5873                         break;
5874                 }
5875         }
5877         quit(0);
5879         return 0;