Code

551100f6031e2f7d1d01827ab4827dfc8f4001ff
[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(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
590 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
591 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
592 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
593 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
594 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
595 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
596 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
597 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
598 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
599 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
600 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
601 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
602 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
604 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
605 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
606 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
607 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
610 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
612 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
613 LINE(BLAME_LINENO, "",                  COLOR_CYAN,     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         col = draw_text(view, number, max_number, showtrimmed, selected);
1511         if (col < max) {
1512                 if (!selected)
1513                         wattrset(view->win, A_NORMAL);
1514                 waddch(view->win, ACS_VLINE);
1515                 col++;
1516         }
1517         if (col < max) {
1518                 waddch(view->win, ' ');
1519                 col++;
1520         }
1522         return col;
1525 static bool
1526 draw_view_line(struct view *view, unsigned int lineno)
1528         struct line *line;
1529         bool selected = (view->offset + lineno == view->lineno);
1530         bool draw_ok;
1532         assert(view_is_displayed(view));
1534         if (view->offset + lineno >= view->lines)
1535                 return FALSE;
1537         line = &view->line[view->offset + lineno];
1539         if (selected) {
1540                 line->selected = TRUE;
1541                 view->ops->select(view, line);
1542         } else if (line->selected) {
1543                 line->selected = FALSE;
1544                 wmove(view->win, lineno, 0);
1545                 wclrtoeol(view->win);
1546         }
1548         scrollok(view->win, FALSE);
1549         draw_ok = view->ops->draw(view, line, lineno, selected);
1550         scrollok(view->win, TRUE);
1552         return draw_ok;
1555 static void
1556 redraw_view_dirty(struct view *view)
1558         bool dirty = FALSE;
1559         int lineno;
1561         for (lineno = 0; lineno < view->height; lineno++) {
1562                 struct line *line = &view->line[view->offset + lineno];
1564                 if (!line->dirty)
1565                         continue;
1566                 line->dirty = 0;
1567                 dirty = TRUE;
1568                 if (!draw_view_line(view, lineno))
1569                         break;
1570         }
1572         if (!dirty)
1573                 return;
1574         redrawwin(view->win);
1575         if (input_mode)
1576                 wnoutrefresh(view->win);
1577         else
1578                 wrefresh(view->win);
1581 static void
1582 redraw_view_from(struct view *view, int lineno)
1584         assert(0 <= lineno && lineno < view->height);
1586         for (; lineno < view->height; lineno++) {
1587                 if (!draw_view_line(view, lineno))
1588                         break;
1589         }
1591         redrawwin(view->win);
1592         if (input_mode)
1593                 wnoutrefresh(view->win);
1594         else
1595                 wrefresh(view->win);
1598 static void
1599 redraw_view(struct view *view)
1601         wclear(view->win);
1602         redraw_view_from(view, 0);
1606 static void
1607 update_view_title(struct view *view)
1609         char buf[SIZEOF_STR];
1610         char state[SIZEOF_STR];
1611         size_t bufpos = 0, statelen = 0;
1613         assert(view_is_displayed(view));
1615         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1616                 unsigned int view_lines = view->offset + view->height;
1617                 unsigned int lines = view->lines
1618                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1619                                    : 0;
1621                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1622                                    view->ops->type,
1623                                    view->lineno + 1,
1624                                    view->lines,
1625                                    lines);
1627                 if (view->pipe) {
1628                         time_t secs = time(NULL) - view->start_time;
1630                         /* Three git seconds are a long time ... */
1631                         if (secs > 2)
1632                                 string_format_from(state, &statelen, " %lds", secs);
1633                 }
1634         }
1636         string_format_from(buf, &bufpos, "[%s]", view->name);
1637         if (*view->ref && bufpos < view->width) {
1638                 size_t refsize = strlen(view->ref);
1639                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1641                 if (minsize < view->width)
1642                         refsize = view->width - minsize + 7;
1643                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1644         }
1646         if (statelen && bufpos < view->width) {
1647                 string_format_from(buf, &bufpos, " %s", state);
1648         }
1650         if (view == display[current_view])
1651                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1652         else
1653                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1655         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1656         wclrtoeol(view->title);
1657         wmove(view->title, 0, view->width - 1);
1659         if (input_mode)
1660                 wnoutrefresh(view->title);
1661         else
1662                 wrefresh(view->title);
1665 static void
1666 resize_display(void)
1668         int offset, i;
1669         struct view *base = display[0];
1670         struct view *view = display[1] ? display[1] : display[0];
1672         /* Setup window dimensions */
1674         getmaxyx(stdscr, base->height, base->width);
1676         /* Make room for the status window. */
1677         base->height -= 1;
1679         if (view != base) {
1680                 /* Horizontal split. */
1681                 view->width   = base->width;
1682                 view->height  = SCALE_SPLIT_VIEW(base->height);
1683                 base->height -= view->height;
1685                 /* Make room for the title bar. */
1686                 view->height -= 1;
1687         }
1689         /* Make room for the title bar. */
1690         base->height -= 1;
1692         offset = 0;
1694         foreach_displayed_view (view, i) {
1695                 if (!view->win) {
1696                         view->win = newwin(view->height, 0, offset, 0);
1697                         if (!view->win)
1698                                 die("Failed to create %s view", view->name);
1700                         scrollok(view->win, TRUE);
1702                         view->title = newwin(1, 0, offset + view->height, 0);
1703                         if (!view->title)
1704                                 die("Failed to create title window");
1706                 } else {
1707                         wresize(view->win, view->height, view->width);
1708                         mvwin(view->win,   offset, 0);
1709                         mvwin(view->title, offset + view->height, 0);
1710                 }
1712                 offset += view->height + 1;
1713         }
1716 static void
1717 redraw_display(void)
1719         struct view *view;
1720         int i;
1722         foreach_displayed_view (view, i) {
1723                 redraw_view(view);
1724                 update_view_title(view);
1725         }
1728 static void
1729 update_display_cursor(struct view *view)
1731         /* Move the cursor to the right-most column of the cursor line.
1732          *
1733          * XXX: This could turn out to be a bit expensive, but it ensures that
1734          * the cursor does not jump around. */
1735         if (view->lines) {
1736                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1737                 wrefresh(view->win);
1738         }
1741 /*
1742  * Navigation
1743  */
1745 /* Scrolling backend */
1746 static void
1747 do_scroll_view(struct view *view, int lines)
1749         bool redraw_current_line = FALSE;
1751         /* The rendering expects the new offset. */
1752         view->offset += lines;
1754         assert(0 <= view->offset && view->offset < view->lines);
1755         assert(lines);
1757         /* Move current line into the view. */
1758         if (view->lineno < view->offset) {
1759                 view->lineno = view->offset;
1760                 redraw_current_line = TRUE;
1761         } else if (view->lineno >= view->offset + view->height) {
1762                 view->lineno = view->offset + view->height - 1;
1763                 redraw_current_line = TRUE;
1764         }
1766         assert(view->offset <= view->lineno && view->lineno < view->lines);
1768         /* Redraw the whole screen if scrolling is pointless. */
1769         if (view->height < ABS(lines)) {
1770                 redraw_view(view);
1772         } else {
1773                 int line = lines > 0 ? view->height - lines : 0;
1774                 int end = line + ABS(lines);
1776                 wscrl(view->win, lines);
1778                 for (; line < end; line++) {
1779                         if (!draw_view_line(view, line))
1780                                 break;
1781                 }
1783                 if (redraw_current_line)
1784                         draw_view_line(view, view->lineno - view->offset);
1785         }
1787         redrawwin(view->win);
1788         wrefresh(view->win);
1789         report("");
1792 /* Scroll frontend */
1793 static void
1794 scroll_view(struct view *view, enum request request)
1796         int lines = 1;
1798         assert(view_is_displayed(view));
1800         switch (request) {
1801         case REQ_SCROLL_PAGE_DOWN:
1802                 lines = view->height;
1803         case REQ_SCROLL_LINE_DOWN:
1804                 if (view->offset + lines > view->lines)
1805                         lines = view->lines - view->offset;
1807                 if (lines == 0 || view->offset + view->height >= view->lines) {
1808                         report("Cannot scroll beyond the last line");
1809                         return;
1810                 }
1811                 break;
1813         case REQ_SCROLL_PAGE_UP:
1814                 lines = view->height;
1815         case REQ_SCROLL_LINE_UP:
1816                 if (lines > view->offset)
1817                         lines = view->offset;
1819                 if (lines == 0) {
1820                         report("Cannot scroll beyond the first line");
1821                         return;
1822                 }
1824                 lines = -lines;
1825                 break;
1827         default:
1828                 die("request %d not handled in switch", request);
1829         }
1831         do_scroll_view(view, lines);
1834 /* Cursor moving */
1835 static void
1836 move_view(struct view *view, enum request request)
1838         int scroll_steps = 0;
1839         int steps;
1841         switch (request) {
1842         case REQ_MOVE_FIRST_LINE:
1843                 steps = -view->lineno;
1844                 break;
1846         case REQ_MOVE_LAST_LINE:
1847                 steps = view->lines - view->lineno - 1;
1848                 break;
1850         case REQ_MOVE_PAGE_UP:
1851                 steps = view->height > view->lineno
1852                       ? -view->lineno : -view->height;
1853                 break;
1855         case REQ_MOVE_PAGE_DOWN:
1856                 steps = view->lineno + view->height >= view->lines
1857                       ? view->lines - view->lineno - 1 : view->height;
1858                 break;
1860         case REQ_MOVE_UP:
1861                 steps = -1;
1862                 break;
1864         case REQ_MOVE_DOWN:
1865                 steps = 1;
1866                 break;
1868         default:
1869                 die("request %d not handled in switch", request);
1870         }
1872         if (steps <= 0 && view->lineno == 0) {
1873                 report("Cannot move beyond the first line");
1874                 return;
1876         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1877                 report("Cannot move beyond the last line");
1878                 return;
1879         }
1881         /* Move the current line */
1882         view->lineno += steps;
1883         assert(0 <= view->lineno && view->lineno < view->lines);
1885         /* Check whether the view needs to be scrolled */
1886         if (view->lineno < view->offset ||
1887             view->lineno >= view->offset + view->height) {
1888                 scroll_steps = steps;
1889                 if (steps < 0 && -steps > view->offset) {
1890                         scroll_steps = -view->offset;
1892                 } else if (steps > 0) {
1893                         if (view->lineno == view->lines - 1 &&
1894                             view->lines > view->height) {
1895                                 scroll_steps = view->lines - view->offset - 1;
1896                                 if (scroll_steps >= view->height)
1897                                         scroll_steps -= view->height - 1;
1898                         }
1899                 }
1900         }
1902         if (!view_is_displayed(view)) {
1903                 view->offset += scroll_steps;
1904                 assert(0 <= view->offset && view->offset < view->lines);
1905                 view->ops->select(view, &view->line[view->lineno]);
1906                 return;
1907         }
1909         /* Repaint the old "current" line if we be scrolling */
1910         if (ABS(steps) < view->height)
1911                 draw_view_line(view, view->lineno - steps - view->offset);
1913         if (scroll_steps) {
1914                 do_scroll_view(view, scroll_steps);
1915                 return;
1916         }
1918         /* Draw the current line */
1919         draw_view_line(view, view->lineno - view->offset);
1921         redrawwin(view->win);
1922         wrefresh(view->win);
1923         report("");
1927 /*
1928  * Searching
1929  */
1931 static void search_view(struct view *view, enum request request);
1933 static bool
1934 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1936         assert(view_is_displayed(view));
1938         if (!view->ops->grep(view, line))
1939                 return FALSE;
1941         if (lineno - view->offset >= view->height) {
1942                 view->offset = lineno;
1943                 view->lineno = lineno;
1944                 redraw_view(view);
1946         } else {
1947                 unsigned long old_lineno = view->lineno - view->offset;
1949                 view->lineno = lineno;
1950                 draw_view_line(view, old_lineno);
1952                 draw_view_line(view, view->lineno - view->offset);
1953                 redrawwin(view->win);
1954                 wrefresh(view->win);
1955         }
1957         report("Line %ld matches '%s'", lineno + 1, view->grep);
1958         return TRUE;
1961 static void
1962 find_next(struct view *view, enum request request)
1964         unsigned long lineno = view->lineno;
1965         int direction;
1967         if (!*view->grep) {
1968                 if (!*opt_search)
1969                         report("No previous search");
1970                 else
1971                         search_view(view, request);
1972                 return;
1973         }
1975         switch (request) {
1976         case REQ_SEARCH:
1977         case REQ_FIND_NEXT:
1978                 direction = 1;
1979                 break;
1981         case REQ_SEARCH_BACK:
1982         case REQ_FIND_PREV:
1983                 direction = -1;
1984                 break;
1986         default:
1987                 return;
1988         }
1990         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1991                 lineno += direction;
1993         /* Note, lineno is unsigned long so will wrap around in which case it
1994          * will become bigger than view->lines. */
1995         for (; lineno < view->lines; lineno += direction) {
1996                 struct line *line = &view->line[lineno];
1998                 if (find_next_line(view, lineno, line))
1999                         return;
2000         }
2002         report("No match found for '%s'", view->grep);
2005 static void
2006 search_view(struct view *view, enum request request)
2008         int regex_err;
2010         if (view->regex) {
2011                 regfree(view->regex);
2012                 *view->grep = 0;
2013         } else {
2014                 view->regex = calloc(1, sizeof(*view->regex));
2015                 if (!view->regex)
2016                         return;
2017         }
2019         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2020         if (regex_err != 0) {
2021                 char buf[SIZEOF_STR] = "unknown error";
2023                 regerror(regex_err, view->regex, buf, sizeof(buf));
2024                 report("Search failed: %s", buf);
2025                 return;
2026         }
2028         string_copy(view->grep, opt_search);
2030         find_next(view, request);
2033 /*
2034  * Incremental updating
2035  */
2037 static void
2038 end_update(struct view *view)
2040         if (!view->pipe)
2041                 return;
2042         set_nonblocking_input(FALSE);
2043         if (view->pipe == stdin)
2044                 fclose(view->pipe);
2045         else
2046                 pclose(view->pipe);
2047         view->pipe = NULL;
2050 static bool
2051 begin_update(struct view *view)
2053         if (view->pipe)
2054                 end_update(view);
2056         if (opt_cmd[0]) {
2057                 string_copy(view->cmd, opt_cmd);
2058                 opt_cmd[0] = 0;
2059                 /* When running random commands, initially show the
2060                  * command in the title. However, it maybe later be
2061                  * overwritten if a commit line is selected. */
2062                 if (view == VIEW(REQ_VIEW_PAGER))
2063                         string_copy(view->ref, view->cmd);
2064                 else
2065                         view->ref[0] = 0;
2067         } else if (view == VIEW(REQ_VIEW_TREE)) {
2068                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2069                 char path[SIZEOF_STR];
2071                 if (strcmp(view->vid, view->id))
2072                         opt_path[0] = path[0] = 0;
2073                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2074                         return FALSE;
2076                 if (!string_format(view->cmd, format, view->id, path))
2077                         return FALSE;
2079         } else {
2080                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2081                 const char *id = view->id;
2083                 if (!string_format(view->cmd, format, id, id, id, id, id))
2084                         return FALSE;
2086                 /* Put the current ref_* value to the view title ref
2087                  * member. This is needed by the blob view. Most other
2088                  * views sets it automatically after loading because the
2089                  * first line is a commit line. */
2090                 string_copy_rev(view->ref, view->id);
2091         }
2093         /* Special case for the pager view. */
2094         if (opt_pipe) {
2095                 view->pipe = opt_pipe;
2096                 opt_pipe = NULL;
2097         } else {
2098                 view->pipe = popen(view->cmd, "r");
2099         }
2101         if (!view->pipe)
2102                 return FALSE;
2104         set_nonblocking_input(TRUE);
2106         view->offset = 0;
2107         view->lines  = 0;
2108         view->lineno = 0;
2109         string_copy_rev(view->vid, view->id);
2111         if (view->line) {
2112                 int i;
2114                 for (i = 0; i < view->lines; i++)
2115                         if (view->line[i].data)
2116                                 free(view->line[i].data);
2118                 free(view->line);
2119                 view->line = NULL;
2120         }
2122         view->start_time = time(NULL);
2124         return TRUE;
2127 #define ITEM_CHUNK_SIZE 256
2128 static void *
2129 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2131         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2132         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2134         if (mem == NULL || num_chunks != num_chunks_new) {
2135                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2136                 mem = realloc(mem, *size * item_size);
2137         }
2139         return mem;
2142 static struct line *
2143 realloc_lines(struct view *view, size_t line_size)
2145         size_t alloc = view->line_alloc;
2146         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2147                                          sizeof(*view->line));
2149         if (!tmp)
2150                 return NULL;
2152         view->line = tmp;
2153         view->line_alloc = alloc;
2154         view->line_size = line_size;
2155         return view->line;
2158 static bool
2159 update_view(struct view *view)
2161         char in_buffer[BUFSIZ];
2162         char out_buffer[BUFSIZ * 2];
2163         char *line;
2164         /* The number of lines to read. If too low it will cause too much
2165          * redrawing (and possible flickering), if too high responsiveness
2166          * will suffer. */
2167         unsigned long lines = view->height;
2168         int redraw_from = -1;
2170         if (!view->pipe)
2171                 return TRUE;
2173         /* Only redraw if lines are visible. */
2174         if (view->offset + view->height >= view->lines)
2175                 redraw_from = view->lines - view->offset;
2177         /* FIXME: This is probably not perfect for backgrounded views. */
2178         if (!realloc_lines(view, view->lines + lines))
2179                 goto alloc_error;
2181         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2182                 size_t linelen = strlen(line);
2184                 if (linelen)
2185                         line[linelen - 1] = 0;
2187                 if (opt_iconv != ICONV_NONE) {
2188                         ICONV_CONST char *inbuf = line;
2189                         size_t inlen = linelen;
2191                         char *outbuf = out_buffer;
2192                         size_t outlen = sizeof(out_buffer);
2194                         size_t ret;
2196                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2197                         if (ret != (size_t) -1) {
2198                                 line = out_buffer;
2199                                 linelen = strlen(out_buffer);
2200                         }
2201                 }
2203                 if (!view->ops->read(view, line))
2204                         goto alloc_error;
2206                 if (lines-- == 1)
2207                         break;
2208         }
2210         {
2211                 int digits;
2213                 lines = view->lines;
2214                 for (digits = 0; lines; digits++)
2215                         lines /= 10;
2217                 /* Keep the displayed view in sync with line number scaling. */
2218                 if (digits != view->digits) {
2219                         view->digits = digits;
2220                         redraw_from = 0;
2221                 }
2222         }
2224         if (!view_is_displayed(view))
2225                 goto check_pipe;
2227         if (view == VIEW(REQ_VIEW_TREE)) {
2228                 /* Clear the view and redraw everything since the tree sorting
2229                  * might have rearranged things. */
2230                 redraw_view(view);
2232         } else if (redraw_from >= 0) {
2233                 /* If this is an incremental update, redraw the previous line
2234                  * since for commits some members could have changed when
2235                  * loading the main view. */
2236                 if (redraw_from > 0)
2237                         redraw_from--;
2239                 /* Since revision graph visualization requires knowledge
2240                  * about the parent commit, it causes a further one-off
2241                  * needed to be redrawn for incremental updates. */
2242                 if (redraw_from > 0 && opt_rev_graph)
2243                         redraw_from--;
2245                 /* Incrementally draw avoids flickering. */
2246                 redraw_view_from(view, redraw_from);
2247         }
2249         if (view == VIEW(REQ_VIEW_BLAME))
2250                 redraw_view_dirty(view);
2252         /* Update the title _after_ the redraw so that if the redraw picks up a
2253          * commit reference in view->ref it'll be available here. */
2254         update_view_title(view);
2256 check_pipe:
2257         if (ferror(view->pipe)) {
2258                 report("Failed to read: %s", strerror(errno));
2259                 goto end;
2261         } else if (feof(view->pipe)) {
2262                 report("");
2263                 goto end;
2264         }
2266         return TRUE;
2268 alloc_error:
2269         report("Allocation failure");
2271 end:
2272         if (view->ops->read(view, NULL))
2273                 end_update(view);
2274         return FALSE;
2277 static struct line *
2278 add_line_data(struct view *view, void *data, enum line_type type)
2280         struct line *line = &view->line[view->lines++];
2282         memset(line, 0, sizeof(*line));
2283         line->type = type;
2284         line->data = data;
2286         return line;
2289 static struct line *
2290 add_line_text(struct view *view, char *data, enum line_type type)
2292         if (data)
2293                 data = strdup(data);
2295         return data ? add_line_data(view, data, type) : NULL;
2299 /*
2300  * View opening
2301  */
2303 enum open_flags {
2304         OPEN_DEFAULT = 0,       /* Use default view switching. */
2305         OPEN_SPLIT = 1,         /* Split current view. */
2306         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2307         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2308 };
2310 static void
2311 open_view(struct view *prev, enum request request, enum open_flags flags)
2313         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2314         bool split = !!(flags & OPEN_SPLIT);
2315         bool reload = !!(flags & OPEN_RELOAD);
2316         struct view *view = VIEW(request);
2317         int nviews = displayed_views();
2318         struct view *base_view = display[0];
2320         if (view == prev && nviews == 1 && !reload) {
2321                 report("Already in %s view", view->name);
2322                 return;
2323         }
2325         if (view->git_dir && !opt_git_dir[0]) {
2326                 report("The %s view is disabled in pager view", view->name);
2327                 return;
2328         }
2330         if (split) {
2331                 display[1] = view;
2332                 if (!backgrounded)
2333                         current_view = 1;
2334         } else {
2335                 /* Maximize the current view. */
2336                 memset(display, 0, sizeof(display));
2337                 current_view = 0;
2338                 display[current_view] = view;
2339         }
2341         /* Resize the view when switching between split- and full-screen,
2342          * or when switching between two different full-screen views. */
2343         if (nviews != displayed_views() ||
2344             (nviews == 1 && base_view != display[0]))
2345                 resize_display();
2347         if (view->ops->open) {
2348                 if (!view->ops->open(view)) {
2349                         report("Failed to load %s view", view->name);
2350                         return;
2351                 }
2353         } else if ((reload || strcmp(view->vid, view->id)) &&
2354                    !begin_update(view)) {
2355                 report("Failed to load %s view", view->name);
2356                 return;
2357         }
2359         if (split && prev->lineno - prev->offset >= prev->height) {
2360                 /* Take the title line into account. */
2361                 int lines = prev->lineno - prev->offset - prev->height + 1;
2363                 /* Scroll the view that was split if the current line is
2364                  * outside the new limited view. */
2365                 do_scroll_view(prev, lines);
2366         }
2368         if (prev && view != prev) {
2369                 if (split && !backgrounded) {
2370                         /* "Blur" the previous view. */
2371                         update_view_title(prev);
2372                 }
2374                 view->parent = prev;
2375         }
2377         if (view->pipe && view->lines == 0) {
2378                 /* Clear the old view and let the incremental updating refill
2379                  * the screen. */
2380                 wclear(view->win);
2381                 report("");
2382         } else {
2383                 redraw_view(view);
2384                 report("");
2385         }
2387         /* If the view is backgrounded the above calls to report()
2388          * won't redraw the view title. */
2389         if (backgrounded)
2390                 update_view_title(view);
2393 static void
2394 open_external_viewer(const char *cmd)
2396         def_prog_mode();           /* save current tty modes */
2397         endwin();                  /* restore original tty modes */
2398         system(cmd);
2399         fprintf(stderr, "Press Enter to continue");
2400         getc(stdin);
2401         reset_prog_mode();
2402         redraw_display();
2405 static void
2406 open_mergetool(const char *file)
2408         char cmd[SIZEOF_STR];
2409         char file_sq[SIZEOF_STR];
2411         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2412             string_format(cmd, "git mergetool %s", file_sq)) {
2413                 open_external_viewer(cmd);
2414         }
2417 static void
2418 open_editor(bool from_root, const char *file)
2420         char cmd[SIZEOF_STR];
2421         char file_sq[SIZEOF_STR];
2422         char *editor;
2423         char *prefix = from_root ? opt_cdup : "";
2425         editor = getenv("GIT_EDITOR");
2426         if (!editor && *opt_editor)
2427                 editor = opt_editor;
2428         if (!editor)
2429                 editor = getenv("VISUAL");
2430         if (!editor)
2431                 editor = getenv("EDITOR");
2432         if (!editor)
2433                 editor = "vi";
2435         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2436             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2437                 open_external_viewer(cmd);
2438         }
2441 static void
2442 open_run_request(enum request request)
2444         struct run_request *req = get_run_request(request);
2445         char buf[SIZEOF_STR * 2];
2446         size_t bufpos;
2447         char *cmd;
2449         if (!req) {
2450                 report("Unknown run request");
2451                 return;
2452         }
2454         bufpos = 0;
2455         cmd = req->cmd;
2457         while (cmd) {
2458                 char *next = strstr(cmd, "%(");
2459                 int len = next - cmd;
2460                 char *value;
2462                 if (!next) {
2463                         len = strlen(cmd);
2464                         value = "";
2466                 } else if (!strncmp(next, "%(head)", 7)) {
2467                         value = ref_head;
2469                 } else if (!strncmp(next, "%(commit)", 9)) {
2470                         value = ref_commit;
2472                 } else if (!strncmp(next, "%(blob)", 7)) {
2473                         value = ref_blob;
2475                 } else {
2476                         report("Unknown replacement in run request: `%s`", req->cmd);
2477                         return;
2478                 }
2480                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2481                         return;
2483                 if (next)
2484                         next = strchr(next, ')') + 1;
2485                 cmd = next;
2486         }
2488         open_external_viewer(buf);
2491 /*
2492  * User request switch noodle
2493  */
2495 static int
2496 view_driver(struct view *view, enum request request)
2498         int i;
2500         if (request == REQ_NONE) {
2501                 doupdate();
2502                 return TRUE;
2503         }
2505         if (request > REQ_NONE) {
2506                 open_run_request(request);
2507                 return TRUE;
2508         }
2510         if (view && view->lines) {
2511                 request = view->ops->request(view, request, &view->line[view->lineno]);
2512                 if (request == REQ_NONE)
2513                         return TRUE;
2514         }
2516         switch (request) {
2517         case REQ_MOVE_UP:
2518         case REQ_MOVE_DOWN:
2519         case REQ_MOVE_PAGE_UP:
2520         case REQ_MOVE_PAGE_DOWN:
2521         case REQ_MOVE_FIRST_LINE:
2522         case REQ_MOVE_LAST_LINE:
2523                 move_view(view, request);
2524                 break;
2526         case REQ_SCROLL_LINE_DOWN:
2527         case REQ_SCROLL_LINE_UP:
2528         case REQ_SCROLL_PAGE_DOWN:
2529         case REQ_SCROLL_PAGE_UP:
2530                 scroll_view(view, request);
2531                 break;
2533         case REQ_VIEW_BLAME:
2534                 if (!opt_file[0]) {
2535                         report("No file chosen, press %s to open tree view",
2536                                get_key(REQ_VIEW_TREE));
2537                         break;
2538                 }
2539                 open_view(view, request, OPEN_DEFAULT);
2540                 break;
2542         case REQ_VIEW_BLOB:
2543                 if (!ref_blob[0]) {
2544                         report("No file chosen, press %s to open tree view",
2545                                get_key(REQ_VIEW_TREE));
2546                         break;
2547                 }
2548                 open_view(view, request, OPEN_DEFAULT);
2549                 break;
2551         case REQ_VIEW_PAGER:
2552                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2553                         report("No pager content, press %s to run command from prompt",
2554                                get_key(REQ_PROMPT));
2555                         break;
2556                 }
2557                 open_view(view, request, OPEN_DEFAULT);
2558                 break;
2560         case REQ_VIEW_STAGE:
2561                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2562                         report("No stage content, press %s to open the status view and choose file",
2563                                get_key(REQ_VIEW_STATUS));
2564                         break;
2565                 }
2566                 open_view(view, request, OPEN_DEFAULT);
2567                 break;
2569         case REQ_VIEW_STATUS:
2570                 if (opt_is_inside_work_tree == FALSE) {
2571                         report("The status view requires a working tree");
2572                         break;
2573                 }
2574                 open_view(view, request, OPEN_DEFAULT);
2575                 break;
2577         case REQ_VIEW_MAIN:
2578         case REQ_VIEW_DIFF:
2579         case REQ_VIEW_LOG:
2580         case REQ_VIEW_TREE:
2581         case REQ_VIEW_HELP:
2582                 open_view(view, request, OPEN_DEFAULT);
2583                 break;
2585         case REQ_NEXT:
2586         case REQ_PREVIOUS:
2587                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2589                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2590                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2591                    (view == VIEW(REQ_VIEW_DIFF) &&
2592                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2593                    (view == VIEW(REQ_VIEW_STAGE) &&
2594                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2595                    (view == VIEW(REQ_VIEW_BLOB) &&
2596                      view->parent == VIEW(REQ_VIEW_TREE))) {
2597                         int line;
2599                         view = view->parent;
2600                         line = view->lineno;
2601                         move_view(view, request);
2602                         if (view_is_displayed(view))
2603                                 update_view_title(view);
2604                         if (line != view->lineno)
2605                                 view->ops->request(view, REQ_ENTER,
2606                                                    &view->line[view->lineno]);
2608                 } else {
2609                         move_view(view, request);
2610                 }
2611                 break;
2613         case REQ_VIEW_NEXT:
2614         {
2615                 int nviews = displayed_views();
2616                 int next_view = (current_view + 1) % nviews;
2618                 if (next_view == current_view) {
2619                         report("Only one view is displayed");
2620                         break;
2621                 }
2623                 current_view = next_view;
2624                 /* Blur out the title of the previous view. */
2625                 update_view_title(view);
2626                 report("");
2627                 break;
2628         }
2629         case REQ_REFRESH:
2630                 report("Refreshing is not yet supported for the %s view", view->name);
2631                 break;
2633         case REQ_MAXIMIZE:
2634                 if (displayed_views() == 2)
2635                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2636                 break;
2638         case REQ_TOGGLE_LINENO:
2639                 opt_line_number = !opt_line_number;
2640                 redraw_display();
2641                 break;
2643         case REQ_TOGGLE_DATE:
2644                 opt_date = !opt_date;
2645                 redraw_display();
2646                 break;
2648         case REQ_TOGGLE_AUTHOR:
2649                 opt_author = !opt_author;
2650                 redraw_display();
2651                 break;
2653         case REQ_TOGGLE_REV_GRAPH:
2654                 opt_rev_graph = !opt_rev_graph;
2655                 redraw_display();
2656                 break;
2658         case REQ_TOGGLE_REFS:
2659                 opt_show_refs = !opt_show_refs;
2660                 redraw_display();
2661                 break;
2663         case REQ_PROMPT:
2664                 /* Always reload^Wrerun commands from the prompt. */
2665                 open_view(view, opt_request, OPEN_RELOAD);
2666                 break;
2668         case REQ_SEARCH:
2669         case REQ_SEARCH_BACK:
2670                 search_view(view, request);
2671                 break;
2673         case REQ_FIND_NEXT:
2674         case REQ_FIND_PREV:
2675                 find_next(view, request);
2676                 break;
2678         case REQ_STOP_LOADING:
2679                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2680                         view = &views[i];
2681                         if (view->pipe)
2682                                 report("Stopped loading the %s view", view->name),
2683                         end_update(view);
2684                 }
2685                 break;
2687         case REQ_SHOW_VERSION:
2688                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2689                 return TRUE;
2691         case REQ_SCREEN_RESIZE:
2692                 resize_display();
2693                 /* Fall-through */
2694         case REQ_SCREEN_REDRAW:
2695                 redraw_display();
2696                 break;
2698         case REQ_EDIT:
2699                 report("Nothing to edit");
2700                 break;
2703         case REQ_ENTER:
2704                 report("Nothing to enter");
2705                 break;
2708         case REQ_VIEW_CLOSE:
2709                 /* XXX: Mark closed views by letting view->parent point to the
2710                  * view itself. Parents to closed view should never be
2711                  * followed. */
2712                 if (view->parent &&
2713                     view->parent->parent != view->parent) {
2714                         memset(display, 0, sizeof(display));
2715                         current_view = 0;
2716                         display[current_view] = view->parent;
2717                         view->parent = view;
2718                         resize_display();
2719                         redraw_display();
2720                         break;
2721                 }
2722                 /* Fall-through */
2723         case REQ_QUIT:
2724                 return FALSE;
2726         default:
2727                 /* An unknown key will show most commonly used commands. */
2728                 report("Unknown key, press 'h' for help");
2729                 return TRUE;
2730         }
2732         return TRUE;
2736 /*
2737  * Pager backend
2738  */
2740 static bool
2741 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2743         static char spaces[] = "                    ";
2744         char *text = line->data;
2745         enum line_type type = line->type;
2746         int attr = A_NORMAL;
2747         int col = 0;
2749         wmove(view->win, lineno, 0);
2751         if (selected) {
2752                 type = LINE_CURSOR;
2753                 wchgat(view->win, -1, 0, type, NULL);
2754                 attr = get_line_attr(type);
2755         }
2756         wattrset(view->win, attr);
2758         if (opt_line_number) {
2759                 col += draw_lineno(view, lineno, view->width, selected);
2760                 if (col >= view->width)
2761                         return TRUE;
2762         }
2764         if (!selected) {
2765                 attr = get_line_attr(type);
2766                 wattrset(view->win, attr);
2767         }
2768         if (opt_tab_size < TABSIZE) {
2769                 int col_offset = col;
2771                 col = 0;
2772                 while (text && col_offset + col < view->width) {
2773                         int cols_max = view->width - col_offset - col;
2774                         char *pos = text;
2775                         int cols;
2777                         if (*text == '\t') {
2778                                 text++;
2779                                 assert(sizeof(spaces) > TABSIZE);
2780                                 pos = spaces;
2781                                 cols = opt_tab_size - (col % opt_tab_size);
2783                         } else {
2784                                 text = strchr(text, '\t');
2785                                 cols = line ? text - pos : strlen(pos);
2786                         }
2788                         waddnstr(view->win, pos, MIN(cols, cols_max));
2789                         col += cols;
2790                 }
2792         } else {
2793                 draw_text(view, text, view->width - col, TRUE, selected);
2794         }
2796         return TRUE;
2799 static bool
2800 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2802         char refbuf[SIZEOF_STR];
2803         char *ref = NULL;
2804         FILE *pipe;
2806         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2807                 return TRUE;
2809         pipe = popen(refbuf, "r");
2810         if (!pipe)
2811                 return TRUE;
2813         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2814                 ref = chomp_string(ref);
2815         pclose(pipe);
2817         if (!ref || !*ref)
2818                 return TRUE;
2820         /* This is the only fatal call, since it can "corrupt" the buffer. */
2821         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2822                 return FALSE;
2824         return TRUE;
2827 static void
2828 add_pager_refs(struct view *view, struct line *line)
2830         char buf[SIZEOF_STR];
2831         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2832         struct ref **refs;
2833         size_t bufpos = 0, refpos = 0;
2834         const char *sep = "Refs: ";
2835         bool is_tag = FALSE;
2837         assert(line->type == LINE_COMMIT);
2839         refs = get_refs(commit_id);
2840         if (!refs) {
2841                 if (view == VIEW(REQ_VIEW_DIFF))
2842                         goto try_add_describe_ref;
2843                 return;
2844         }
2846         do {
2847                 struct ref *ref = refs[refpos];
2848                 char *fmt = ref->tag    ? "%s[%s]" :
2849                             ref->remote ? "%s<%s>" : "%s%s";
2851                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2852                         return;
2853                 sep = ", ";
2854                 if (ref->tag)
2855                         is_tag = TRUE;
2856         } while (refs[refpos++]->next);
2858         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2859 try_add_describe_ref:
2860                 /* Add <tag>-g<commit_id> "fake" reference. */
2861                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2862                         return;
2863         }
2865         if (bufpos == 0)
2866                 return;
2868         if (!realloc_lines(view, view->line_size + 1))
2869                 return;
2871         add_line_text(view, buf, LINE_PP_REFS);
2874 static bool
2875 pager_read(struct view *view, char *data)
2877         struct line *line;
2879         if (!data)
2880                 return TRUE;
2882         line = add_line_text(view, data, get_line_type(data));
2883         if (!line)
2884                 return FALSE;
2886         if (line->type == LINE_COMMIT &&
2887             (view == VIEW(REQ_VIEW_DIFF) ||
2888              view == VIEW(REQ_VIEW_LOG)))
2889                 add_pager_refs(view, line);
2891         return TRUE;
2894 static enum request
2895 pager_request(struct view *view, enum request request, struct line *line)
2897         int split = 0;
2899         if (request != REQ_ENTER)
2900                 return request;
2902         if (line->type == LINE_COMMIT &&
2903            (view == VIEW(REQ_VIEW_LOG) ||
2904             view == VIEW(REQ_VIEW_PAGER))) {
2905                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2906                 split = 1;
2907         }
2909         /* Always scroll the view even if it was split. That way
2910          * you can use Enter to scroll through the log view and
2911          * split open each commit diff. */
2912         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2914         /* FIXME: A minor workaround. Scrolling the view will call report("")
2915          * but if we are scrolling a non-current view this won't properly
2916          * update the view title. */
2917         if (split)
2918                 update_view_title(view);
2920         return REQ_NONE;
2923 static bool
2924 pager_grep(struct view *view, struct line *line)
2926         regmatch_t pmatch;
2927         char *text = line->data;
2929         if (!*text)
2930                 return FALSE;
2932         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2933                 return FALSE;
2935         return TRUE;
2938 static void
2939 pager_select(struct view *view, struct line *line)
2941         if (line->type == LINE_COMMIT) {
2942                 char *text = (char *)line->data + STRING_SIZE("commit ");
2944                 if (view != VIEW(REQ_VIEW_PAGER))
2945                         string_copy_rev(view->ref, text);
2946                 string_copy_rev(ref_commit, text);
2947         }
2950 static struct view_ops pager_ops = {
2951         "line",
2952         NULL,
2953         pager_read,
2954         pager_draw,
2955         pager_request,
2956         pager_grep,
2957         pager_select,
2958 };
2961 /*
2962  * Help backend
2963  */
2965 static bool
2966 help_open(struct view *view)
2968         char buf[BUFSIZ];
2969         int lines = ARRAY_SIZE(req_info) + 2;
2970         int i;
2972         if (view->lines > 0)
2973                 return TRUE;
2975         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2976                 if (!req_info[i].request)
2977                         lines++;
2979         lines += run_requests + 1;
2981         view->line = calloc(lines, sizeof(*view->line));
2982         if (!view->line)
2983                 return FALSE;
2985         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2987         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2988                 char *key;
2990                 if (req_info[i].request == REQ_NONE)
2991                         continue;
2993                 if (!req_info[i].request) {
2994                         add_line_text(view, "", LINE_DEFAULT);
2995                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2996                         continue;
2997                 }
2999                 key = get_key(req_info[i].request);
3000                 if (!*key)
3001                         key = "(no key defined)";
3003                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3004                         continue;
3006                 add_line_text(view, buf, LINE_DEFAULT);
3007         }
3009         if (run_requests) {
3010                 add_line_text(view, "", LINE_DEFAULT);
3011                 add_line_text(view, "External commands:", LINE_DEFAULT);
3012         }
3014         for (i = 0; i < run_requests; i++) {
3015                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3016                 char *key;
3018                 if (!req)
3019                         continue;
3021                 key = get_key_name(req->key);
3022                 if (!*key)
3023                         key = "(no key defined)";
3025                 if (!string_format(buf, "    %-10s %-14s `%s`",
3026                                    keymap_table[req->keymap].name,
3027                                    key, req->cmd))
3028                         continue;
3030                 add_line_text(view, buf, LINE_DEFAULT);
3031         }
3033         return TRUE;
3036 static struct view_ops help_ops = {
3037         "line",
3038         help_open,
3039         NULL,
3040         pager_draw,
3041         pager_request,
3042         pager_grep,
3043         pager_select,
3044 };
3047 /*
3048  * Tree backend
3049  */
3051 struct tree_stack_entry {
3052         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3053         unsigned long lineno;           /* Line number to restore */
3054         char *name;                     /* Position of name in opt_path */
3055 };
3057 /* The top of the path stack. */
3058 static struct tree_stack_entry *tree_stack = NULL;
3059 unsigned long tree_lineno = 0;
3061 static void
3062 pop_tree_stack_entry(void)
3064         struct tree_stack_entry *entry = tree_stack;
3066         tree_lineno = entry->lineno;
3067         entry->name[0] = 0;
3068         tree_stack = entry->prev;
3069         free(entry);
3072 static void
3073 push_tree_stack_entry(char *name, unsigned long lineno)
3075         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3076         size_t pathlen = strlen(opt_path);
3078         if (!entry)
3079                 return;
3081         entry->prev = tree_stack;
3082         entry->name = opt_path + pathlen;
3083         tree_stack = entry;
3085         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3086                 pop_tree_stack_entry();
3087                 return;
3088         }
3090         /* Move the current line to the first tree entry. */
3091         tree_lineno = 1;
3092         entry->lineno = lineno;
3095 /* Parse output from git-ls-tree(1):
3096  *
3097  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3098  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3099  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3100  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3101  */
3103 #define SIZEOF_TREE_ATTR \
3104         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3106 #define TREE_UP_FORMAT "040000 tree %s\t.."
3108 static int
3109 tree_compare_entry(enum line_type type1, char *name1,
3110                    enum line_type type2, char *name2)
3112         if (type1 != type2) {
3113                 if (type1 == LINE_TREE_DIR)
3114                         return -1;
3115                 return 1;
3116         }
3118         return strcmp(name1, name2);
3121 static char *
3122 tree_path(struct line *line)
3124         char *path = line->data;
3126         return path + SIZEOF_TREE_ATTR;
3129 static bool
3130 tree_read(struct view *view, char *text)
3132         size_t textlen = text ? strlen(text) : 0;
3133         char buf[SIZEOF_STR];
3134         unsigned long pos;
3135         enum line_type type;
3136         bool first_read = view->lines == 0;
3138         if (!text)
3139                 return TRUE;
3140         if (textlen <= SIZEOF_TREE_ATTR)
3141                 return FALSE;
3143         type = text[STRING_SIZE("100644 ")] == 't'
3144              ? LINE_TREE_DIR : LINE_TREE_FILE;
3146         if (first_read) {
3147                 /* Add path info line */
3148                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3149                     !realloc_lines(view, view->line_size + 1) ||
3150                     !add_line_text(view, buf, LINE_DEFAULT))
3151                         return FALSE;
3153                 /* Insert "link" to parent directory. */
3154                 if (*opt_path) {
3155                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3156                             !realloc_lines(view, view->line_size + 1) ||
3157                             !add_line_text(view, buf, LINE_TREE_DIR))
3158                                 return FALSE;
3159                 }
3160         }
3162         /* Strip the path part ... */
3163         if (*opt_path) {
3164                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3165                 size_t striplen = strlen(opt_path);
3166                 char *path = text + SIZEOF_TREE_ATTR;
3168                 if (pathlen > striplen)
3169                         memmove(path, path + striplen,
3170                                 pathlen - striplen + 1);
3171         }
3173         /* Skip "Directory ..." and ".." line. */
3174         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3175                 struct line *line = &view->line[pos];
3176                 char *path1 = tree_path(line);
3177                 char *path2 = text + SIZEOF_TREE_ATTR;
3178                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3180                 if (cmp <= 0)
3181                         continue;
3183                 text = strdup(text);
3184                 if (!text)
3185                         return FALSE;
3187                 if (view->lines > pos)
3188                         memmove(&view->line[pos + 1], &view->line[pos],
3189                                 (view->lines - pos) * sizeof(*line));
3191                 line = &view->line[pos];
3192                 line->data = text;
3193                 line->type = type;
3194                 view->lines++;
3195                 return TRUE;
3196         }
3198         if (!add_line_text(view, text, type))
3199                 return FALSE;
3201         if (tree_lineno > view->lineno) {
3202                 view->lineno = tree_lineno;
3203                 tree_lineno = 0;
3204         }
3206         return TRUE;
3209 static enum request
3210 tree_request(struct view *view, enum request request, struct line *line)
3212         enum open_flags flags;
3214         if (request == REQ_VIEW_BLAME) {
3215                 char *filename = tree_path(line);
3217                 if (line->type == LINE_TREE_DIR) {
3218                         report("Cannot show blame for directory %s", opt_path);
3219                         return REQ_NONE;
3220                 }
3222                 string_copy(opt_ref, view->vid);
3223                 string_format(opt_file, "%s%s", opt_path, filename);
3224                 return request;
3225         }
3226         if (request == REQ_TREE_PARENT) {
3227                 if (*opt_path) {
3228                         /* fake 'cd  ..' */
3229                         request = REQ_ENTER;
3230                         line = &view->line[1];
3231                 } else {
3232                         /* quit view if at top of tree */
3233                         return REQ_VIEW_CLOSE;
3234                 }
3235         }
3236         if (request != REQ_ENTER)
3237                 return request;
3239         /* Cleanup the stack if the tree view is at a different tree. */
3240         while (!*opt_path && tree_stack)
3241                 pop_tree_stack_entry();
3243         switch (line->type) {
3244         case LINE_TREE_DIR:
3245                 /* Depending on whether it is a subdir or parent (updir?) link
3246                  * mangle the path buffer. */
3247                 if (line == &view->line[1] && *opt_path) {
3248                         pop_tree_stack_entry();
3250                 } else {
3251                         char *basename = tree_path(line);
3253                         push_tree_stack_entry(basename, view->lineno);
3254                 }
3256                 /* Trees and subtrees share the same ID, so they are not not
3257                  * unique like blobs. */
3258                 flags = OPEN_RELOAD;
3259                 request = REQ_VIEW_TREE;
3260                 break;
3262         case LINE_TREE_FILE:
3263                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3264                 request = REQ_VIEW_BLOB;
3265                 break;
3267         default:
3268                 return TRUE;
3269         }
3271         open_view(view, request, flags);
3272         if (request == REQ_VIEW_TREE) {
3273                 view->lineno = tree_lineno;
3274         }
3276         return REQ_NONE;
3279 static void
3280 tree_select(struct view *view, struct line *line)
3282         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3284         if (line->type == LINE_TREE_FILE) {
3285                 string_copy_rev(ref_blob, text);
3287         } else if (line->type != LINE_TREE_DIR) {
3288                 return;
3289         }
3291         string_copy_rev(view->ref, text);
3294 static struct view_ops tree_ops = {
3295         "file",
3296         NULL,
3297         tree_read,
3298         pager_draw,
3299         tree_request,
3300         pager_grep,
3301         tree_select,
3302 };
3304 static bool
3305 blob_read(struct view *view, char *line)
3307         if (!line)
3308                 return TRUE;
3309         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3312 static struct view_ops blob_ops = {
3313         "line",
3314         NULL,
3315         blob_read,
3316         pager_draw,
3317         pager_request,
3318         pager_grep,
3319         pager_select,
3320 };
3322 /*
3323  * Blame backend
3324  *
3325  * Loading the blame view is a two phase job:
3326  *
3327  *  1. File content is read either using opt_file from the
3328  *     filesystem or using git-cat-file.
3329  *  2. Then blame information is incrementally added by
3330  *     reading output from git-blame.
3331  */
3333 struct blame_commit {
3334         char id[SIZEOF_REV];            /* SHA1 ID. */
3335         char title[128];                /* First line of the commit message. */
3336         char author[75];                /* Author of the commit. */
3337         struct tm time;                 /* Date from the author ident. */
3338         char filename[128];             /* Name of file. */
3339 };
3341 struct blame {
3342         struct blame_commit *commit;
3343         unsigned int header:1;
3344         char text[1];
3345 };
3347 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3348 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3350 static bool
3351 blame_open(struct view *view)
3353         char path[SIZEOF_STR];
3354         char ref[SIZEOF_STR] = "";
3356         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3357                 return FALSE;
3359         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3360                 return FALSE;
3362         if (*opt_ref) {
3363                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3364                         return FALSE;
3365         } else {
3366                 view->pipe = fopen(opt_file, "r");
3367                 if (!view->pipe &&
3368                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3369                         return FALSE;
3370         }
3372         if (!view->pipe)
3373                 view->pipe = popen(view->cmd, "r");
3374         if (!view->pipe)
3375                 return FALSE;
3377         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3378                 return FALSE;
3380         string_format(view->ref, "%s ...", opt_file);
3381         string_copy_rev(view->vid, opt_file);
3382         set_nonblocking_input(TRUE);
3384         if (view->line) {
3385                 int i;
3387                 for (i = 0; i < view->lines; i++)
3388                         free(view->line[i].data);
3389                 free(view->line);
3390         }
3392         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3393         view->offset = view->lines  = view->lineno = 0;
3394         view->line = NULL;
3395         view->start_time = time(NULL);
3397         return TRUE;
3400 static struct blame_commit *
3401 get_blame_commit(struct view *view, const char *id)
3403         size_t i;
3405         for (i = 0; i < view->lines; i++) {
3406                 struct blame *blame = view->line[i].data;
3408                 if (!blame->commit)
3409                         continue;
3411                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3412                         return blame->commit;
3413         }
3415         {
3416                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3418                 if (commit)
3419                         string_ncopy(commit->id, id, SIZEOF_REV);
3420                 return commit;
3421         }
3424 static bool
3425 parse_number(char **posref, size_t *number, size_t min, size_t max)
3427         char *pos = *posref;
3429         *posref = NULL;
3430         pos = strchr(pos + 1, ' ');
3431         if (!pos || !isdigit(pos[1]))
3432                 return FALSE;
3433         *number = atoi(pos + 1);
3434         if (*number < min || *number > max)
3435                 return FALSE;
3437         *posref = pos;
3438         return TRUE;
3441 static struct blame_commit *
3442 parse_blame_commit(struct view *view, char *text, int *blamed)
3444         struct blame_commit *commit;
3445         struct blame *blame;
3446         char *pos = text + SIZEOF_REV - 1;
3447         size_t lineno;
3448         size_t group;
3450         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3451                 return NULL;
3453         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3454             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3455                 return NULL;
3457         commit = get_blame_commit(view, text);
3458         if (!commit)
3459                 return NULL;
3461         *blamed += group;
3462         while (group--) {
3463                 struct line *line = &view->line[lineno + group - 1];
3465                 blame = line->data;
3466                 blame->commit = commit;
3467                 blame->header = !group;
3468                 line->dirty = 1;
3469         }
3471         return commit;
3474 static bool
3475 blame_read_file(struct view *view, char *line)
3477         if (!line) {
3478                 FILE *pipe = NULL;
3480                 if (view->lines > 0)
3481                         pipe = popen(view->cmd, "r");
3482                 view->cmd[0] = 0;
3483                 if (!pipe) {
3484                         report("Failed to load blame data");
3485                         return TRUE;
3486                 }
3488                 fclose(view->pipe);
3489                 view->pipe = pipe;
3490                 return FALSE;
3492         } else {
3493                 size_t linelen = strlen(line);
3494                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3496                 if (!line)
3497                         return FALSE;
3499                 blame->commit = NULL;
3500                 strncpy(blame->text, line, linelen);
3501                 blame->text[linelen] = 0;
3502                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3503         }
3506 static bool
3507 match_blame_header(const char *name, char **line)
3509         size_t namelen = strlen(name);
3510         bool matched = !strncmp(name, *line, namelen);
3512         if (matched)
3513                 *line += namelen;
3515         return matched;
3518 static bool
3519 blame_read(struct view *view, char *line)
3521         static struct blame_commit *commit = NULL;
3522         static int blamed = 0;
3523         static time_t author_time;
3525         if (*view->cmd)
3526                 return blame_read_file(view, line);
3528         if (!line) {
3529                 /* Reset all! */
3530                 commit = NULL;
3531                 blamed = 0;
3532                 string_format(view->ref, "%s", view->vid);
3533                 if (view_is_displayed(view)) {
3534                         update_view_title(view);
3535                         redraw_view_from(view, 0);
3536                 }
3537                 return TRUE;
3538         }
3540         if (!commit) {
3541                 commit = parse_blame_commit(view, line, &blamed);
3542                 string_format(view->ref, "%s %2d%%", view->vid,
3543                               blamed * 100 / view->lines);
3545         } else if (match_blame_header("author ", &line)) {
3546                 string_ncopy(commit->author, line, strlen(line));
3548         } else if (match_blame_header("author-time ", &line)) {
3549                 author_time = (time_t) atol(line);
3551         } else if (match_blame_header("author-tz ", &line)) {
3552                 long tz;
3554                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3555                 tz += ('0' - line[2]) * 60 * 60;
3556                 tz += ('0' - line[3]) * 60;
3557                 tz += ('0' - line[4]) * 60;
3559                 if (line[0] == '-')
3560                         tz = -tz;
3562                 author_time -= tz;
3563                 gmtime_r(&author_time, &commit->time);
3565         } else if (match_blame_header("summary ", &line)) {
3566                 string_ncopy(commit->title, line, strlen(line));
3568         } else if (match_blame_header("filename ", &line)) {
3569                 string_ncopy(commit->filename, line, strlen(line));
3570                 commit = NULL;
3571         }
3573         return TRUE;
3576 static bool
3577 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3579         struct blame *blame = line->data;
3580         int col = 0;
3582         wmove(view->win, lineno, 0);
3584         if (selected) {
3585                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3586                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3587         } else {
3588                 wattrset(view->win, A_NORMAL);
3589         }
3591         if (opt_date) {
3592                 int n;
3594                 if (!selected)
3595                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3596                 if (blame->commit) {
3597                         char buf[DATE_COLS + 1];
3598                         int timelen;
3600                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3601                         n = draw_text(view, buf, view->width - col, FALSE, selected);
3602                         draw_text(view, " ", view->width - col - n, FALSE, selected);
3603                 }
3605                 col += DATE_COLS;
3606                 wmove(view->win, lineno, col);
3607                 if (col >= view->width)
3608                         return TRUE;
3609         }
3611         if (opt_author) {
3612                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3614                 if (!selected)
3615                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3616                 if (blame->commit)
3617                         draw_text(view, blame->commit->author, max, TRUE, selected);
3618                 col += AUTHOR_COLS;
3619                 if (col >= view->width)
3620                         return TRUE;
3621                 wmove(view->win, lineno, col);
3622         }
3624         {
3625                 int max = MIN(ID_COLS - 1, view->width - col);
3627                 if (!selected)
3628                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3629                 if (blame->commit)
3630                         draw_text(view, blame->commit->id, max, FALSE, -1);
3631                 col += ID_COLS;
3632                 if (col >= view->width)
3633                         return TRUE;
3634                 wmove(view->win, lineno, col);
3635         }
3637         {
3638                 if (!selected)
3639                         wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
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;