Code

Improve staging of diff chunks
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
97 #define ID_COLS         8
99 /* The default interval between line numbers. */
100 #define NUMBER_INTERVAL 5
102 #define TABSIZE         8
104 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
106 #define NULL_ID         "0000000000000000000000000000000000000000"
108 #ifndef GIT_CONFIG
109 #define GIT_CONFIG "git config"
110 #endif
112 #define TIG_LS_REMOTE \
113         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
115 #define TIG_DIFF_CMD \
116         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
118 #define TIG_LOG_CMD     \
119         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
121 #define TIG_MAIN_CMD \
122         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
124 #define TIG_TREE_CMD    \
125         "git ls-tree %s %s"
127 #define TIG_BLOB_CMD    \
128         "git cat-file blob %s"
130 /* XXX: Needs to be defined to the empty string. */
131 #define TIG_HELP_CMD    ""
132 #define TIG_PAGER_CMD   ""
133 #define TIG_STATUS_CMD  ""
134 #define TIG_STAGE_CMD   ""
135 #define TIG_BLAME_CMD   ""
137 /* Some ascii-shorthands fitted into the ncurses namespace. */
138 #define KEY_TAB         '\t'
139 #define KEY_RETURN      '\r'
140 #define KEY_ESC         27
143 struct ref {
144         char *name;             /* Ref name; tag or head names are shortened. */
145         char id[SIZEOF_REV];    /* Commit SHA1 ID */
146         unsigned int head:1;    /* Is it the current HEAD? */
147         unsigned int tag:1;     /* Is it a tag? */
148         unsigned int ltag:1;    /* If so, is the tag local? */
149         unsigned int remote:1;  /* Is it a remote ref? */
150         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
151         unsigned int next:1;    /* For ref lists: are there more refs? */
152 };
154 static struct ref **get_refs(char *id);
156 struct int_map {
157         const char *name;
158         int namelen;
159         int value;
160 };
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164                  int *value, const char *name, int namelen)
167         int i;
169         for (i = 0; i < map_size; i++)
170                 if (namelen == map[i].namelen &&
171                     !strncasecmp(name, map[i].name, namelen)) {
172                         *value = map[i].value;
173                         return OK;
174                 }
176         return ERR;
180 /*
181  * String helpers
182  */
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187         if (srclen > dstlen - 1)
188                 srclen = dstlen - 1;
190         strncpy(dst, src, srclen);
191         dst[srclen] = 0;
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200         string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
211         int namelen;
213         while (isspace(*name))
214                 name++;
216         namelen = strlen(name) - 1;
217         while (namelen > 0 && isspace(name[namelen]))
218                 name[namelen--] = 0;
220         return name;
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
226         va_list args;
227         size_t pos = bufpos ? *bufpos : 0;
229         va_start(args, fmt);
230         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231         va_end(args);
233         if (bufpos)
234                 *bufpos = pos;
236         return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240         string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243         string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
248         size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252         /* Diff-Header == DIFF_HEADER */
253         for (i = 0; i < len; i++) {
254                 if (toupper(str1[i]) == toupper(str2[i]))
255                         continue;
257                 if (string_enum_sep(str1[i]) &&
258                     string_enum_sep(str2[i]))
259                         continue;
261                 return str1[i] - str2[i];
262         }
264         return 0;
267 /* Shell quoting
268  *
269  * NOTE: The following is a slightly modified copy of the git project's shell
270  * quoting routines found in the quote.c file.
271  *
272  * Help to copy the thing properly quoted for the shell safety.  any single
273  * quote is replaced with '\'', any exclamation point is replaced with '\!',
274  * and the whole thing is enclosed in a
275  *
276  * E.g.
277  *  original     sq_quote     result
278  *  name     ==> name      ==> 'name'
279  *  a b      ==> a b       ==> 'a b'
280  *  a'b      ==> a'\''b    ==> 'a'\''b'
281  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
282  */
284 static size_t
285 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
287         char c;
289 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
291         BUFPUT('\'');
292         while ((c = *src++)) {
293                 if (c == '\'' || c == '!') {
294                         BUFPUT('\'');
295                         BUFPUT('\\');
296                         BUFPUT(c);
297                         BUFPUT('\'');
298                 } else {
299                         BUFPUT(c);
300                 }
301         }
302         BUFPUT('\'');
304         if (bufsize < SIZEOF_STR)
305                 buf[bufsize] = 0;
307         return bufsize;
311 /*
312  * User requests
313  */
315 #define REQ_INFO \
316         /* XXX: Keep the view request first and in sync with views[]. */ \
317         REQ_GROUP("View switching") \
318         REQ_(VIEW_MAIN,         "Show main view"), \
319         REQ_(VIEW_DIFF,         "Show diff view"), \
320         REQ_(VIEW_LOG,          "Show log view"), \
321         REQ_(VIEW_TREE,         "Show tree view"), \
322         REQ_(VIEW_BLOB,         "Show blob view"), \
323         REQ_(VIEW_BLAME,        "Show blame view"), \
324         REQ_(VIEW_HELP,         "Show help page"), \
325         REQ_(VIEW_PAGER,        "Show pager view"), \
326         REQ_(VIEW_STATUS,       "Show status view"), \
327         REQ_(VIEW_STAGE,        "Show stage view"), \
328         \
329         REQ_GROUP("View manipulation") \
330         REQ_(ENTER,             "Enter current line and scroll"), \
331         REQ_(NEXT,              "Move to next"), \
332         REQ_(PREVIOUS,          "Move to previous"), \
333         REQ_(VIEW_NEXT,         "Move focus to next view"), \
334         REQ_(REFRESH,           "Reload and refresh"), \
335         REQ_(MAXIMIZE,          "Maximize the current view"), \
336         REQ_(VIEW_CLOSE,        "Close the current view"), \
337         REQ_(QUIT,              "Close all views and quit"), \
338         \
339         REQ_GROUP("Cursor navigation") \
340         REQ_(MOVE_UP,           "Move cursor one line up"), \
341         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
342         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
343         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
344         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
345         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
346         \
347         REQ_GROUP("Scrolling") \
348         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
349         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
350         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
351         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
352         \
353         REQ_GROUP("Searching") \
354         REQ_(SEARCH,            "Search the view"), \
355         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
356         REQ_(FIND_NEXT,         "Find next search match"), \
357         REQ_(FIND_PREV,         "Find previous search match"), \
358         \
359         REQ_GROUP("Misc") \
360         REQ_(PROMPT,            "Bring up the prompt"), \
361         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
362         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
363         REQ_(SHOW_VERSION,      "Show version information"), \
364         REQ_(STOP_LOADING,      "Stop all loading views"), \
365         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
366         REQ_(TOGGLE_DATE,       "Toggle date display"), \
367         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
368         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
369         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
370         REQ_(STATUS_UPDATE,     "Update file status"), \
371         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
372         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
373         REQ_(EDIT,              "Open in editor"), \
374         REQ_(NONE,              "Do nothing")
377 /* User action requests. */
378 enum request {
379 #define REQ_GROUP(help)
380 #define REQ_(req, help) REQ_##req
382         /* Offset all requests to avoid conflicts with ncurses getch values. */
383         REQ_OFFSET = KEY_MAX + 1,
384         REQ_INFO
386 #undef  REQ_GROUP
387 #undef  REQ_
388 };
390 struct request_info {
391         enum request request;
392         char *name;
393         int namelen;
394         char *help;
395 };
397 static struct request_info req_info[] = {
398 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
399 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
400         REQ_INFO
401 #undef  REQ_GROUP
402 #undef  REQ_
403 };
405 static enum request
406 get_request(const char *name)
408         int namelen = strlen(name);
409         int i;
411         for (i = 0; i < ARRAY_SIZE(req_info); i++)
412                 if (req_info[i].namelen == namelen &&
413                     !string_enum_compare(req_info[i].name, name, namelen))
414                         return req_info[i].request;
416         return REQ_NONE;
420 /*
421  * Options
422  */
424 static const char usage[] =
425 "tig " TIG_VERSION " (" __DATE__ ")\n"
426 "\n"
427 "Usage: tig        [options] [revs] [--] [paths]\n"
428 "   or: tig show   [options] [revs] [--] [paths]\n"
429 "   or: tig blame  [rev] path\n"
430 "   or: tig status\n"
431 "   or: tig <      [git command output]\n"
432 "\n"
433 "Options:\n"
434 "  -v, --version   Show version and exit\n"
435 "  -h, --help      Show help message and exit";
437 /* Option and state variables. */
438 static bool opt_date                    = TRUE;
439 static bool opt_author                  = TRUE;
440 static bool opt_line_number             = FALSE;
441 static bool opt_rev_graph               = FALSE;
442 static bool opt_show_refs               = TRUE;
443 static int opt_num_interval             = NUMBER_INTERVAL;
444 static int opt_tab_size                 = TABSIZE;
445 static enum request opt_request         = REQ_VIEW_MAIN;
446 static char opt_cmd[SIZEOF_STR]         = "";
447 static char opt_path[SIZEOF_STR]        = "";
448 static char opt_file[SIZEOF_STR]        = "";
449 static char opt_ref[SIZEOF_REF]         = "";
450 static char opt_head[SIZEOF_REF]        = "";
451 static char opt_remote[SIZEOF_REF]      = "";
452 static bool opt_no_head                 = TRUE;
453 static FILE *opt_pipe                   = NULL;
454 static char opt_encoding[20]            = "UTF-8";
455 static bool opt_utf8                    = TRUE;
456 static char opt_codeset[20]             = "UTF-8";
457 static iconv_t opt_iconv                = ICONV_NONE;
458 static char opt_search[SIZEOF_STR]      = "";
459 static char opt_cdup[SIZEOF_STR]        = "";
460 static char opt_git_dir[SIZEOF_STR]     = "";
461 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
462 static char opt_editor[SIZEOF_STR]      = "";
464 static bool
465 parse_options(int argc, char *argv[])
467         size_t buf_size;
468         char *subcommand;
469         bool seen_dashdash = FALSE;
470         int i;
472         if (!isatty(STDIN_FILENO)) {
473                 opt_request = REQ_VIEW_PAGER;
474                 opt_pipe = stdin;
475                 return TRUE;
476         }
478         if (argc <= 1)
479                 return TRUE;
481         subcommand = argv[1];
482         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
483                 opt_request = REQ_VIEW_STATUS;
484                 if (!strcmp(subcommand, "-S"))
485                         warn("`-S' has been deprecated; use `tig status' instead");
486                 if (argc > 2)
487                         warn("ignoring arguments after `%s'", subcommand);
488                 return TRUE;
490         } else if (!strcmp(subcommand, "blame")) {
491                 opt_request = REQ_VIEW_BLAME;
492                 if (argc <= 2 || argc > 4)
493                         die("invalid number of options to blame\n\n%s", usage);
495                 i = 2;
496                 if (argc == 4) {
497                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
498                         i++;
499                 }
501                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
502                 return TRUE;
504         } else if (!strcmp(subcommand, "show")) {
505                 opt_request = REQ_VIEW_DIFF;
507         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
508                 opt_request = subcommand[0] == 'l'
509                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510                 warn("`tig %s' has been deprecated", subcommand);
512         } else {
513                 subcommand = NULL;
514         }
516         if (!subcommand)
517                 /* XXX: This is vulnerable to the user overriding
518                  * options required for the main view parser. */
519                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
520         else
521                 string_format(opt_cmd, "git %s", subcommand);
523         buf_size = strlen(opt_cmd);
525         for (i = 1 + !!subcommand; i < argc; i++) {
526                 char *opt = argv[i];
528                 if (seen_dashdash || !strcmp(opt, "--")) {
529                         seen_dashdash = TRUE;
531                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
532                         printf("tig version %s\n", TIG_VERSION);
533                         return FALSE;
535                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
536                         printf("%s\n", usage);
537                         return FALSE;
538                 }
540                 opt_cmd[buf_size++] = ' ';
541                 buf_size = sq_quote(opt_cmd, buf_size, opt);
542                 if (buf_size >= sizeof(opt_cmd))
543                         die("command too long");
544         }
546         opt_cmd[buf_size] = 0;
548         return TRUE;
552 /*
553  * Line-oriented content detection.
554  */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
558 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
559 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
560 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
561 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
562 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
568 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
570 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
572 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
573 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
579 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
580 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
581 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
582 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
586 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
587 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
590 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
591 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
592 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
593 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
599 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
601 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
604 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
607 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
611 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
612 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
613 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
615 enum line_type {
616 #define LINE(type, line, fg, bg, attr) \
617         LINE_##type
618         LINE_INFO
619 #undef  LINE
620 };
622 struct line_info {
623         const char *name;       /* Option name. */
624         int namelen;            /* Size of option name. */
625         const char *line;       /* The start of line to match. */
626         int linelen;            /* Size of string to match. */
627         int fg, bg, attr;       /* Color and text attributes for the lines. */
628 };
630 static struct line_info line_info[] = {
631 #define LINE(type, line, fg, bg, attr) \
632         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
633         LINE_INFO
634 #undef  LINE
635 };
637 static enum line_type
638 get_line_type(char *line)
640         int linelen = strlen(line);
641         enum line_type type;
643         for (type = 0; type < ARRAY_SIZE(line_info); type++)
644                 /* Case insensitive search matches Signed-off-by lines better. */
645                 if (linelen >= line_info[type].linelen &&
646                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
647                         return type;
649         return LINE_DEFAULT;
652 static inline int
653 get_line_attr(enum line_type type)
655         assert(type < ARRAY_SIZE(line_info));
656         return COLOR_PAIR(type) | line_info[type].attr;
659 static struct line_info *
660 get_line_info(char *name)
662         size_t namelen = strlen(name);
663         enum line_type type;
665         for (type = 0; type < ARRAY_SIZE(line_info); type++)
666                 if (namelen == line_info[type].namelen &&
667                     !string_enum_compare(line_info[type].name, name, namelen))
668                         return &line_info[type];
670         return NULL;
673 static void
674 init_colors(void)
676         int default_bg = line_info[LINE_DEFAULT].bg;
677         int default_fg = line_info[LINE_DEFAULT].fg;
678         enum line_type type;
680         start_color();
682         if (assume_default_colors(default_fg, default_bg) == ERR) {
683                 default_bg = COLOR_BLACK;
684                 default_fg = COLOR_WHITE;
685         }
687         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
688                 struct line_info *info = &line_info[type];
689                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
690                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
692                 init_pair(type, fg, bg);
693         }
696 struct line {
697         enum line_type type;
699         /* State flags */
700         unsigned int selected:1;
701         unsigned int dirty:1;
703         void *data;             /* User data */
704 };
707 /*
708  * Keys
709  */
711 struct keybinding {
712         int alias;
713         enum request request;
714         struct keybinding *next;
715 };
717 static struct keybinding default_keybindings[] = {
718         /* View switching */
719         { 'm',          REQ_VIEW_MAIN },
720         { 'd',          REQ_VIEW_DIFF },
721         { 'l',          REQ_VIEW_LOG },
722         { 't',          REQ_VIEW_TREE },
723         { 'f',          REQ_VIEW_BLOB },
724         { 'B',          REQ_VIEW_BLAME },
725         { 'p',          REQ_VIEW_PAGER },
726         { 'h',          REQ_VIEW_HELP },
727         { 'S',          REQ_VIEW_STATUS },
728         { 'c',          REQ_VIEW_STAGE },
730         /* View manipulation */
731         { 'q',          REQ_VIEW_CLOSE },
732         { KEY_TAB,      REQ_VIEW_NEXT },
733         { KEY_RETURN,   REQ_ENTER },
734         { KEY_UP,       REQ_PREVIOUS },
735         { KEY_DOWN,     REQ_NEXT },
736         { 'R',          REQ_REFRESH },
737         { 'M',          REQ_MAXIMIZE },
739         /* Cursor navigation */
740         { 'k',          REQ_MOVE_UP },
741         { 'j',          REQ_MOVE_DOWN },
742         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
743         { KEY_END,      REQ_MOVE_LAST_LINE },
744         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
745         { ' ',          REQ_MOVE_PAGE_DOWN },
746         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
747         { 'b',          REQ_MOVE_PAGE_UP },
748         { '-',          REQ_MOVE_PAGE_UP },
750         /* Scrolling */
751         { KEY_IC,       REQ_SCROLL_LINE_UP },
752         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
753         { 'w',          REQ_SCROLL_PAGE_UP },
754         { 's',          REQ_SCROLL_PAGE_DOWN },
756         /* Searching */
757         { '/',          REQ_SEARCH },
758         { '?',          REQ_SEARCH_BACK },
759         { 'n',          REQ_FIND_NEXT },
760         { 'N',          REQ_FIND_PREV },
762         /* Misc */
763         { 'Q',          REQ_QUIT },
764         { 'z',          REQ_STOP_LOADING },
765         { 'v',          REQ_SHOW_VERSION },
766         { 'r',          REQ_SCREEN_REDRAW },
767         { '.',          REQ_TOGGLE_LINENO },
768         { 'D',          REQ_TOGGLE_DATE },
769         { 'A',          REQ_TOGGLE_AUTHOR },
770         { 'g',          REQ_TOGGLE_REV_GRAPH },
771         { 'F',          REQ_TOGGLE_REFS },
772         { ':',          REQ_PROMPT },
773         { 'u',          REQ_STATUS_UPDATE },
774         { 'M',          REQ_STATUS_MERGE },
775         { ',',          REQ_TREE_PARENT },
776         { 'e',          REQ_EDIT },
778         /* Using the ncurses SIGWINCH handler. */
779         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
780 };
782 #define KEYMAP_INFO \
783         KEYMAP_(GENERIC), \
784         KEYMAP_(MAIN), \
785         KEYMAP_(DIFF), \
786         KEYMAP_(LOG), \
787         KEYMAP_(TREE), \
788         KEYMAP_(BLOB), \
789         KEYMAP_(BLAME), \
790         KEYMAP_(PAGER), \
791         KEYMAP_(HELP), \
792         KEYMAP_(STATUS), \
793         KEYMAP_(STAGE)
795 enum keymap {
796 #define KEYMAP_(name) KEYMAP_##name
797         KEYMAP_INFO
798 #undef  KEYMAP_
799 };
801 static struct int_map keymap_table[] = {
802 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
803         KEYMAP_INFO
804 #undef  KEYMAP_
805 };
807 #define set_keymap(map, name) \
808         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
810 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
812 static void
813 add_keybinding(enum keymap keymap, enum request request, int key)
815         struct keybinding *keybinding;
817         keybinding = calloc(1, sizeof(*keybinding));
818         if (!keybinding)
819                 die("Failed to allocate keybinding");
821         keybinding->alias = key;
822         keybinding->request = request;
823         keybinding->next = keybindings[keymap];
824         keybindings[keymap] = keybinding;
827 /* Looks for a key binding first in the given map, then in the generic map, and
828  * lastly in the default keybindings. */
829 static enum request
830 get_keybinding(enum keymap keymap, int key)
832         struct keybinding *kbd;
833         int i;
835         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
836                 if (kbd->alias == key)
837                         return kbd->request;
839         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
840                 if (kbd->alias == key)
841                         return kbd->request;
843         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
844                 if (default_keybindings[i].alias == key)
845                         return default_keybindings[i].request;
847         return (enum request) key;
851 struct key {
852         char *name;
853         int value;
854 };
856 static struct key key_table[] = {
857         { "Enter",      KEY_RETURN },
858         { "Space",      ' ' },
859         { "Backspace",  KEY_BACKSPACE },
860         { "Tab",        KEY_TAB },
861         { "Escape",     KEY_ESC },
862         { "Left",       KEY_LEFT },
863         { "Right",      KEY_RIGHT },
864         { "Up",         KEY_UP },
865         { "Down",       KEY_DOWN },
866         { "Insert",     KEY_IC },
867         { "Delete",     KEY_DC },
868         { "Hash",       '#' },
869         { "Home",       KEY_HOME },
870         { "End",        KEY_END },
871         { "PageUp",     KEY_PPAGE },
872         { "PageDown",   KEY_NPAGE },
873         { "F1",         KEY_F(1) },
874         { "F2",         KEY_F(2) },
875         { "F3",         KEY_F(3) },
876         { "F4",         KEY_F(4) },
877         { "F5",         KEY_F(5) },
878         { "F6",         KEY_F(6) },
879         { "F7",         KEY_F(7) },
880         { "F8",         KEY_F(8) },
881         { "F9",         KEY_F(9) },
882         { "F10",        KEY_F(10) },
883         { "F11",        KEY_F(11) },
884         { "F12",        KEY_F(12) },
885 };
887 static int
888 get_key_value(const char *name)
890         int i;
892         for (i = 0; i < ARRAY_SIZE(key_table); i++)
893                 if (!strcasecmp(key_table[i].name, name))
894                         return key_table[i].value;
896         if (strlen(name) == 1 && isprint(*name))
897                 return (int) *name;
899         return ERR;
902 static char *
903 get_key_name(int key_value)
905         static char key_char[] = "'X'";
906         char *seq = NULL;
907         int key;
909         for (key = 0; key < ARRAY_SIZE(key_table); key++)
910                 if (key_table[key].value == key_value)
911                         seq = key_table[key].name;
913         if (seq == NULL &&
914             key_value < 127 &&
915             isprint(key_value)) {
916                 key_char[1] = (char) key_value;
917                 seq = key_char;
918         }
920         return seq ? seq : "'?'";
923 static char *
924 get_key(enum request request)
926         static char buf[BUFSIZ];
927         size_t pos = 0;
928         char *sep = "";
929         int i;
931         buf[pos] = 0;
933         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
934                 struct keybinding *keybinding = &default_keybindings[i];
936                 if (keybinding->request != request)
937                         continue;
939                 if (!string_format_from(buf, &pos, "%s%s", sep,
940                                         get_key_name(keybinding->alias)))
941                         return "Too many keybindings!";
942                 sep = ", ";
943         }
945         return buf;
948 struct run_request {
949         enum keymap keymap;
950         int key;
951         char cmd[SIZEOF_STR];
952 };
954 static struct run_request *run_request;
955 static size_t run_requests;
957 static enum request
958 add_run_request(enum keymap keymap, int key, int argc, char **argv)
960         struct run_request *tmp;
961         struct run_request req = { keymap, key };
962         size_t bufpos;
964         for (bufpos = 0; argc > 0; argc--, argv++)
965                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
966                         return REQ_NONE;
968         req.cmd[bufpos - 1] = 0;
970         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
971         if (!tmp)
972                 return REQ_NONE;
974         run_request = tmp;
975         run_request[run_requests++] = req;
977         return REQ_NONE + run_requests;
980 static struct run_request *
981 get_run_request(enum request request)
983         if (request <= REQ_NONE)
984                 return NULL;
985         return &run_request[request - REQ_NONE - 1];
988 static void
989 add_builtin_run_requests(void)
991         struct {
992                 enum keymap keymap;
993                 int key;
994                 char *argv[1];
995         } reqs[] = {
996                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
997                 { KEYMAP_GENERIC, 'G', { "git gc" } },
998         };
999         int i;
1001         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1002                 enum request req;
1004                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1005                 if (req != REQ_NONE)
1006                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1007         }
1010 /*
1011  * User config file handling.
1012  */
1014 static struct int_map color_map[] = {
1015 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1016         COLOR_MAP(DEFAULT),
1017         COLOR_MAP(BLACK),
1018         COLOR_MAP(BLUE),
1019         COLOR_MAP(CYAN),
1020         COLOR_MAP(GREEN),
1021         COLOR_MAP(MAGENTA),
1022         COLOR_MAP(RED),
1023         COLOR_MAP(WHITE),
1024         COLOR_MAP(YELLOW),
1025 };
1027 #define set_color(color, name) \
1028         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1030 static struct int_map attr_map[] = {
1031 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1032         ATTR_MAP(NORMAL),
1033         ATTR_MAP(BLINK),
1034         ATTR_MAP(BOLD),
1035         ATTR_MAP(DIM),
1036         ATTR_MAP(REVERSE),
1037         ATTR_MAP(STANDOUT),
1038         ATTR_MAP(UNDERLINE),
1039 };
1041 #define set_attribute(attr, name) \
1042         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1044 static int   config_lineno;
1045 static bool  config_errors;
1046 static char *config_msg;
1048 /* Wants: object fgcolor bgcolor [attr] */
1049 static int
1050 option_color_command(int argc, char *argv[])
1052         struct line_info *info;
1054         if (argc != 3 && argc != 4) {
1055                 config_msg = "Wrong number of arguments given to color command";
1056                 return ERR;
1057         }
1059         info = get_line_info(argv[0]);
1060         if (!info) {
1061                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1062                         info = get_line_info("delimiter");
1064                 } else {
1065                         config_msg = "Unknown color name";
1066                         return ERR;
1067                 }
1068         }
1070         if (set_color(&info->fg, argv[1]) == ERR ||
1071             set_color(&info->bg, argv[2]) == ERR) {
1072                 config_msg = "Unknown color";
1073                 return ERR;
1074         }
1076         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1077                 config_msg = "Unknown attribute";
1078                 return ERR;
1079         }
1081         return OK;
1084 static bool parse_bool(const char *s)
1086         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1087                 !strcmp(s, "yes")) ? TRUE : FALSE;
1090 /* Wants: name = value */
1091 static int
1092 option_set_command(int argc, char *argv[])
1094         if (argc != 3) {
1095                 config_msg = "Wrong number of arguments given to set command";
1096                 return ERR;
1097         }
1099         if (strcmp(argv[1], "=")) {
1100                 config_msg = "No value assigned";
1101                 return ERR;
1102         }
1104         if (!strcmp(argv[0], "show-author")) {
1105                 opt_author = parse_bool(argv[2]);
1106                 return OK;
1107         }
1109         if (!strcmp(argv[0], "show-date")) {
1110                 opt_date = parse_bool(argv[2]);
1111                 return OK;
1112         }
1114         if (!strcmp(argv[0], "show-rev-graph")) {
1115                 opt_rev_graph = parse_bool(argv[2]);
1116                 return OK;
1117         }
1119         if (!strcmp(argv[0], "show-refs")) {
1120                 opt_show_refs = parse_bool(argv[2]);
1121                 return OK;
1122         }
1124         if (!strcmp(argv[0], "show-line-numbers")) {
1125                 opt_line_number = parse_bool(argv[2]);
1126                 return OK;
1127         }
1129         if (!strcmp(argv[0], "line-number-interval")) {
1130                 opt_num_interval = atoi(argv[2]);
1131                 return OK;
1132         }
1134         if (!strcmp(argv[0], "tab-size")) {
1135                 opt_tab_size = atoi(argv[2]);
1136                 return OK;
1137         }
1139         if (!strcmp(argv[0], "commit-encoding")) {
1140                 char *arg = argv[2];
1141                 int delimiter = *arg;
1142                 int i;
1144                 switch (delimiter) {
1145                 case '"':
1146                 case '\'':
1147                         for (arg++, i = 0; arg[i]; i++)
1148                                 if (arg[i] == delimiter) {
1149                                         arg[i] = 0;
1150                                         break;
1151                                 }
1152                 default:
1153                         string_ncopy(opt_encoding, arg, strlen(arg));
1154                         return OK;
1155                 }
1156         }
1158         config_msg = "Unknown variable name";
1159         return ERR;
1162 /* Wants: mode request key */
1163 static int
1164 option_bind_command(int argc, char *argv[])
1166         enum request request;
1167         int keymap;
1168         int key;
1170         if (argc < 3) {
1171                 config_msg = "Wrong number of arguments given to bind command";
1172                 return ERR;
1173         }
1175         if (set_keymap(&keymap, argv[0]) == ERR) {
1176                 config_msg = "Unknown key map";
1177                 return ERR;
1178         }
1180         key = get_key_value(argv[1]);
1181         if (key == ERR) {
1182                 config_msg = "Unknown key";
1183                 return ERR;
1184         }
1186         request = get_request(argv[2]);
1187         if (request == REQ_NONE) {
1188                 const char *obsolete[] = { "cherry-pick" };
1189                 size_t namelen = strlen(argv[2]);
1190                 int i;
1192                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1193                         if (namelen == strlen(obsolete[i]) &&
1194                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1195                                 config_msg = "Obsolete request name";
1196                                 return ERR;
1197                         }
1198                 }
1199         }
1200         if (request == REQ_NONE && *argv[2]++ == '!')
1201                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1202         if (request == REQ_NONE) {
1203                 config_msg = "Unknown request name";
1204                 return ERR;
1205         }
1207         add_keybinding(keymap, request, key);
1209         return OK;
1212 static int
1213 set_option(char *opt, char *value)
1215         char *argv[16];
1216         int valuelen;
1217         int argc = 0;
1219         /* Tokenize */
1220         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1221                 argv[argc++] = value;
1222                 value += valuelen;
1224                 /* Nothing more to tokenize or last available token. */
1225                 if (!*value || argc >= ARRAY_SIZE(argv))
1226                         break;
1228                 *value++ = 0;
1229                 while (isspace(*value))
1230                         value++;
1231         }
1233         if (!strcmp(opt, "color"))
1234                 return option_color_command(argc, argv);
1236         if (!strcmp(opt, "set"))
1237                 return option_set_command(argc, argv);
1239         if (!strcmp(opt, "bind"))
1240                 return option_bind_command(argc, argv);
1242         config_msg = "Unknown option command";
1243         return ERR;
1246 static int
1247 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1249         int status = OK;
1251         config_lineno++;
1252         config_msg = "Internal error";
1254         /* Check for comment markers, since read_properties() will
1255          * only ensure opt and value are split at first " \t". */
1256         optlen = strcspn(opt, "#");
1257         if (optlen == 0)
1258                 return OK;
1260         if (opt[optlen] != 0) {
1261                 config_msg = "No option value";
1262                 status = ERR;
1264         }  else {
1265                 /* Look for comment endings in the value. */
1266                 size_t len = strcspn(value, "#");
1268                 if (len < valuelen) {
1269                         valuelen = len;
1270                         value[valuelen] = 0;
1271                 }
1273                 status = set_option(opt, value);
1274         }
1276         if (status == ERR) {
1277                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1278                         config_lineno, (int) optlen, opt, config_msg);
1279                 config_errors = TRUE;
1280         }
1282         /* Always keep going if errors are encountered. */
1283         return OK;
1286 static void
1287 load_option_file(const char *path)
1289         FILE *file;
1291         /* It's ok that the file doesn't exist. */
1292         file = fopen(path, "r");
1293         if (!file)
1294                 return;
1296         config_lineno = 0;
1297         config_errors = FALSE;
1299         if (read_properties(file, " \t", read_option) == ERR ||
1300             config_errors == TRUE)
1301                 fprintf(stderr, "Errors while loading %s.\n", path);
1304 static int
1305 load_options(void)
1307         char *home = getenv("HOME");
1308         char *tigrc_user = getenv("TIGRC_USER");
1309         char *tigrc_system = getenv("TIGRC_SYSTEM");
1310         char buf[SIZEOF_STR];
1312         add_builtin_run_requests();
1314         if (!tigrc_system) {
1315                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1316                         return ERR;
1317                 tigrc_system = buf;
1318         }
1319         load_option_file(tigrc_system);
1321         if (!tigrc_user) {
1322                 if (!home || !string_format(buf, "%s/.tigrc", home))
1323                         return ERR;
1324                 tigrc_user = buf;
1325         }
1326         load_option_file(tigrc_user);
1328         return OK;
1332 /*
1333  * The viewer
1334  */
1336 struct view;
1337 struct view_ops;
1339 /* The display array of active views and the index of the current view. */
1340 static struct view *display[2];
1341 static unsigned int current_view;
1343 /* Reading from the prompt? */
1344 static bool input_mode = FALSE;
1346 #define foreach_displayed_view(view, i) \
1347         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1349 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1351 /* Current head and commit ID */
1352 static char ref_blob[SIZEOF_REF]        = "";
1353 static char ref_commit[SIZEOF_REF]      = "HEAD";
1354 static char ref_head[SIZEOF_REF]        = "HEAD";
1356 struct view {
1357         const char *name;       /* View name */
1358         const char *cmd_fmt;    /* Default command line format */
1359         const char *cmd_env;    /* Command line set via environment */
1360         const char *id;         /* Points to either of ref_{head,commit,blob} */
1362         struct view_ops *ops;   /* View operations */
1364         enum keymap keymap;     /* What keymap does this view have */
1365         bool git_dir;           /* Whether the view requires a git directory. */
1367         char cmd[SIZEOF_STR];   /* Command buffer */
1368         char ref[SIZEOF_REF];   /* Hovered commit reference */
1369         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1371         int height, width;      /* The width and height of the main window */
1372         WINDOW *win;            /* The main window */
1373         WINDOW *title;          /* The title window living below the main window */
1375         /* Navigation */
1376         unsigned long offset;   /* Offset of the window top */
1377         unsigned long lineno;   /* Current line number */
1379         /* Searching */
1380         char grep[SIZEOF_STR];  /* Search string */
1381         regex_t *regex;         /* Pre-compiled regex */
1383         /* If non-NULL, points to the view that opened this view. If this view
1384          * is closed tig will switch back to the parent view. */
1385         struct view *parent;
1387         /* Buffering */
1388         size_t lines;           /* Total number of lines */
1389         struct line *line;      /* Line index */
1390         size_t line_alloc;      /* Total number of allocated lines */
1391         size_t line_size;       /* Total number of used lines */
1392         unsigned int digits;    /* Number of digits in the lines member. */
1394         /* Loading */
1395         FILE *pipe;
1396         time_t start_time;
1397 };
1399 struct view_ops {
1400         /* What type of content being displayed. Used in the title bar. */
1401         const char *type;
1402         /* Open and reads in all view content. */
1403         bool (*open)(struct view *view);
1404         /* Read one line; updates view->line. */
1405         bool (*read)(struct view *view, char *data);
1406         /* Draw one line; @lineno must be < view->height. */
1407         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1408         /* Depending on view handle a special requests. */
1409         enum request (*request)(struct view *view, enum request request, struct line *line);
1410         /* Search for regex in a line. */
1411         bool (*grep)(struct view *view, struct line *line);
1412         /* Select line */
1413         void (*select)(struct view *view, struct line *line);
1414 };
1416 static struct view_ops pager_ops;
1417 static struct view_ops main_ops;
1418 static struct view_ops tree_ops;
1419 static struct view_ops blob_ops;
1420 static struct view_ops blame_ops;
1421 static struct view_ops help_ops;
1422 static struct view_ops status_ops;
1423 static struct view_ops stage_ops;
1425 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1426         { name, cmd, #env, ref, ops, map, git }
1428 #define VIEW_(id, name, ops, git, ref) \
1429         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1432 static struct view views[] = {
1433         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1434         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1435         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1436         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1437         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1438         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1439         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1440         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1441         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1442         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1443 };
1445 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1446 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1448 #define foreach_view(view, i) \
1449         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1451 #define view_is_displayed(view) \
1452         (view == display[0] || view == display[1])
1454 static int
1455 draw_text(struct view *view, const char *string, int max_len,
1456           bool use_tilde, bool selected)
1458         int len = 0;
1459         int trimmed = FALSE;
1461         if (max_len <= 0)
1462                 return 0;
1464         if (opt_utf8) {
1465                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1466         } else {
1467                 len = strlen(string);
1468                 if (len > max_len) {
1469                         if (use_tilde) {
1470                                 max_len -= 1;
1471                         }
1472                         len = max_len;
1473                         trimmed = TRUE;
1474                 }
1475         }
1477         waddnstr(view->win, string, len);
1478         if (trimmed && use_tilde) {
1479                 if (!selected)
1480                         wattrset(view->win, get_line_attr(LINE_DELIMITER));
1481                 waddch(view->win, '~');
1482                 len++;
1483         }
1485         return len;
1488 static int
1489 draw_lineno(struct view *view, unsigned int lineno, int max, bool selected)
1491         static char fmt[] = "%1ld";
1492         char number[10] = "          ";
1493         int max_number = MIN(view->digits, STRING_SIZE(number));
1494         bool showtrimmed = FALSE;
1495         int col;
1497         lineno += view->offset + 1;
1498         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1499                 if (view->digits <= 9)
1500                         fmt[1] = '0' + view->digits;
1502                 if (!string_format(number, fmt, lineno))
1503                         number[0] = 0;
1504                 showtrimmed = TRUE;
1505         }
1507         if (max < max_number)
1508                 max_number = max;
1510         if (!selected)
1511                 wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
1512         col = draw_text(view, number, max_number, showtrimmed, selected);
1513         if (col < max) {
1514                 if (!selected)
1515                         wattrset(view->win, A_NORMAL);
1516                 waddch(view->win, ACS_VLINE);
1517                 col++;
1518         }
1519         if (col < max) {
1520                 waddch(view->win, ' ');
1521                 col++;
1522         }
1524         return col;
1527 static bool
1528 draw_view_line(struct view *view, unsigned int lineno)
1530         struct line *line;
1531         bool selected = (view->offset + lineno == view->lineno);
1532         bool draw_ok;
1534         assert(view_is_displayed(view));
1536         if (view->offset + lineno >= view->lines)
1537                 return FALSE;
1539         line = &view->line[view->offset + lineno];
1541         if (selected) {
1542                 line->selected = TRUE;
1543                 view->ops->select(view, line);
1544         } else if (line->selected) {
1545                 line->selected = FALSE;
1546                 wmove(view->win, lineno, 0);
1547                 wclrtoeol(view->win);
1548         }
1550         scrollok(view->win, FALSE);
1551         draw_ok = view->ops->draw(view, line, lineno, selected);
1552         scrollok(view->win, TRUE);
1554         return draw_ok;
1557 static void
1558 redraw_view_dirty(struct view *view)
1560         bool dirty = FALSE;
1561         int lineno;
1563         for (lineno = 0; lineno < view->height; lineno++) {
1564                 struct line *line = &view->line[view->offset + lineno];
1566                 if (!line->dirty)
1567                         continue;
1568                 line->dirty = 0;
1569                 dirty = TRUE;
1570                 if (!draw_view_line(view, lineno))
1571                         break;
1572         }
1574         if (!dirty)
1575                 return;
1576         redrawwin(view->win);
1577         if (input_mode)
1578                 wnoutrefresh(view->win);
1579         else
1580                 wrefresh(view->win);
1583 static void
1584 redraw_view_from(struct view *view, int lineno)
1586         assert(0 <= lineno && lineno < view->height);
1588         for (; lineno < view->height; lineno++) {
1589                 if (!draw_view_line(view, lineno))
1590                         break;
1591         }
1593         redrawwin(view->win);
1594         if (input_mode)
1595                 wnoutrefresh(view->win);
1596         else
1597                 wrefresh(view->win);
1600 static void
1601 redraw_view(struct view *view)
1603         wclear(view->win);
1604         redraw_view_from(view, 0);
1608 static void
1609 update_view_title(struct view *view)
1611         char buf[SIZEOF_STR];
1612         char state[SIZEOF_STR];
1613         size_t bufpos = 0, statelen = 0;
1615         assert(view_is_displayed(view));
1617         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1618                 unsigned int view_lines = view->offset + view->height;
1619                 unsigned int lines = view->lines
1620                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1621                                    : 0;
1623                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1624                                    view->ops->type,
1625                                    view->lineno + 1,
1626                                    view->lines,
1627                                    lines);
1629                 if (view->pipe) {
1630                         time_t secs = time(NULL) - view->start_time;
1632                         /* Three git seconds are a long time ... */
1633                         if (secs > 2)
1634                                 string_format_from(state, &statelen, " %lds", secs);
1635                 }
1636         }
1638         string_format_from(buf, &bufpos, "[%s]", view->name);
1639         if (*view->ref && bufpos < view->width) {
1640                 size_t refsize = strlen(view->ref);
1641                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1643                 if (minsize < view->width)
1644                         refsize = view->width - minsize + 7;
1645                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1646         }
1648         if (statelen && bufpos < view->width) {
1649                 string_format_from(buf, &bufpos, " %s", state);
1650         }
1652         if (view == display[current_view])
1653                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1654         else
1655                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1657         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1658         wclrtoeol(view->title);
1659         wmove(view->title, 0, view->width - 1);
1661         if (input_mode)
1662                 wnoutrefresh(view->title);
1663         else
1664                 wrefresh(view->title);
1667 static void
1668 resize_display(void)
1670         int offset, i;
1671         struct view *base = display[0];
1672         struct view *view = display[1] ? display[1] : display[0];
1674         /* Setup window dimensions */
1676         getmaxyx(stdscr, base->height, base->width);
1678         /* Make room for the status window. */
1679         base->height -= 1;
1681         if (view != base) {
1682                 /* Horizontal split. */
1683                 view->width   = base->width;
1684                 view->height  = SCALE_SPLIT_VIEW(base->height);
1685                 base->height -= view->height;
1687                 /* Make room for the title bar. */
1688                 view->height -= 1;
1689         }
1691         /* Make room for the title bar. */
1692         base->height -= 1;
1694         offset = 0;
1696         foreach_displayed_view (view, i) {
1697                 if (!view->win) {
1698                         view->win = newwin(view->height, 0, offset, 0);
1699                         if (!view->win)
1700                                 die("Failed to create %s view", view->name);
1702                         scrollok(view->win, TRUE);
1704                         view->title = newwin(1, 0, offset + view->height, 0);
1705                         if (!view->title)
1706                                 die("Failed to create title window");
1708                 } else {
1709                         wresize(view->win, view->height, view->width);
1710                         mvwin(view->win,   offset, 0);
1711                         mvwin(view->title, offset + view->height, 0);
1712                 }
1714                 offset += view->height + 1;
1715         }
1718 static void
1719 redraw_display(void)
1721         struct view *view;
1722         int i;
1724         foreach_displayed_view (view, i) {
1725                 redraw_view(view);
1726                 update_view_title(view);
1727         }
1730 static void
1731 update_display_cursor(struct view *view)
1733         /* Move the cursor to the right-most column of the cursor line.
1734          *
1735          * XXX: This could turn out to be a bit expensive, but it ensures that
1736          * the cursor does not jump around. */
1737         if (view->lines) {
1738                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1739                 wrefresh(view->win);
1740         }
1743 /*
1744  * Navigation
1745  */
1747 /* Scrolling backend */
1748 static void
1749 do_scroll_view(struct view *view, int lines)
1751         bool redraw_current_line = FALSE;
1753         /* The rendering expects the new offset. */
1754         view->offset += lines;
1756         assert(0 <= view->offset && view->offset < view->lines);
1757         assert(lines);
1759         /* Move current line into the view. */
1760         if (view->lineno < view->offset) {
1761                 view->lineno = view->offset;
1762                 redraw_current_line = TRUE;
1763         } else if (view->lineno >= view->offset + view->height) {
1764                 view->lineno = view->offset + view->height - 1;
1765                 redraw_current_line = TRUE;
1766         }
1768         assert(view->offset <= view->lineno && view->lineno < view->lines);
1770         /* Redraw the whole screen if scrolling is pointless. */
1771         if (view->height < ABS(lines)) {
1772                 redraw_view(view);
1774         } else {
1775                 int line = lines > 0 ? view->height - lines : 0;
1776                 int end = line + ABS(lines);
1778                 wscrl(view->win, lines);
1780                 for (; line < end; line++) {
1781                         if (!draw_view_line(view, line))
1782                                 break;
1783                 }
1785                 if (redraw_current_line)
1786                         draw_view_line(view, view->lineno - view->offset);
1787         }
1789         redrawwin(view->win);
1790         wrefresh(view->win);
1791         report("");
1794 /* Scroll frontend */
1795 static void
1796 scroll_view(struct view *view, enum request request)
1798         int lines = 1;
1800         assert(view_is_displayed(view));
1802         switch (request) {
1803         case REQ_SCROLL_PAGE_DOWN:
1804                 lines = view->height;
1805         case REQ_SCROLL_LINE_DOWN:
1806                 if (view->offset + lines > view->lines)
1807                         lines = view->lines - view->offset;
1809                 if (lines == 0 || view->offset + view->height >= view->lines) {
1810                         report("Cannot scroll beyond the last line");
1811                         return;
1812                 }
1813                 break;
1815         case REQ_SCROLL_PAGE_UP:
1816                 lines = view->height;
1817         case REQ_SCROLL_LINE_UP:
1818                 if (lines > view->offset)
1819                         lines = view->offset;
1821                 if (lines == 0) {
1822                         report("Cannot scroll beyond the first line");
1823                         return;
1824                 }
1826                 lines = -lines;
1827                 break;
1829         default:
1830                 die("request %d not handled in switch", request);
1831         }
1833         do_scroll_view(view, lines);
1836 /* Cursor moving */
1837 static void
1838 move_view(struct view *view, enum request request)
1840         int scroll_steps = 0;
1841         int steps;
1843         switch (request) {
1844         case REQ_MOVE_FIRST_LINE:
1845                 steps = -view->lineno;
1846                 break;
1848         case REQ_MOVE_LAST_LINE:
1849                 steps = view->lines - view->lineno - 1;
1850                 break;
1852         case REQ_MOVE_PAGE_UP:
1853                 steps = view->height > view->lineno
1854                       ? -view->lineno : -view->height;
1855                 break;
1857         case REQ_MOVE_PAGE_DOWN:
1858                 steps = view->lineno + view->height >= view->lines
1859                       ? view->lines - view->lineno - 1 : view->height;
1860                 break;
1862         case REQ_MOVE_UP:
1863                 steps = -1;
1864                 break;
1866         case REQ_MOVE_DOWN:
1867                 steps = 1;
1868                 break;
1870         default:
1871                 die("request %d not handled in switch", request);
1872         }
1874         if (steps <= 0 && view->lineno == 0) {
1875                 report("Cannot move beyond the first line");
1876                 return;
1878         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1879                 report("Cannot move beyond the last line");
1880                 return;
1881         }
1883         /* Move the current line */
1884         view->lineno += steps;
1885         assert(0 <= view->lineno && view->lineno < view->lines);
1887         /* Check whether the view needs to be scrolled */
1888         if (view->lineno < view->offset ||
1889             view->lineno >= view->offset + view->height) {
1890                 scroll_steps = steps;
1891                 if (steps < 0 && -steps > view->offset) {
1892                         scroll_steps = -view->offset;
1894                 } else if (steps > 0) {
1895                         if (view->lineno == view->lines - 1 &&
1896                             view->lines > view->height) {
1897                                 scroll_steps = view->lines - view->offset - 1;
1898                                 if (scroll_steps >= view->height)
1899                                         scroll_steps -= view->height - 1;
1900                         }
1901                 }
1902         }
1904         if (!view_is_displayed(view)) {
1905                 view->offset += scroll_steps;
1906                 assert(0 <= view->offset && view->offset < view->lines);
1907                 view->ops->select(view, &view->line[view->lineno]);
1908                 return;
1909         }
1911         /* Repaint the old "current" line if we be scrolling */
1912         if (ABS(steps) < view->height)
1913                 draw_view_line(view, view->lineno - steps - view->offset);
1915         if (scroll_steps) {
1916                 do_scroll_view(view, scroll_steps);
1917                 return;
1918         }
1920         /* Draw the current line */
1921         draw_view_line(view, view->lineno - view->offset);
1923         redrawwin(view->win);
1924         wrefresh(view->win);
1925         report("");
1929 /*
1930  * Searching
1931  */
1933 static void search_view(struct view *view, enum request request);
1935 static bool
1936 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1938         assert(view_is_displayed(view));
1940         if (!view->ops->grep(view, line))
1941                 return FALSE;
1943         if (lineno - view->offset >= view->height) {
1944                 view->offset = lineno;
1945                 view->lineno = lineno;
1946                 redraw_view(view);
1948         } else {
1949                 unsigned long old_lineno = view->lineno - view->offset;
1951                 view->lineno = lineno;
1952                 draw_view_line(view, old_lineno);
1954                 draw_view_line(view, view->lineno - view->offset);
1955                 redrawwin(view->win);
1956                 wrefresh(view->win);
1957         }
1959         report("Line %ld matches '%s'", lineno + 1, view->grep);
1960         return TRUE;
1963 static void
1964 find_next(struct view *view, enum request request)
1966         unsigned long lineno = view->lineno;
1967         int direction;
1969         if (!*view->grep) {
1970                 if (!*opt_search)
1971                         report("No previous search");
1972                 else
1973                         search_view(view, request);
1974                 return;
1975         }
1977         switch (request) {
1978         case REQ_SEARCH:
1979         case REQ_FIND_NEXT:
1980                 direction = 1;
1981                 break;
1983         case REQ_SEARCH_BACK:
1984         case REQ_FIND_PREV:
1985                 direction = -1;
1986                 break;
1988         default:
1989                 return;
1990         }
1992         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1993                 lineno += direction;
1995         /* Note, lineno is unsigned long so will wrap around in which case it
1996          * will become bigger than view->lines. */
1997         for (; lineno < view->lines; lineno += direction) {
1998                 struct line *line = &view->line[lineno];
2000                 if (find_next_line(view, lineno, line))
2001                         return;
2002         }
2004         report("No match found for '%s'", view->grep);
2007 static void
2008 search_view(struct view *view, enum request request)
2010         int regex_err;
2012         if (view->regex) {
2013                 regfree(view->regex);
2014                 *view->grep = 0;
2015         } else {
2016                 view->regex = calloc(1, sizeof(*view->regex));
2017                 if (!view->regex)
2018                         return;
2019         }
2021         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2022         if (regex_err != 0) {
2023                 char buf[SIZEOF_STR] = "unknown error";
2025                 regerror(regex_err, view->regex, buf, sizeof(buf));
2026                 report("Search failed: %s", buf);
2027                 return;
2028         }
2030         string_copy(view->grep, opt_search);
2032         find_next(view, request);
2035 /*
2036  * Incremental updating
2037  */
2039 static void
2040 end_update(struct view *view)
2042         if (!view->pipe)
2043                 return;
2044         set_nonblocking_input(FALSE);
2045         if (view->pipe == stdin)
2046                 fclose(view->pipe);
2047         else
2048                 pclose(view->pipe);
2049         view->pipe = NULL;
2052 static bool
2053 begin_update(struct view *view)
2055         if (view->pipe)
2056                 end_update(view);
2058         if (opt_cmd[0]) {
2059                 string_copy(view->cmd, opt_cmd);
2060                 opt_cmd[0] = 0;
2061                 /* When running random commands, initially show the
2062                  * command in the title. However, it maybe later be
2063                  * overwritten if a commit line is selected. */
2064                 if (view == VIEW(REQ_VIEW_PAGER))
2065                         string_copy(view->ref, view->cmd);
2066                 else
2067                         view->ref[0] = 0;
2069         } else if (view == VIEW(REQ_VIEW_TREE)) {
2070                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2071                 char path[SIZEOF_STR];
2073                 if (strcmp(view->vid, view->id))
2074                         opt_path[0] = path[0] = 0;
2075                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2076                         return FALSE;
2078                 if (!string_format(view->cmd, format, view->id, path))
2079                         return FALSE;
2081         } else {
2082                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2083                 const char *id = view->id;
2085                 if (!string_format(view->cmd, format, id, id, id, id, id))
2086                         return FALSE;
2088                 /* Put the current ref_* value to the view title ref
2089                  * member. This is needed by the blob view. Most other
2090                  * views sets it automatically after loading because the
2091                  * first line is a commit line. */
2092                 string_copy_rev(view->ref, view->id);
2093         }
2095         /* Special case for the pager view. */
2096         if (opt_pipe) {
2097                 view->pipe = opt_pipe;
2098                 opt_pipe = NULL;
2099         } else {
2100                 view->pipe = popen(view->cmd, "r");
2101         }
2103         if (!view->pipe)
2104                 return FALSE;
2106         set_nonblocking_input(TRUE);
2108         view->offset = 0;
2109         view->lines  = 0;
2110         view->lineno = 0;
2111         string_copy_rev(view->vid, view->id);
2113         if (view->line) {
2114                 int i;
2116                 for (i = 0; i < view->lines; i++)
2117                         if (view->line[i].data)
2118                                 free(view->line[i].data);
2120                 free(view->line);
2121                 view->line = NULL;
2122         }
2124         view->start_time = time(NULL);
2126         return TRUE;
2129 #define ITEM_CHUNK_SIZE 256
2130 static void *
2131 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2133         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2134         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2136         if (mem == NULL || num_chunks != num_chunks_new) {
2137                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2138                 mem = realloc(mem, *size * item_size);
2139         }
2141         return mem;
2144 static struct line *
2145 realloc_lines(struct view *view, size_t line_size)
2147         size_t alloc = view->line_alloc;
2148         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2149                                          sizeof(*view->line));
2151         if (!tmp)
2152                 return NULL;
2154         view->line = tmp;
2155         view->line_alloc = alloc;
2156         view->line_size = line_size;
2157         return view->line;
2160 static bool
2161 update_view(struct view *view)
2163         char in_buffer[BUFSIZ];
2164         char out_buffer[BUFSIZ * 2];
2165         char *line;
2166         /* The number of lines to read. If too low it will cause too much
2167          * redrawing (and possible flickering), if too high responsiveness
2168          * will suffer. */
2169         unsigned long lines = view->height;
2170         int redraw_from = -1;
2172         if (!view->pipe)
2173                 return TRUE;
2175         /* Only redraw if lines are visible. */
2176         if (view->offset + view->height >= view->lines)
2177                 redraw_from = view->lines - view->offset;
2179         /* FIXME: This is probably not perfect for backgrounded views. */
2180         if (!realloc_lines(view, view->lines + lines))
2181                 goto alloc_error;
2183         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2184                 size_t linelen = strlen(line);
2186                 if (linelen)
2187                         line[linelen - 1] = 0;
2189                 if (opt_iconv != ICONV_NONE) {
2190                         ICONV_CONST char *inbuf = line;
2191                         size_t inlen = linelen;
2193                         char *outbuf = out_buffer;
2194                         size_t outlen = sizeof(out_buffer);
2196                         size_t ret;
2198                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2199                         if (ret != (size_t) -1) {
2200                                 line = out_buffer;
2201                                 linelen = strlen(out_buffer);
2202                         }
2203                 }
2205                 if (!view->ops->read(view, line))
2206                         goto alloc_error;
2208                 if (lines-- == 1)
2209                         break;
2210         }
2212         {
2213                 int digits;
2215                 lines = view->lines;
2216                 for (digits = 0; lines; digits++)
2217                         lines /= 10;
2219                 /* Keep the displayed view in sync with line number scaling. */
2220                 if (digits != view->digits) {
2221                         view->digits = digits;
2222                         redraw_from = 0;
2223                 }
2224         }
2226         if (!view_is_displayed(view))
2227                 goto check_pipe;
2229         if (view == VIEW(REQ_VIEW_TREE)) {
2230                 /* Clear the view and redraw everything since the tree sorting
2231                  * might have rearranged things. */
2232                 redraw_view(view);
2234         } else if (redraw_from >= 0) {
2235                 /* If this is an incremental update, redraw the previous line
2236                  * since for commits some members could have changed when
2237                  * loading the main view. */
2238                 if (redraw_from > 0)
2239                         redraw_from--;
2241                 /* Since revision graph visualization requires knowledge
2242                  * about the parent commit, it causes a further one-off
2243                  * needed to be redrawn for incremental updates. */
2244                 if (redraw_from > 0 && opt_rev_graph)
2245                         redraw_from--;
2247                 /* Incrementally draw avoids flickering. */
2248                 redraw_view_from(view, redraw_from);
2249         }
2251         if (view == VIEW(REQ_VIEW_BLAME))
2252                 redraw_view_dirty(view);
2254         /* Update the title _after_ the redraw so that if the redraw picks up a
2255          * commit reference in view->ref it'll be available here. */
2256         update_view_title(view);
2258 check_pipe:
2259         if (ferror(view->pipe)) {
2260                 report("Failed to read: %s", strerror(errno));
2261                 goto end;
2263         } else if (feof(view->pipe)) {
2264                 report("");
2265                 goto end;
2266         }
2268         return TRUE;
2270 alloc_error:
2271         report("Allocation failure");
2273 end:
2274         if (view->ops->read(view, NULL))
2275                 end_update(view);
2276         return FALSE;
2279 static struct line *
2280 add_line_data(struct view *view, void *data, enum line_type type)
2282         struct line *line = &view->line[view->lines++];
2284         memset(line, 0, sizeof(*line));
2285         line->type = type;
2286         line->data = data;
2288         return line;
2291 static struct line *
2292 add_line_text(struct view *view, char *data, enum line_type type)
2294         if (data)
2295                 data = strdup(data);
2297         return data ? add_line_data(view, data, type) : NULL;
2301 /*
2302  * View opening
2303  */
2305 enum open_flags {
2306         OPEN_DEFAULT = 0,       /* Use default view switching. */
2307         OPEN_SPLIT = 1,         /* Split current view. */
2308         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2309         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2310         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2311 };
2313 static void
2314 open_view(struct view *prev, enum request request, enum open_flags flags)
2316         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2317         bool split = !!(flags & OPEN_SPLIT);
2318         bool reload = !!(flags & OPEN_RELOAD);
2319         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2320         struct view *view = VIEW(request);
2321         int nviews = displayed_views();
2322         struct view *base_view = display[0];
2324         if (view == prev && nviews == 1 && !reload) {
2325                 report("Already in %s view", view->name);
2326                 return;
2327         }
2329         if (view->git_dir && !opt_git_dir[0]) {
2330                 report("The %s view is disabled in pager view", view->name);
2331                 return;
2332         }
2334         if (split) {
2335                 display[1] = view;
2336                 if (!backgrounded)
2337                         current_view = 1;
2338         } else if (!nomaximize) {
2339                 /* Maximize the current view. */
2340                 memset(display, 0, sizeof(display));
2341                 current_view = 0;
2342                 display[current_view] = view;
2343         }
2345         /* Resize the view when switching between split- and full-screen,
2346          * or when switching between two different full-screen views. */
2347         if (nviews != displayed_views() ||
2348             (nviews == 1 && base_view != display[0]))
2349                 resize_display();
2351         if (view->ops->open) {
2352                 if (!view->ops->open(view)) {
2353                         report("Failed to load %s view", view->name);
2354                         return;
2355                 }
2357         } else if ((reload || strcmp(view->vid, view->id)) &&
2358                    !begin_update(view)) {
2359                 report("Failed to load %s view", view->name);
2360                 return;
2361         }
2363         if (split && prev->lineno - prev->offset >= prev->height) {
2364                 /* Take the title line into account. */
2365                 int lines = prev->lineno - prev->offset - prev->height + 1;
2367                 /* Scroll the view that was split if the current line is
2368                  * outside the new limited view. */
2369                 do_scroll_view(prev, lines);
2370         }
2372         if (prev && view != prev) {
2373                 if (split && !backgrounded) {
2374                         /* "Blur" the previous view. */
2375                         update_view_title(prev);
2376                 }
2378                 view->parent = prev;
2379         }
2381         if (view->pipe && view->lines == 0) {
2382                 /* Clear the old view and let the incremental updating refill
2383                  * the screen. */
2384                 werase(view->win);
2385                 report("");
2386         } else {
2387                 redraw_view(view);
2388                 report("");
2389         }
2391         /* If the view is backgrounded the above calls to report()
2392          * won't redraw the view title. */
2393         if (backgrounded)
2394                 update_view_title(view);
2397 static void
2398 open_external_viewer(const char *cmd)
2400         def_prog_mode();           /* save current tty modes */
2401         endwin();                  /* restore original tty modes */
2402         system(cmd);
2403         fprintf(stderr, "Press Enter to continue");
2404         getc(stdin);
2405         reset_prog_mode();
2406         redraw_display();
2409 static void
2410 open_mergetool(const char *file)
2412         char cmd[SIZEOF_STR];
2413         char file_sq[SIZEOF_STR];
2415         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2416             string_format(cmd, "git mergetool %s", file_sq)) {
2417                 open_external_viewer(cmd);
2418         }
2421 static void
2422 open_editor(bool from_root, const char *file)
2424         char cmd[SIZEOF_STR];
2425         char file_sq[SIZEOF_STR];
2426         char *editor;
2427         char *prefix = from_root ? opt_cdup : "";
2429         editor = getenv("GIT_EDITOR");
2430         if (!editor && *opt_editor)
2431                 editor = opt_editor;
2432         if (!editor)
2433                 editor = getenv("VISUAL");
2434         if (!editor)
2435                 editor = getenv("EDITOR");
2436         if (!editor)
2437                 editor = "vi";
2439         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2440             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2441                 open_external_viewer(cmd);
2442         }
2445 static void
2446 open_run_request(enum request request)
2448         struct run_request *req = get_run_request(request);
2449         char buf[SIZEOF_STR * 2];
2450         size_t bufpos;
2451         char *cmd;
2453         if (!req) {
2454                 report("Unknown run request");
2455                 return;
2456         }
2458         bufpos = 0;
2459         cmd = req->cmd;
2461         while (cmd) {
2462                 char *next = strstr(cmd, "%(");
2463                 int len = next - cmd;
2464                 char *value;
2466                 if (!next) {
2467                         len = strlen(cmd);
2468                         value = "";
2470                 } else if (!strncmp(next, "%(head)", 7)) {
2471                         value = ref_head;
2473                 } else if (!strncmp(next, "%(commit)", 9)) {
2474                         value = ref_commit;
2476                 } else if (!strncmp(next, "%(blob)", 7)) {
2477                         value = ref_blob;
2479                 } else {
2480                         report("Unknown replacement in run request: `%s`", req->cmd);
2481                         return;
2482                 }
2484                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2485                         return;
2487                 if (next)
2488                         next = strchr(next, ')') + 1;
2489                 cmd = next;
2490         }
2492         open_external_viewer(buf);
2495 /*
2496  * User request switch noodle
2497  */
2499 static int
2500 view_driver(struct view *view, enum request request)
2502         int i;
2504         if (request == REQ_NONE) {
2505                 doupdate();
2506                 return TRUE;
2507         }
2509         if (request > REQ_NONE) {
2510                 open_run_request(request);
2511                 /* FIXME: When all views can refresh always do this. */
2512                 if (view == VIEW(REQ_VIEW_STATUS) ||
2513                     view == VIEW(REQ_VIEW_STAGE))
2514                         request = REQ_REFRESH;
2515                 else
2516                         return TRUE;
2517         }
2519         if (view && view->lines) {
2520                 request = view->ops->request(view, request, &view->line[view->lineno]);
2521                 if (request == REQ_NONE)
2522                         return TRUE;
2523         }
2525         switch (request) {
2526         case REQ_MOVE_UP:
2527         case REQ_MOVE_DOWN:
2528         case REQ_MOVE_PAGE_UP:
2529         case REQ_MOVE_PAGE_DOWN:
2530         case REQ_MOVE_FIRST_LINE:
2531         case REQ_MOVE_LAST_LINE:
2532                 move_view(view, request);
2533                 break;
2535         case REQ_SCROLL_LINE_DOWN:
2536         case REQ_SCROLL_LINE_UP:
2537         case REQ_SCROLL_PAGE_DOWN:
2538         case REQ_SCROLL_PAGE_UP:
2539                 scroll_view(view, request);
2540                 break;
2542         case REQ_VIEW_BLAME:
2543                 if (!opt_file[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_BLOB:
2552                 if (!ref_blob[0]) {
2553                         report("No file chosen, press %s to open tree view",
2554                                get_key(REQ_VIEW_TREE));
2555                         break;
2556                 }
2557                 open_view(view, request, OPEN_DEFAULT);
2558                 break;
2560         case REQ_VIEW_PAGER:
2561                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2562                         report("No pager content, press %s to run command from prompt",
2563                                get_key(REQ_PROMPT));
2564                         break;
2565                 }
2566                 open_view(view, request, OPEN_DEFAULT);
2567                 break;
2569         case REQ_VIEW_STAGE:
2570                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2571                         report("No stage content, press %s to open the status view and choose file",
2572                                get_key(REQ_VIEW_STATUS));
2573                         break;
2574                 }
2575                 open_view(view, request, OPEN_DEFAULT);
2576                 break;
2578         case REQ_VIEW_STATUS:
2579                 if (opt_is_inside_work_tree == FALSE) {
2580                         report("The status view requires a working tree");
2581                         break;
2582                 }
2583                 open_view(view, request, OPEN_DEFAULT);
2584                 break;
2586         case REQ_VIEW_MAIN:
2587         case REQ_VIEW_DIFF:
2588         case REQ_VIEW_LOG:
2589         case REQ_VIEW_TREE:
2590         case REQ_VIEW_HELP:
2591                 open_view(view, request, OPEN_DEFAULT);
2592                 break;
2594         case REQ_NEXT:
2595         case REQ_PREVIOUS:
2596                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2598                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2599                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2600                    (view == VIEW(REQ_VIEW_DIFF) &&
2601                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2602                    (view == VIEW(REQ_VIEW_STAGE) &&
2603                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2604                    (view == VIEW(REQ_VIEW_BLOB) &&
2605                      view->parent == VIEW(REQ_VIEW_TREE))) {
2606                         int line;
2608                         view = view->parent;
2609                         line = view->lineno;
2610                         move_view(view, request);
2611                         if (view_is_displayed(view))
2612                                 update_view_title(view);
2613                         if (line != view->lineno)
2614                                 view->ops->request(view, REQ_ENTER,
2615                                                    &view->line[view->lineno]);
2617                 } else {
2618                         move_view(view, request);
2619                 }
2620                 break;
2622         case REQ_VIEW_NEXT:
2623         {
2624                 int nviews = displayed_views();
2625                 int next_view = (current_view + 1) % nviews;
2627                 if (next_view == current_view) {
2628                         report("Only one view is displayed");
2629                         break;
2630                 }
2632                 current_view = next_view;
2633                 /* Blur out the title of the previous view. */
2634                 update_view_title(view);
2635                 report("");
2636                 break;
2637         }
2638         case REQ_REFRESH:
2639                 report("Refreshing is not yet supported for the %s view", view->name);
2640                 break;
2642         case REQ_MAXIMIZE:
2643                 if (displayed_views() == 2)
2644                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2645                 break;
2647         case REQ_TOGGLE_LINENO:
2648                 opt_line_number = !opt_line_number;
2649                 redraw_view(view);
2650                 break;
2652         case REQ_TOGGLE_DATE:
2653                 opt_date = !opt_date;
2654                 redraw_view(view);
2655                 break;
2657         case REQ_TOGGLE_AUTHOR:
2658                 opt_author = !opt_author;
2659                 redraw_view(view);
2660                 break;
2662         case REQ_TOGGLE_REV_GRAPH:
2663                 opt_rev_graph = !opt_rev_graph;
2664                 redraw_view(view);
2665                 break;
2667         case REQ_TOGGLE_REFS:
2668                 opt_show_refs = !opt_show_refs;
2669                 redraw_view(view);
2670                 break;
2672         case REQ_PROMPT:
2673                 /* Always reload^Wrerun commands from the prompt. */
2674                 open_view(view, opt_request, OPEN_RELOAD);
2675                 break;
2677         case REQ_SEARCH:
2678         case REQ_SEARCH_BACK:
2679                 search_view(view, request);
2680                 break;
2682         case REQ_FIND_NEXT:
2683         case REQ_FIND_PREV:
2684                 find_next(view, request);
2685                 break;
2687         case REQ_STOP_LOADING:
2688                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2689                         view = &views[i];
2690                         if (view->pipe)
2691                                 report("Stopped loading the %s view", view->name),
2692                         end_update(view);
2693                 }
2694                 break;
2696         case REQ_SHOW_VERSION:
2697                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2698                 return TRUE;
2700         case REQ_SCREEN_RESIZE:
2701                 resize_display();
2702                 /* Fall-through */
2703         case REQ_SCREEN_REDRAW:
2704                 redraw_display();
2705                 break;
2707         case REQ_EDIT:
2708                 report("Nothing to edit");
2709                 break;
2712         case REQ_ENTER:
2713                 report("Nothing to enter");
2714                 break;
2717         case REQ_VIEW_CLOSE:
2718                 /* XXX: Mark closed views by letting view->parent point to the
2719                  * view itself. Parents to closed view should never be
2720                  * followed. */
2721                 if (view->parent &&
2722                     view->parent->parent != view->parent) {
2723                         memset(display, 0, sizeof(display));
2724                         current_view = 0;
2725                         display[current_view] = view->parent;
2726                         view->parent = view;
2727                         resize_display();
2728                         redraw_display();
2729                         break;
2730                 }
2731                 /* Fall-through */
2732         case REQ_QUIT:
2733                 return FALSE;
2735         default:
2736                 /* An unknown key will show most commonly used commands. */
2737                 report("Unknown key, press 'h' for help");
2738                 return TRUE;
2739         }
2741         return TRUE;
2745 /*
2746  * Pager backend
2747  */
2749 static bool
2750 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2752         static char spaces[] = "                    ";
2753         char *text = line->data;
2754         enum line_type type = line->type;
2755         int attr = A_NORMAL;
2756         int col = 0;
2758         wmove(view->win, lineno, 0);
2760         if (selected) {
2761                 type = LINE_CURSOR;
2762                 wchgat(view->win, -1, 0, type, NULL);
2763                 attr = get_line_attr(type);
2764         }
2765         wattrset(view->win, attr);
2767         if (opt_line_number) {
2768                 col += draw_lineno(view, lineno, view->width, selected);
2769                 if (col >= view->width)
2770                         return TRUE;
2771         }
2773         if (!selected) {
2774                 attr = get_line_attr(type);
2775                 wattrset(view->win, attr);
2776         }
2777         if (opt_tab_size < TABSIZE) {
2778                 int col_offset = col;
2780                 col = 0;
2781                 while (text && col_offset + col < view->width) {
2782                         int cols_max = view->width - col_offset - col;
2783                         char *pos = text;
2784                         int cols;
2786                         if (*text == '\t') {
2787                                 text++;
2788                                 assert(sizeof(spaces) > TABSIZE);
2789                                 pos = spaces;
2790                                 cols = opt_tab_size - (col % opt_tab_size);
2792                         } else {
2793                                 text = strchr(text, '\t');
2794                                 cols = line ? text - pos : strlen(pos);
2795                         }
2797                         waddnstr(view->win, pos, MIN(cols, cols_max));
2798                         col += cols;
2799                 }
2801         } else {
2802                 draw_text(view, text, view->width - col, TRUE, selected);
2803         }
2805         return TRUE;
2808 static bool
2809 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2811         char refbuf[SIZEOF_STR];
2812         char *ref = NULL;
2813         FILE *pipe;
2815         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2816                 return TRUE;
2818         pipe = popen(refbuf, "r");
2819         if (!pipe)
2820                 return TRUE;
2822         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2823                 ref = chomp_string(ref);
2824         pclose(pipe);
2826         if (!ref || !*ref)
2827                 return TRUE;
2829         /* This is the only fatal call, since it can "corrupt" the buffer. */
2830         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2831                 return FALSE;
2833         return TRUE;
2836 static void
2837 add_pager_refs(struct view *view, struct line *line)
2839         char buf[SIZEOF_STR];
2840         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2841         struct ref **refs;
2842         size_t bufpos = 0, refpos = 0;
2843         const char *sep = "Refs: ";
2844         bool is_tag = FALSE;
2846         assert(line->type == LINE_COMMIT);
2848         refs = get_refs(commit_id);
2849         if (!refs) {
2850                 if (view == VIEW(REQ_VIEW_DIFF))
2851                         goto try_add_describe_ref;
2852                 return;
2853         }
2855         do {
2856                 struct ref *ref = refs[refpos];
2857                 char *fmt = ref->tag    ? "%s[%s]" :
2858                             ref->remote ? "%s<%s>" : "%s%s";
2860                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2861                         return;
2862                 sep = ", ";
2863                 if (ref->tag)
2864                         is_tag = TRUE;
2865         } while (refs[refpos++]->next);
2867         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2868 try_add_describe_ref:
2869                 /* Add <tag>-g<commit_id> "fake" reference. */
2870                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2871                         return;
2872         }
2874         if (bufpos == 0)
2875                 return;
2877         if (!realloc_lines(view, view->line_size + 1))
2878                 return;
2880         add_line_text(view, buf, LINE_PP_REFS);
2883 static bool
2884 pager_read(struct view *view, char *data)
2886         struct line *line;
2888         if (!data)
2889                 return TRUE;
2891         line = add_line_text(view, data, get_line_type(data));
2892         if (!line)
2893                 return FALSE;
2895         if (line->type == LINE_COMMIT &&
2896             (view == VIEW(REQ_VIEW_DIFF) ||
2897              view == VIEW(REQ_VIEW_LOG)))
2898                 add_pager_refs(view, line);
2900         return TRUE;
2903 static enum request
2904 pager_request(struct view *view, enum request request, struct line *line)
2906         int split = 0;
2908         if (request != REQ_ENTER)
2909                 return request;
2911         if (line->type == LINE_COMMIT &&
2912            (view == VIEW(REQ_VIEW_LOG) ||
2913             view == VIEW(REQ_VIEW_PAGER))) {
2914                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2915                 split = 1;
2916         }
2918         /* Always scroll the view even if it was split. That way
2919          * you can use Enter to scroll through the log view and
2920          * split open each commit diff. */
2921         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2923         /* FIXME: A minor workaround. Scrolling the view will call report("")
2924          * but if we are scrolling a non-current view this won't properly
2925          * update the view title. */
2926         if (split)
2927                 update_view_title(view);
2929         return REQ_NONE;
2932 static bool
2933 pager_grep(struct view *view, struct line *line)
2935         regmatch_t pmatch;
2936         char *text = line->data;
2938         if (!*text)
2939                 return FALSE;
2941         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2942                 return FALSE;
2944         return TRUE;
2947 static void
2948 pager_select(struct view *view, struct line *line)
2950         if (line->type == LINE_COMMIT) {
2951                 char *text = (char *)line->data + STRING_SIZE("commit ");
2953                 if (view != VIEW(REQ_VIEW_PAGER))
2954                         string_copy_rev(view->ref, text);
2955                 string_copy_rev(ref_commit, text);
2956         }
2959 static struct view_ops pager_ops = {
2960         "line",
2961         NULL,
2962         pager_read,
2963         pager_draw,
2964         pager_request,
2965         pager_grep,
2966         pager_select,
2967 };
2970 /*
2971  * Help backend
2972  */
2974 static bool
2975 help_open(struct view *view)
2977         char buf[BUFSIZ];
2978         int lines = ARRAY_SIZE(req_info) + 2;
2979         int i;
2981         if (view->lines > 0)
2982                 return TRUE;
2984         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2985                 if (!req_info[i].request)
2986                         lines++;
2988         lines += run_requests + 1;
2990         view->line = calloc(lines, sizeof(*view->line));
2991         if (!view->line)
2992                 return FALSE;
2994         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2996         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2997                 char *key;
2999                 if (req_info[i].request == REQ_NONE)
3000                         continue;
3002                 if (!req_info[i].request) {
3003                         add_line_text(view, "", LINE_DEFAULT);
3004                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3005                         continue;
3006                 }
3008                 key = get_key(req_info[i].request);
3009                 if (!*key)
3010                         key = "(no key defined)";
3012                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3013                         continue;
3015                 add_line_text(view, buf, LINE_DEFAULT);
3016         }
3018         if (run_requests) {
3019                 add_line_text(view, "", LINE_DEFAULT);
3020                 add_line_text(view, "External commands:", LINE_DEFAULT);
3021         }
3023         for (i = 0; i < run_requests; i++) {
3024                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3025                 char *key;
3027                 if (!req)
3028                         continue;
3030                 key = get_key_name(req->key);
3031                 if (!*key)
3032                         key = "(no key defined)";
3034                 if (!string_format(buf, "    %-10s %-14s `%s`",
3035                                    keymap_table[req->keymap].name,
3036                                    key, req->cmd))
3037                         continue;
3039                 add_line_text(view, buf, LINE_DEFAULT);
3040         }
3042         return TRUE;
3045 static struct view_ops help_ops = {
3046         "line",
3047         help_open,
3048         NULL,
3049         pager_draw,
3050         pager_request,
3051         pager_grep,
3052         pager_select,
3053 };
3056 /*
3057  * Tree backend
3058  */
3060 struct tree_stack_entry {
3061         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3062         unsigned long lineno;           /* Line number to restore */
3063         char *name;                     /* Position of name in opt_path */
3064 };
3066 /* The top of the path stack. */
3067 static struct tree_stack_entry *tree_stack = NULL;
3068 unsigned long tree_lineno = 0;
3070 static void
3071 pop_tree_stack_entry(void)
3073         struct tree_stack_entry *entry = tree_stack;
3075         tree_lineno = entry->lineno;
3076         entry->name[0] = 0;
3077         tree_stack = entry->prev;
3078         free(entry);
3081 static void
3082 push_tree_stack_entry(char *name, unsigned long lineno)
3084         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3085         size_t pathlen = strlen(opt_path);
3087         if (!entry)
3088                 return;
3090         entry->prev = tree_stack;
3091         entry->name = opt_path + pathlen;
3092         tree_stack = entry;
3094         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3095                 pop_tree_stack_entry();
3096                 return;
3097         }
3099         /* Move the current line to the first tree entry. */
3100         tree_lineno = 1;
3101         entry->lineno = lineno;
3104 /* Parse output from git-ls-tree(1):
3105  *
3106  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3107  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3108  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3109  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3110  */
3112 #define SIZEOF_TREE_ATTR \
3113         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3115 #define TREE_UP_FORMAT "040000 tree %s\t.."
3117 static int
3118 tree_compare_entry(enum line_type type1, char *name1,
3119                    enum line_type type2, char *name2)
3121         if (type1 != type2) {
3122                 if (type1 == LINE_TREE_DIR)
3123                         return -1;
3124                 return 1;
3125         }
3127         return strcmp(name1, name2);
3130 static char *
3131 tree_path(struct line *line)
3133         char *path = line->data;
3135         return path + SIZEOF_TREE_ATTR;
3138 static bool
3139 tree_read(struct view *view, char *text)
3141         size_t textlen = text ? strlen(text) : 0;
3142         char buf[SIZEOF_STR];
3143         unsigned long pos;
3144         enum line_type type;
3145         bool first_read = view->lines == 0;
3147         if (!text)
3148                 return TRUE;
3149         if (textlen <= SIZEOF_TREE_ATTR)
3150                 return FALSE;
3152         type = text[STRING_SIZE("100644 ")] == 't'
3153              ? LINE_TREE_DIR : LINE_TREE_FILE;
3155         if (first_read) {
3156                 /* Add path info line */
3157                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3158                     !realloc_lines(view, view->line_size + 1) ||
3159                     !add_line_text(view, buf, LINE_DEFAULT))
3160                         return FALSE;
3162                 /* Insert "link" to parent directory. */
3163                 if (*opt_path) {
3164                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3165                             !realloc_lines(view, view->line_size + 1) ||
3166                             !add_line_text(view, buf, LINE_TREE_DIR))
3167                                 return FALSE;
3168                 }
3169         }
3171         /* Strip the path part ... */
3172         if (*opt_path) {
3173                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3174                 size_t striplen = strlen(opt_path);
3175                 char *path = text + SIZEOF_TREE_ATTR;
3177                 if (pathlen > striplen)
3178                         memmove(path, path + striplen,
3179                                 pathlen - striplen + 1);
3180         }
3182         /* Skip "Directory ..." and ".." line. */
3183         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3184                 struct line *line = &view->line[pos];
3185                 char *path1 = tree_path(line);
3186                 char *path2 = text + SIZEOF_TREE_ATTR;
3187                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3189                 if (cmp <= 0)
3190                         continue;
3192                 text = strdup(text);
3193                 if (!text)
3194                         return FALSE;
3196                 if (view->lines > pos)
3197                         memmove(&view->line[pos + 1], &view->line[pos],
3198                                 (view->lines - pos) * sizeof(*line));
3200                 line = &view->line[pos];
3201                 line->data = text;
3202                 line->type = type;
3203                 view->lines++;
3204                 return TRUE;
3205         }
3207         if (!add_line_text(view, text, type))
3208                 return FALSE;
3210         if (tree_lineno > view->lineno) {
3211                 view->lineno = tree_lineno;
3212                 tree_lineno = 0;
3213         }
3215         return TRUE;
3218 static enum request
3219 tree_request(struct view *view, enum request request, struct line *line)
3221         enum open_flags flags;
3223         if (request == REQ_VIEW_BLAME) {
3224                 char *filename = tree_path(line);
3226                 if (line->type == LINE_TREE_DIR) {
3227                         report("Cannot show blame for directory %s", opt_path);
3228                         return REQ_NONE;
3229                 }
3231                 string_copy(opt_ref, view->vid);
3232                 string_format(opt_file, "%s%s", opt_path, filename);
3233                 return request;
3234         }
3235         if (request == REQ_TREE_PARENT) {
3236                 if (*opt_path) {
3237                         /* fake 'cd  ..' */
3238                         request = REQ_ENTER;
3239                         line = &view->line[1];
3240                 } else {
3241                         /* quit view if at top of tree */
3242                         return REQ_VIEW_CLOSE;
3243                 }
3244         }
3245         if (request != REQ_ENTER)
3246                 return request;
3248         /* Cleanup the stack if the tree view is at a different tree. */
3249         while (!*opt_path && tree_stack)
3250                 pop_tree_stack_entry();
3252         switch (line->type) {
3253         case LINE_TREE_DIR:
3254                 /* Depending on whether it is a subdir or parent (updir?) link
3255                  * mangle the path buffer. */
3256                 if (line == &view->line[1] && *opt_path) {
3257                         pop_tree_stack_entry();
3259                 } else {
3260                         char *basename = tree_path(line);
3262                         push_tree_stack_entry(basename, view->lineno);
3263                 }
3265                 /* Trees and subtrees share the same ID, so they are not not
3266                  * unique like blobs. */
3267                 flags = OPEN_RELOAD;
3268                 request = REQ_VIEW_TREE;
3269                 break;
3271         case LINE_TREE_FILE:
3272                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3273                 request = REQ_VIEW_BLOB;
3274                 break;
3276         default:
3277                 return TRUE;
3278         }
3280         open_view(view, request, flags);
3281         if (request == REQ_VIEW_TREE) {
3282                 view->lineno = tree_lineno;
3283         }
3285         return REQ_NONE;
3288 static void
3289 tree_select(struct view *view, struct line *line)
3291         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3293         if (line->type == LINE_TREE_FILE) {
3294                 string_copy_rev(ref_blob, text);
3296         } else if (line->type != LINE_TREE_DIR) {
3297                 return;
3298         }
3300         string_copy_rev(view->ref, text);
3303 static struct view_ops tree_ops = {
3304         "file",
3305         NULL,
3306         tree_read,
3307         pager_draw,
3308         tree_request,
3309         pager_grep,
3310         tree_select,
3311 };
3313 static bool
3314 blob_read(struct view *view, char *line)
3316         if (!line)
3317                 return TRUE;
3318         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3321 static struct view_ops blob_ops = {
3322         "line",
3323         NULL,
3324         blob_read,
3325         pager_draw,
3326         pager_request,
3327         pager_grep,
3328         pager_select,
3329 };
3331 /*
3332  * Blame backend
3333  *
3334  * Loading the blame view is a two phase job:
3335  *
3336  *  1. File content is read either using opt_file from the
3337  *     filesystem or using git-cat-file.
3338  *  2. Then blame information is incrementally added by
3339  *     reading output from git-blame.
3340  */
3342 struct blame_commit {
3343         char id[SIZEOF_REV];            /* SHA1 ID. */
3344         char title[128];                /* First line of the commit message. */
3345         char author[75];                /* Author of the commit. */
3346         struct tm time;                 /* Date from the author ident. */
3347         char filename[128];             /* Name of file. */
3348 };
3350 struct blame {
3351         struct blame_commit *commit;
3352         unsigned int header:1;
3353         char text[1];
3354 };
3356 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3357 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3359 static bool
3360 blame_open(struct view *view)
3362         char path[SIZEOF_STR];
3363         char ref[SIZEOF_STR] = "";
3365         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3366                 return FALSE;
3368         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3369                 return FALSE;
3371         if (*opt_ref) {
3372                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3373                         return FALSE;
3374         } else {
3375                 view->pipe = fopen(opt_file, "r");
3376                 if (!view->pipe &&
3377                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3378                         return FALSE;
3379         }
3381         if (!view->pipe)
3382                 view->pipe = popen(view->cmd, "r");
3383         if (!view->pipe)
3384                 return FALSE;
3386         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3387                 return FALSE;
3389         string_format(view->ref, "%s ...", opt_file);
3390         string_copy_rev(view->vid, opt_file);
3391         set_nonblocking_input(TRUE);
3393         if (view->line) {
3394                 int i;
3396                 for (i = 0; i < view->lines; i++)
3397                         free(view->line[i].data);
3398                 free(view->line);
3399         }
3401         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3402         view->offset = view->lines  = view->lineno = 0;
3403         view->line = NULL;
3404         view->start_time = time(NULL);
3406         return TRUE;
3409 static struct blame_commit *
3410 get_blame_commit(struct view *view, const char *id)
3412         size_t i;
3414         for (i = 0; i < view->lines; i++) {
3415                 struct blame *blame = view->line[i].data;
3417                 if (!blame->commit)
3418                         continue;
3420                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3421                         return blame->commit;
3422         }
3424         {
3425                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3427                 if (commit)
3428                         string_ncopy(commit->id, id, SIZEOF_REV);
3429                 return commit;
3430         }
3433 static bool
3434 parse_number(char **posref, size_t *number, size_t min, size_t max)
3436         char *pos = *posref;
3438         *posref = NULL;
3439         pos = strchr(pos + 1, ' ');
3440         if (!pos || !isdigit(pos[1]))
3441                 return FALSE;
3442         *number = atoi(pos + 1);
3443         if (*number < min || *number > max)
3444                 return FALSE;
3446         *posref = pos;
3447         return TRUE;
3450 static struct blame_commit *
3451 parse_blame_commit(struct view *view, char *text, int *blamed)
3453         struct blame_commit *commit;
3454         struct blame *blame;
3455         char *pos = text + SIZEOF_REV - 1;
3456         size_t lineno;
3457         size_t group;
3459         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3460                 return NULL;
3462         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3463             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3464                 return NULL;
3466         commit = get_blame_commit(view, text);
3467         if (!commit)
3468                 return NULL;
3470         *blamed += group;
3471         while (group--) {
3472                 struct line *line = &view->line[lineno + group - 1];
3474                 blame = line->data;
3475                 blame->commit = commit;
3476                 blame->header = !group;
3477                 line->dirty = 1;
3478         }
3480         return commit;
3483 static bool
3484 blame_read_file(struct view *view, char *line)
3486         if (!line) {
3487                 FILE *pipe = NULL;
3489                 if (view->lines > 0)
3490                         pipe = popen(view->cmd, "r");
3491                 view->cmd[0] = 0;
3492                 if (!pipe) {
3493                         report("Failed to load blame data");
3494                         return TRUE;
3495                 }
3497                 fclose(view->pipe);
3498                 view->pipe = pipe;
3499                 return FALSE;
3501         } else {
3502                 size_t linelen = strlen(line);
3503                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3505                 if (!line)
3506                         return FALSE;
3508                 blame->commit = NULL;
3509                 strncpy(blame->text, line, linelen);
3510                 blame->text[linelen] = 0;
3511                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3512         }
3515 static bool
3516 match_blame_header(const char *name, char **line)
3518         size_t namelen = strlen(name);
3519         bool matched = !strncmp(name, *line, namelen);
3521         if (matched)
3522                 *line += namelen;
3524         return matched;
3527 static bool
3528 blame_read(struct view *view, char *line)
3530         static struct blame_commit *commit = NULL;
3531         static int blamed = 0;
3532         static time_t author_time;
3534         if (*view->cmd)
3535                 return blame_read_file(view, line);
3537         if (!line) {
3538                 /* Reset all! */
3539                 commit = NULL;
3540                 blamed = 0;
3541                 string_format(view->ref, "%s", view->vid);
3542                 if (view_is_displayed(view)) {
3543                         update_view_title(view);
3544                         redraw_view_from(view, 0);
3545                 }
3546                 return TRUE;
3547         }
3549         if (!commit) {
3550                 commit = parse_blame_commit(view, line, &blamed);
3551                 string_format(view->ref, "%s %2d%%", view->vid,
3552                               blamed * 100 / view->lines);
3554         } else if (match_blame_header("author ", &line)) {
3555                 string_ncopy(commit->author, line, strlen(line));
3557         } else if (match_blame_header("author-time ", &line)) {
3558                 author_time = (time_t) atol(line);
3560         } else if (match_blame_header("author-tz ", &line)) {
3561                 long tz;
3563                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3564                 tz += ('0' - line[2]) * 60 * 60;
3565                 tz += ('0' - line[3]) * 60;
3566                 tz += ('0' - line[4]) * 60;
3568                 if (line[0] == '-')
3569                         tz = -tz;
3571                 author_time -= tz;
3572                 gmtime_r(&author_time, &commit->time);
3574         } else if (match_blame_header("summary ", &line)) {
3575                 string_ncopy(commit->title, line, strlen(line));
3577         } else if (match_blame_header("filename ", &line)) {
3578                 string_ncopy(commit->filename, line, strlen(line));
3579                 commit = NULL;
3580         }
3582         return TRUE;
3585 static bool
3586 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3588         struct blame *blame = line->data;
3589         int col = 0;
3591         wmove(view->win, lineno, 0);
3593         if (selected) {
3594                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3595                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3596         } else {
3597                 wattrset(view->win, A_NORMAL);
3598         }
3600         if (opt_date) {
3601                 int n;
3603                 if (!selected)
3604                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3605                 if (blame->commit) {
3606                         char buf[DATE_COLS + 1];
3607                         int timelen;
3609                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3610                         n = draw_text(view, buf, view->width - col, FALSE, selected);
3611                         draw_text(view, " ", view->width - col - n, FALSE, selected);
3612                 }
3614                 col += DATE_COLS;
3615                 wmove(view->win, lineno, col);
3616                 if (col >= view->width)
3617                         return TRUE;
3618         }
3620         if (opt_author) {
3621                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3623                 if (!selected)
3624                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3625                 if (blame->commit)
3626                         draw_text(view, blame->commit->author, max, TRUE, selected);
3627                 col += AUTHOR_COLS;
3628                 if (col >= view->width)
3629                         return TRUE;
3630                 wmove(view->win, lineno, col);
3631         }
3633         {
3634                 int max = MIN(ID_COLS - 1, view->width - col);
3636                 if (!selected)
3637                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3638                 if (blame->commit)
3639                         draw_text(view, blame->commit->id, max, FALSE, -1);
3640                 col += ID_COLS;
3641                 if (col >= view->width)
3642                         return TRUE;
3643                 wmove(view->win, lineno, col);
3644         }
3646         {
3647                 col += draw_lineno(view, lineno, view->width - col, selected);
3648                 if (col >= view->width)
3649                         return TRUE;
3650         }
3652         col += draw_text(view, blame->text, view->width - col, TRUE, selected);
3654         return TRUE;
3657 static enum request
3658 blame_request(struct view *view, enum request request, struct line *line)
3660         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3661         struct blame *blame = line->data;
3663         switch (request) {
3664         case REQ_ENTER:
3665                 if (!blame->commit) {
3666                         report("No commit loaded yet");
3667                         break;
3668                 }
3670                 if (!strcmp(blame->commit->id, NULL_ID)) {
3671                         char path[SIZEOF_STR];
3673                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3674                                 break;
3675                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3676                 }
3678                 open_view(view, REQ_VIEW_DIFF, flags);
3679                 break;
3681         default:
3682                 return request;
3683         }
3685         return REQ_NONE;
3688 static bool
3689 blame_grep(struct view *view, struct line *line)
3691         struct blame *blame = line->data;
3692         struct blame_commit *commit = blame->commit;
3693         regmatch_t pmatch;
3695 #define MATCH(text) \
3696         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3698         if (commit) {
3699                 char buf[DATE_COLS + 1];
3701                 if (MATCH(commit->title) ||
3702                     MATCH(commit->author) ||
3703                     MATCH(commit->id))
3704                         return TRUE;
3706                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3707                     MATCH(buf))
3708                         return TRUE;
3709         }
3711         return MATCH(blame->text);
3713 #undef MATCH
3716 static void
3717 blame_select(struct view *view, struct line *line)
3719         struct blame *blame = line->data;
3720         struct blame_commit *commit = blame->commit;
3722         if (!commit)
3723                 return;
3725         if (!strcmp(commit->id, NULL_ID))
3726                 string_ncopy(ref_commit, "HEAD", 4);
3727         else
3728                 string_copy_rev(ref_commit, commit->id);
3731 static struct view_ops blame_ops = {
3732         "line",
3733         blame_open,
3734         blame_read,
3735         blame_draw,
3736         blame_request,
3737         blame_grep,
3738         blame_select,
3739 };
3741 /*
3742  * Status backend
3743  */
3745 struct status {
3746         char status;
3747         struct {
3748                 mode_t mode;
3749                 char rev[SIZEOF_REV];
3750                 char name[SIZEOF_STR];
3751         } old;
3752         struct {
3753                 mode_t mode;
3754                 char rev[SIZEOF_REV];
3755                 char name[SIZEOF_STR];
3756         } new;
3757 };
3759 static char status_onbranch[SIZEOF_STR];
3760 static struct status stage_status;
3761 static enum line_type stage_line_type;
3763 /* Get fields from the diff line:
3764  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3765  */
3766 static inline bool
3767 status_get_diff(struct status *file, char *buf, size_t bufsize)
3769         char *old_mode = buf +  1;
3770         char *new_mode = buf +  8;
3771         char *old_rev  = buf + 15;
3772         char *new_rev  = buf + 56;
3773         char *status   = buf + 97;
3775         if (bufsize < 99 ||
3776             old_mode[-1] != ':' ||
3777             new_mode[-1] != ' ' ||
3778             old_rev[-1]  != ' ' ||
3779             new_rev[-1]  != ' ' ||
3780             status[-1]   != ' ')
3781                 return FALSE;
3783         file->status = *status;
3785         string_copy_rev(file->old.rev, old_rev);
3786         string_copy_rev(file->new.rev, new_rev);
3788         file->old.mode = strtoul(old_mode, NULL, 8);
3789         file->new.mode = strtoul(new_mode, NULL, 8);
3791         file->old.name[0] = file->new.name[0] = 0;
3793         return TRUE;
3796 static bool
3797 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3799         struct status *file = NULL;
3800         struct status *unmerged = NULL;
3801         char buf[SIZEOF_STR * 4];
3802         size_t bufsize = 0;
3803         FILE *pipe;
3805         pipe = popen(cmd, "r");
3806         if (!pipe)
3807                 return FALSE;
3809         add_line_data(view, NULL, type);
3811         while (!feof(pipe) && !ferror(pipe)) {
3812                 char *sep;
3813                 size_t readsize;
3815                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3816                 if (!readsize)
3817                         break;
3818                 bufsize += readsize;
3820                 /* Process while we have NUL chars. */
3821                 while ((sep = memchr(buf, 0, bufsize))) {
3822                         size_t sepsize = sep - buf + 1;
3824                         if (!file) {
3825                                 if (!realloc_lines(view, view->line_size + 1))
3826                                         goto error_out;
3828                                 file = calloc(1, sizeof(*file));
3829                                 if (!file)
3830                                         goto error_out;
3832                                 add_line_data(view, file, type);
3833                         }
3835                         /* Parse diff info part. */
3836                         if (status) {
3837                                 file->status = status;
3838                                 if (status == 'A')
3839                                         string_copy(file->old.rev, NULL_ID);
3841                         } else if (!file->status) {
3842                                 if (!status_get_diff(file, buf, sepsize))
3843                                         goto error_out;
3845                                 bufsize -= sepsize;
3846                                 memmove(buf, sep + 1, bufsize);
3848                                 sep = memchr(buf, 0, bufsize);
3849                                 if (!sep)
3850                                         break;
3851                                 sepsize = sep - buf + 1;
3853                                 /* Collapse all 'M'odified entries that
3854                                  * follow a associated 'U'nmerged entry.
3855                                  */
3856                                 if (file->status == 'U') {
3857                                         unmerged = file;
3859                                 } else if (unmerged) {
3860                                         int collapse = !strcmp(buf, unmerged->new.name);
3862                                         unmerged = NULL;
3863                                         if (collapse) {
3864                                                 free(file);
3865                                                 view->lines--;
3866                                                 continue;
3867                                         }
3868                                 }
3869                         }
3871                         /* Grab the old name for rename/copy. */
3872                         if (!*file->old.name &&
3873                             (file->status == 'R' || file->status == 'C')) {
3874                                 sepsize = sep - buf + 1;
3875                                 string_ncopy(file->old.name, buf, sepsize);
3876                                 bufsize -= sepsize;
3877                                 memmove(buf, sep + 1, bufsize);
3879                                 sep = memchr(buf, 0, bufsize);
3880                                 if (!sep)
3881                                         break;
3882                                 sepsize = sep - buf + 1;
3883                         }
3885                         /* git-ls-files just delivers a NUL separated
3886                          * list of file names similar to the second half
3887                          * of the git-diff-* output. */
3888                         string_ncopy(file->new.name, buf, sepsize);
3889                         if (!*file->old.name)
3890                                 string_copy(file->old.name, file->new.name);
3891                         bufsize -= sepsize;
3892                         memmove(buf, sep + 1, bufsize);
3893                         file = NULL;
3894                 }
3895         }
3897         if (ferror(pipe)) {
3898 error_out:
3899                 pclose(pipe);
3900                 return FALSE;
3901         }
3903         if (!view->line[view->lines - 1].data)
3904                 add_line_data(view, NULL, LINE_STAT_NONE);
3906         pclose(pipe);
3907         return TRUE;
3910 /* Don't show unmerged entries in the staged section. */
3911 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3912 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3913 #define STATUS_LIST_OTHER_CMD \
3914         "git ls-files -z --others --exclude-per-directory=.gitignore"
3915 #define STATUS_LIST_NO_HEAD_CMD \
3916         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3918 #define STATUS_DIFF_INDEX_SHOW_CMD \
3919         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3921 #define STATUS_DIFF_FILES_SHOW_CMD \
3922         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3924 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3925         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3927 /* First parse staged info using git-diff-index(1), then parse unstaged
3928  * info using git-diff-files(1), and finally untracked files using
3929  * git-ls-files(1). */
3930 static bool
3931 status_open(struct view *view)
3933         struct stat statbuf;
3934         char exclude[SIZEOF_STR];
3935         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3936         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3937         unsigned long prev_lineno = view->lineno;
3938         char indexstatus = 0;
3939         size_t i;
3941         for (i = 0; i < view->lines; i++)
3942                 free(view->line[i].data);
3943         free(view->line);
3944         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3945         view->line = NULL;
3947         if (!realloc_lines(view, view->line_size + 7))
3948                 return FALSE;
3950         add_line_data(view, NULL, LINE_STAT_HEAD);
3951         if (opt_no_head)
3952                 string_copy(status_onbranch, "Initial commit");
3953         else if (!*opt_head)
3954                 string_copy(status_onbranch, "Not currently on any branch");
3955         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3956                 return FALSE;
3958         if (opt_no_head) {
3959                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3960                 indexstatus = 'A';
3961         }
3963         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3964                 return FALSE;
3966         if (stat(exclude, &statbuf) >= 0) {
3967                 size_t cmdsize = strlen(othercmd);
3969                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3970                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3971                         return FALSE;
3973                 cmdsize = strlen(indexcmd);
3974                 if (opt_no_head &&
3975                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3976                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
3977                         return FALSE;
3978         }
3980         system("git update-index -q --refresh");
3982         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3983             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3984             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
3985                 return FALSE;
3987         /* If all went well restore the previous line number to stay in
3988          * the context or select a line with something that can be
3989          * updated. */
3990         if (prev_lineno >= view->lines)
3991                 prev_lineno = view->lines - 1;
3992         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3993                 prev_lineno++;
3994         while (prev_lineno > 0 && !view->line[prev_lineno].data)
3995                 prev_lineno--;
3997         /* If the above fails, always skip the "On branch" line. */
3998         if (prev_lineno < view->lines)
3999                 view->lineno = prev_lineno;
4000         else
4001                 view->lineno = 1;
4003         if (view->lineno < view->offset)
4004                 view->offset = view->lineno;
4005         else if (view->offset + view->height <= view->lineno)
4006                 view->offset = view->lineno - view->height + 1;
4008         return TRUE;
4011 static bool
4012 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4014         struct status *status = line->data;
4015         char *text;
4016         int col = 0;
4018         wmove(view->win, lineno, 0);
4020         if (selected) {
4021                 wattrset(view->win, get_line_attr(LINE_CURSOR));
4022                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4024         } else if (line->type == LINE_STAT_HEAD) {
4025                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4026                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4028         } else if (!status && line->type != LINE_STAT_NONE) {
4029                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4030                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4032         } else {
4033                 wattrset(view->win, get_line_attr(line->type));
4034         }
4036         if (!status) {
4037                 switch (line->type) {
4038                 case LINE_STAT_STAGED:
4039                         text = "Changes to be committed:";
4040                         break;
4042                 case LINE_STAT_UNSTAGED:
4043                         text = "Changed but not updated:";
4044                         break;
4046                 case LINE_STAT_UNTRACKED:
4047                         text = "Untracked files:";
4048                         break;
4050                 case LINE_STAT_NONE:
4051                         text = "    (no files)";
4052                         break;
4054                 case LINE_STAT_HEAD:
4055                         text = status_onbranch;
4056                         break;
4058                 default:
4059                         return FALSE;
4060                 }
4061         } else {
4062                 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4064                 col += draw_text(view, buf, view->width, TRUE, selected);
4065                 if (!selected)
4066                         wattrset(view->win, A_NORMAL);
4067                 text = status->new.name;
4068         }
4070         draw_text(view, text, view->width - col, TRUE, selected);
4071         return TRUE;
4074 static enum request
4075 status_enter(struct view *view, struct line *line)
4077         struct status *status = line->data;
4078         char oldpath[SIZEOF_STR] = "";
4079         char newpath[SIZEOF_STR] = "";
4080         char *info;
4081         size_t cmdsize = 0;
4083         if (line->type == LINE_STAT_NONE ||
4084             (!status && line[1].type == LINE_STAT_NONE)) {
4085                 report("No file to diff");
4086                 return REQ_NONE;
4087         }
4089         if (status) {
4090                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4091                         return REQ_QUIT;
4092                 /* Diffs for unmerged entries are empty when pasing the
4093                  * new path, so leave it empty. */
4094                 if (status->status != 'U' &&
4095                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4096                         return REQ_QUIT;
4097         }
4099         if (opt_cdup[0] &&
4100             line->type != LINE_STAT_UNTRACKED &&
4101             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4102                 return REQ_QUIT;
4104         switch (line->type) {
4105         case LINE_STAT_STAGED:
4106                 if (opt_no_head) {
4107                         if (!string_format_from(opt_cmd, &cmdsize,
4108                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4109                                                 newpath))
4110                                 return REQ_QUIT;
4111                 } else {
4112                         if (!string_format_from(opt_cmd, &cmdsize,
4113                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4114                                                 oldpath, newpath))
4115                                 return REQ_QUIT;
4116                 }
4118                 if (status)
4119                         info = "Staged changes to %s";
4120                 else
4121                         info = "Staged changes";
4122                 break;
4124         case LINE_STAT_UNSTAGED:
4125                 if (!string_format_from(opt_cmd, &cmdsize,
4126                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4127                         return REQ_QUIT;
4128                 if (status)
4129                         info = "Unstaged changes to %s";
4130                 else
4131                         info = "Unstaged changes";
4132                 break;
4134         case LINE_STAT_UNTRACKED:
4135                 if (opt_pipe)
4136                         return REQ_QUIT;
4138                 if (!status) {
4139                         report("No file to show");
4140                         return REQ_NONE;
4141                 }
4143                 opt_pipe = fopen(status->new.name, "r");
4144                 info = "Untracked file %s";
4145                 break;
4147         case LINE_STAT_HEAD:
4148                 return REQ_NONE;
4150         default:
4151                 die("line type %d not handled in switch", line->type);
4152         }
4154         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4155         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4156                 if (status) {
4157                         stage_status = *status;
4158                 } else {
4159                         memset(&stage_status, 0, sizeof(stage_status));
4160                 }
4162                 stage_line_type = line->type;
4163                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4164         }
4166         return REQ_NONE;
4169 static bool
4170 status_exists(struct status *status, enum line_type type)
4172         struct view *view = VIEW(REQ_VIEW_STATUS);
4173         struct line *line;
4175         for (line = view->line; line < view->line + view->lines; line++) {
4176                 struct status *pos = line->data;
4178                 if (line->type == type && pos &&
4179                     !strcmp(status->new.name, pos->new.name))
4180                         return TRUE;
4181         }
4183         return FALSE;
4187 static FILE *
4188 status_update_prepare(enum line_type type)
4190         char cmd[SIZEOF_STR];
4191         size_t cmdsize = 0;
4193         if (opt_cdup[0] &&
4194             type != LINE_STAT_UNTRACKED &&
4195             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4196                 return NULL;
4198         switch (type) {
4199         case LINE_STAT_STAGED:
4200                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4201                 break;
4203         case LINE_STAT_UNSTAGED:
4204         case LINE_STAT_UNTRACKED:
4205                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4206                 break;
4208         default:
4209                 die("line type %d not handled in switch", type);
4210         }
4212         return popen(cmd, "w");
4215 static bool
4216 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4218         char buf[SIZEOF_STR];
4219         size_t bufsize = 0;
4220         size_t written = 0;
4222         switch (type) {
4223         case LINE_STAT_STAGED:
4224                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4225                                         status->old.mode,
4226                                         status->old.rev,
4227                                         status->old.name, 0))
4228                         return FALSE;
4229                 break;
4231         case LINE_STAT_UNSTAGED:
4232         case LINE_STAT_UNTRACKED:
4233                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4234                         return FALSE;
4235                 break;
4237         default:
4238                 die("line type %d not handled in switch", type);
4239         }
4241         while (!ferror(pipe) && written < bufsize) {
4242                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4243         }
4245         return written == bufsize;
4248 static bool
4249 status_update_file(struct status *status, enum line_type type)
4251         FILE *pipe = status_update_prepare(type);
4252         bool result;
4254         if (!pipe)
4255                 return FALSE;
4257         result = status_update_write(pipe, status, type);
4258         pclose(pipe);
4259         return result;
4262 static bool
4263 status_update_files(struct view *view, struct line *line)
4265         FILE *pipe = status_update_prepare(line->type);
4266         bool result = TRUE;
4267         struct line *pos = view->line + view->lines;
4268         int files = 0;
4269         int file, done;
4271         if (!pipe)
4272                 return FALSE;
4274         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4275                 files++;
4277         for (file = 0, done = 0; result && file < files; line++, file++) {
4278                 int almost_done = file * 100 / files;
4280                 if (almost_done > done) {
4281                         done = almost_done;
4282                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4283                                       file, files, done);
4284                         update_view_title(view);
4285                 }
4286                 result = status_update_write(pipe, line->data, line->type);
4287         }
4289         pclose(pipe);
4290         return result;
4293 static bool
4294 status_update(struct view *view)
4296         struct line *line = &view->line[view->lineno];
4298         assert(view->lines);
4300         if (!line->data) {
4301                 /* This should work even for the "On branch" line. */
4302                 if (line < view->line + view->lines && !line[1].data) {
4303                         report("Nothing to update");
4304                         return FALSE;
4305                 }
4307                 if (!status_update_files(view, line + 1))
4308                         report("Failed to update file status");
4310         } else if (!status_update_file(line->data, line->type)) {
4311                 report("Failed to update file status");
4312         }
4314         return TRUE;
4317 static enum request
4318 status_request(struct view *view, enum request request, struct line *line)
4320         struct status *status = line->data;
4322         switch (request) {
4323         case REQ_STATUS_UPDATE:
4324                 if (!status_update(view))
4325                         return REQ_NONE;
4326                 break;
4328         case REQ_STATUS_MERGE:
4329                 if (!status || status->status != 'U') {
4330                         report("Merging only possible for files with unmerged status ('U').");
4331                         return REQ_NONE;
4332                 }
4333                 open_mergetool(status->new.name);
4334                 break;
4336         case REQ_EDIT:
4337                 if (!status)
4338                         return request;
4340                 open_editor(status->status != '?', status->new.name);
4341                 break;
4343         case REQ_VIEW_BLAME:
4344                 if (status) {
4345                         string_copy(opt_file, status->new.name);
4346                         opt_ref[0] = 0;
4347                 }
4348                 return request;
4350         case REQ_ENTER:
4351                 /* After returning the status view has been split to
4352                  * show the stage view. No further reloading is
4353                  * necessary. */
4354                 status_enter(view, line);
4355                 return REQ_NONE;
4357         case REQ_REFRESH:
4358                 /* Simply reload the view. */
4359                 break;
4361         default:
4362                 return request;
4363         }
4365         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4367         return REQ_NONE;
4370 static void
4371 status_select(struct view *view, struct line *line)
4373         struct status *status = line->data;
4374         char file[SIZEOF_STR] = "all files";
4375         char *text;
4376         char *key;
4378         if (status && !string_format(file, "'%s'", status->new.name))
4379                 return;
4381         if (!status && line[1].type == LINE_STAT_NONE)
4382                 line++;
4384         switch (line->type) {
4385         case LINE_STAT_STAGED:
4386                 text = "Press %s to unstage %s for commit";
4387                 break;
4389         case LINE_STAT_UNSTAGED:
4390                 text = "Press %s to stage %s for commit";
4391                 break;
4393         case LINE_STAT_UNTRACKED:
4394                 text = "Press %s to stage %s for addition";
4395                 break;
4397         case LINE_STAT_HEAD:
4398         case LINE_STAT_NONE:
4399                 text = "Nothing to update";
4400                 break;
4402         default:
4403                 die("line type %d not handled in switch", line->type);
4404         }
4406         if (status && status->status == 'U') {
4407                 text = "Press %s to resolve conflict in %s";
4408                 key = get_key(REQ_STATUS_MERGE);
4410         } else {
4411                 key = get_key(REQ_STATUS_UPDATE);
4412         }
4414         string_format(view->ref, text, key, file);
4417 static bool
4418 status_grep(struct view *view, struct line *line)
4420         struct status *status = line->data;
4421         enum { S_STATUS, S_NAME, S_END } state;
4422         char buf[2] = "?";
4423         regmatch_t pmatch;
4425         if (!status)
4426                 return FALSE;
4428         for (state = S_STATUS; state < S_END; state++) {
4429                 char *text;
4431                 switch (state) {
4432                 case S_NAME:    text = status->new.name;        break;
4433                 case S_STATUS:
4434                         buf[0] = status->status;
4435                         text = buf;
4436                         break;
4438                 default:
4439                         return FALSE;
4440                 }
4442                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4443                         return TRUE;
4444         }
4446         return FALSE;
4449 static struct view_ops status_ops = {
4450         "file",
4451         status_open,
4452         NULL,
4453         status_draw,
4454         status_request,
4455         status_grep,
4456         status_select,
4457 };
4460 static bool
4461 stage_diff_line(FILE *pipe, struct line *line)
4463         char *buf = line->data;
4464         size_t bufsize = strlen(buf);
4465         size_t written = 0;
4467         while (!ferror(pipe) && written < bufsize) {
4468                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4469         }
4471         fputc('\n', pipe);
4473         return written == bufsize;
4476 static bool
4477 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4479         while (line < end) {
4480                 if (!stage_diff_line(pipe, line++))
4481                         return FALSE;
4482                 if (line->type == LINE_DIFF_CHUNK ||
4483                     line->type == LINE_DIFF_HEADER)
4484                         break;
4485         }
4487         return TRUE;
4490 static struct line *
4491 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4493         for (; view->line < line; line--)
4494                 if (line->type == type)
4495                         return line;
4497         return NULL;
4500 static bool
4501 stage_update_chunk(struct view *view, struct line *chunk)
4503         char cmd[SIZEOF_STR];
4504         size_t cmdsize = 0;
4505         struct line *diff_hdr;
4506         FILE *pipe;
4508         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4509         if (!diff_hdr)
4510                 return FALSE;
4512         if (opt_cdup[0] &&
4513             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4514                 return FALSE;
4516         if (!string_format_from(cmd, &cmdsize,
4517                                 "git apply --whitespace=nowarn --cached %s - && "
4518                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4519                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4520                 return FALSE;
4522         pipe = popen(cmd, "w");
4523         if (!pipe)
4524                 return FALSE;
4526         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4527             !stage_diff_write(pipe, chunk, view->line + view->lines))
4528                 chunk = NULL;
4530         pclose(pipe);
4532         return chunk ? TRUE : FALSE;
4535 static bool
4536 stage_update(struct view *view, struct line *line)
4538         struct line *chunk = NULL;
4540         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4541                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4543         if (chunk) {
4544                 if (!stage_update_chunk(view, chunk)) {
4545                         report("Failed to apply chunk");
4546                         return FALSE;
4547                 }
4549         } else if (!status_update_file(&stage_status, stage_line_type)) {
4550                 report("Failed to update file");
4551                 return FALSE;
4552         }
4554         return TRUE;
4557 static enum request
4558 stage_request(struct view *view, enum request request, struct line *line)
4560         switch (request) {
4561         case REQ_STATUS_UPDATE:
4562                 stage_update(view, line);
4563                 break;
4565         case REQ_EDIT:
4566                 if (!stage_status.new.name[0])
4567                         return request;
4569                 open_editor(stage_status.status != '?', stage_status.new.name);
4570                 break;
4572         case REQ_REFRESH:
4573                 /* Reload everything ... */
4574                 break;
4576         case REQ_VIEW_BLAME:
4577                 if (stage_status.new.name[0]) {
4578                         string_copy(opt_file, stage_status.new.name);
4579                         opt_ref[0] = 0;
4580                 }
4581                 return request;
4583         case REQ_ENTER:
4584                 return pager_request(view, request, line);
4586         default:
4587                 return request;
4588         }
4590         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4592         /* Check whether the staged entry still exists, and close the
4593          * stage view if it doesn't. */
4594         if (!status_exists(&stage_status, stage_line_type))
4595                 return REQ_VIEW_CLOSE;
4597         if (stage_line_type == LINE_STAT_UNTRACKED)
4598                 opt_pipe = fopen(stage_status.new.name, "r");
4599         else
4600                 string_copy(opt_cmd, view->cmd);
4601         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4603         return REQ_NONE;
4606 static struct view_ops stage_ops = {
4607         "line",
4608         NULL,
4609         pager_read,
4610         pager_draw,
4611         stage_request,
4612         pager_grep,
4613         pager_select,
4614 };
4617 /*
4618  * Revision graph
4619  */
4621 struct commit {
4622         char id[SIZEOF_REV];            /* SHA1 ID. */
4623         char title[128];                /* First line of the commit message. */
4624         char author[75];                /* Author of the commit. */
4625         struct tm time;                 /* Date from the author ident. */
4626         struct ref **refs;              /* Repository references. */
4627         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4628         size_t graph_size;              /* The width of the graph array. */
4629         bool has_parents;               /* Rewritten --parents seen. */
4630 };
4632 /* Size of rev graph with no  "padding" columns */
4633 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4635 struct rev_graph {
4636         struct rev_graph *prev, *next, *parents;
4637         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4638         size_t size;
4639         struct commit *commit;
4640         size_t pos;
4641         unsigned int boundary:1;
4642 };
4644 /* Parents of the commit being visualized. */
4645 static struct rev_graph graph_parents[4];
4647 /* The current stack of revisions on the graph. */
4648 static struct rev_graph graph_stacks[4] = {
4649         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4650         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4651         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4652         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4653 };
4655 static inline bool
4656 graph_parent_is_merge(struct rev_graph *graph)
4658         return graph->parents->size > 1;
4661 static inline void
4662 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4664         struct commit *commit = graph->commit;
4666         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4667                 commit->graph[commit->graph_size++] = symbol;
4670 static void
4671 done_rev_graph(struct rev_graph *graph)
4673         if (graph_parent_is_merge(graph) &&
4674             graph->pos < graph->size - 1 &&
4675             graph->next->size == graph->size + graph->parents->size - 1) {
4676                 size_t i = graph->pos + graph->parents->size - 1;
4678                 graph->commit->graph_size = i * 2;
4679                 while (i < graph->next->size - 1) {
4680                         append_to_rev_graph(graph, ' ');
4681                         append_to_rev_graph(graph, '\\');
4682                         i++;
4683                 }
4684         }
4686         graph->size = graph->pos = 0;
4687         graph->commit = NULL;
4688         memset(graph->parents, 0, sizeof(*graph->parents));
4691 static void
4692 push_rev_graph(struct rev_graph *graph, char *parent)
4694         int i;
4696         /* "Collapse" duplicate parents lines.
4697          *
4698          * FIXME: This needs to also update update the drawn graph but
4699          * for now it just serves as a method for pruning graph lines. */
4700         for (i = 0; i < graph->size; i++)
4701                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4702                         return;
4704         if (graph->size < SIZEOF_REVITEMS) {
4705                 string_copy_rev(graph->rev[graph->size++], parent);
4706         }
4709 static chtype
4710 get_rev_graph_symbol(struct rev_graph *graph)
4712         chtype symbol;
4714         if (graph->boundary)
4715                 symbol = REVGRAPH_BOUND;
4716         else if (graph->parents->size == 0)
4717                 symbol = REVGRAPH_INIT;
4718         else if (graph_parent_is_merge(graph))
4719                 symbol = REVGRAPH_MERGE;
4720         else if (graph->pos >= graph->size)
4721                 symbol = REVGRAPH_BRANCH;
4722         else
4723                 symbol = REVGRAPH_COMMIT;
4725         return symbol;
4728 static void
4729 draw_rev_graph(struct rev_graph *graph)
4731         struct rev_filler {
4732                 chtype separator, line;
4733         };
4734         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4735         static struct rev_filler fillers[] = {
4736                 { ' ',  REVGRAPH_LINE },
4737                 { '`',  '.' },
4738                 { '\'', ' ' },
4739                 { '/',  ' ' },
4740         };
4741         chtype symbol = get_rev_graph_symbol(graph);
4742         struct rev_filler *filler;
4743         size_t i;
4745         filler = &fillers[DEFAULT];
4747         for (i = 0; i < graph->pos; i++) {
4748                 append_to_rev_graph(graph, filler->line);
4749                 if (graph_parent_is_merge(graph->prev) &&
4750                     graph->prev->pos == i)
4751                         filler = &fillers[RSHARP];
4753                 append_to_rev_graph(graph, filler->separator);
4754         }
4756         /* Place the symbol for this revision. */
4757         append_to_rev_graph(graph, symbol);
4759         if (graph->prev->size > graph->size)
4760                 filler = &fillers[RDIAG];
4761         else
4762                 filler = &fillers[DEFAULT];
4764         i++;
4766         for (; i < graph->size; i++) {
4767                 append_to_rev_graph(graph, filler->separator);
4768                 append_to_rev_graph(graph, filler->line);
4769                 if (graph_parent_is_merge(graph->prev) &&
4770                     i < graph->prev->pos + graph->parents->size)
4771                         filler = &fillers[RSHARP];
4772                 if (graph->prev->size > graph->size)
4773                         filler = &fillers[LDIAG];
4774         }
4776         if (graph->prev->size > graph->size) {
4777                 append_to_rev_graph(graph, filler->separator);
4778                 if (filler->line != ' ')
4779                         append_to_rev_graph(graph, filler->line);
4780         }
4783 /* Prepare the next rev graph */
4784 static void
4785 prepare_rev_graph(struct rev_graph *graph)
4787         size_t i;
4789         /* First, traverse all lines of revisions up to the active one. */
4790         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4791                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4792                         break;
4794                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4795         }
4797         /* Interleave the new revision parent(s). */
4798         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4799                 push_rev_graph(graph->next, graph->parents->rev[i]);
4801         /* Lastly, put any remaining revisions. */
4802         for (i = graph->pos + 1; i < graph->size; i++)
4803                 push_rev_graph(graph->next, graph->rev[i]);
4806 static void
4807 update_rev_graph(struct rev_graph *graph)
4809         /* If this is the finalizing update ... */
4810         if (graph->commit)
4811                 prepare_rev_graph(graph);
4813         /* Graph visualization needs a one rev look-ahead,
4814          * so the first update doesn't visualize anything. */
4815         if (!graph->prev->commit)
4816                 return;
4818         draw_rev_graph(graph->prev);
4819         done_rev_graph(graph->prev->prev);
4823 /*
4824  * Main view backend
4825  */
4827 static bool
4828 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4830         char buf[DATE_COLS + 1];
4831         struct commit *commit = line->data;
4832         enum line_type type;
4833         int col = 0;
4834         size_t timelen;
4835         int space;
4837         if (!*commit->author)
4838                 return FALSE;
4840         space = view->width;
4841         wmove(view->win, lineno, col);
4843         if (selected) {
4844                 type = LINE_CURSOR;
4845                 wattrset(view->win, get_line_attr(type));
4846                 wchgat(view->win, -1, 0, type, NULL);
4847         } else {
4848                 type = LINE_MAIN_COMMIT;
4849                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4850         }
4852         if (opt_date) {
4853                 int n;
4855                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4856                 n = draw_text(view, buf, view->width - col, FALSE, selected);
4857                 draw_text(view, " ", view->width - col - n, FALSE, selected);
4859                 col += DATE_COLS;
4860                 wmove(view->win, lineno, col);
4861                 if (col >= view->width)
4862                         return TRUE;
4863         }
4864         if (type != LINE_CURSOR)
4865                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4867         if (opt_author) {
4868                 int max_len;
4870                 max_len = view->width - col;
4871                 if (max_len > AUTHOR_COLS - 1)
4872                         max_len = AUTHOR_COLS - 1;
4873                 draw_text(view, commit->author, max_len, TRUE, selected);
4874                 col += AUTHOR_COLS;
4875                 if (col >= view->width)
4876                         return TRUE;
4877         }
4879         if (opt_rev_graph && commit->graph_size) {
4880                 size_t graph_size = view->width - col;
4881                 size_t i;
4883                 if (type != LINE_CURSOR)
4884                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4885                 wmove(view->win, lineno, col);
4886                 if (graph_size > commit->graph_size)
4887                         graph_size = commit->graph_size;
4888                 /* Using waddch() instead of waddnstr() ensures that
4889                  * they'll be rendered correctly for the cursor line. */
4890                 for (i = 0; i < graph_size; i++)
4891                         waddch(view->win, commit->graph[i]);
4893                 col += commit->graph_size + 1;
4894                 if (col >= view->width)
4895                         return TRUE;
4896                 waddch(view->win, ' ');
4897         }
4898         if (type != LINE_CURSOR)
4899                 wattrset(view->win, A_NORMAL);
4901         wmove(view->win, lineno, col);
4903         if (opt_show_refs && commit->refs) {
4904                 size_t i = 0;
4906                 do {
4907                         if (type == LINE_CURSOR)
4908                                 ;
4909                         else if (commit->refs[i]->head)
4910                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4911                         else if (commit->refs[i]->ltag)
4912                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4913                         else if (commit->refs[i]->tag)
4914                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4915                         else if (commit->refs[i]->tracked)
4916                                 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
4917                         else if (commit->refs[i]->remote)
4918                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4919                         else
4920                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4922                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4923                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4924                                          TRUE, selected);
4925                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4926                         if (type != LINE_CURSOR)
4927                                 wattrset(view->win, A_NORMAL);
4928                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4929                         if (col >= view->width)
4930                                 return TRUE;
4931                 } while (commit->refs[i++]->next);
4932         }
4934         if (type != LINE_CURSOR)
4935                 wattrset(view->win, get_line_attr(type));
4937         draw_text(view, commit->title, view->width - col, TRUE, selected);
4938         return TRUE;
4941 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4942 static bool
4943 main_read(struct view *view, char *line)
4945         static struct rev_graph *graph = graph_stacks;
4946         enum line_type type;
4947         struct commit *commit;
4949         if (!line) {
4950                 update_rev_graph(graph);
4951                 return TRUE;
4952         }
4954         type = get_line_type(line);
4955         if (type == LINE_COMMIT) {
4956                 commit = calloc(1, sizeof(struct commit));
4957                 if (!commit)
4958                         return FALSE;
4960                 line += STRING_SIZE("commit ");
4961                 if (*line == '-') {
4962                         graph->boundary = 1;
4963                         line++;
4964                 }
4966                 string_copy_rev(commit->id, line);
4967                 commit->refs = get_refs(commit->id);
4968                 graph->commit = commit;
4969                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4971                 while ((line = strchr(line, ' '))) {
4972                         line++;
4973                         push_rev_graph(graph->parents, line);
4974                         commit->has_parents = TRUE;
4975                 }
4976                 return TRUE;
4977         }
4979         if (!view->lines)
4980                 return TRUE;
4981         commit = view->line[view->lines - 1].data;
4983         switch (type) {
4984         case LINE_PARENT:
4985                 if (commit->has_parents)
4986                         break;
4987                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4988                 break;
4990         case LINE_AUTHOR:
4991         {
4992                 /* Parse author lines where the name may be empty:
4993                  *      author  <email@address.tld> 1138474660 +0100
4994                  */
4995                 char *ident = line + STRING_SIZE("author ");
4996                 char *nameend = strchr(ident, '<');
4997                 char *emailend = strchr(ident, '>');
4999                 if (!nameend || !emailend)
5000                         break;
5002                 update_rev_graph(graph);
5003                 graph = graph->next;
5005                 *nameend = *emailend = 0;
5006                 ident = chomp_string(ident);
5007                 if (!*ident) {
5008                         ident = chomp_string(nameend + 1);
5009                         if (!*ident)
5010                                 ident = "Unknown";
5011                 }
5013                 string_ncopy(commit->author, ident, strlen(ident));
5015                 /* Parse epoch and timezone */
5016                 if (emailend[1] == ' ') {
5017                         char *secs = emailend + 2;
5018                         char *zone = strchr(secs, ' ');
5019                         time_t time = (time_t) atol(secs);
5021                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5022                                 long tz;
5024                                 zone++;
5025                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5026                                 tz += ('0' - zone[2]) * 60 * 60;
5027                                 tz += ('0' - zone[3]) * 60;
5028                                 tz += ('0' - zone[4]) * 60;
5030                                 if (zone[0] == '-')
5031                                         tz = -tz;
5033                                 time -= tz;
5034                         }
5036                         gmtime_r(&time, &commit->time);
5037                 }
5038                 break;
5039         }
5040         default:
5041                 /* Fill in the commit title if it has not already been set. */
5042                 if (commit->title[0])
5043                         break;
5045                 /* Require titles to start with a non-space character at the
5046                  * offset used by git log. */
5047                 if (strncmp(line, "    ", 4))
5048                         break;
5049                 line += 4;
5050                 /* Well, if the title starts with a whitespace character,
5051                  * try to be forgiving.  Otherwise we end up with no title. */
5052                 while (isspace(*line))
5053                         line++;
5054                 if (*line == '\0')
5055                         break;
5056                 /* FIXME: More graceful handling of titles; append "..." to
5057                  * shortened titles, etc. */
5059                 string_ncopy(commit->title, line, strlen(line));
5060         }
5062         return TRUE;
5065 static enum request
5066 main_request(struct view *view, enum request request, struct line *line)
5068         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5070         if (request == REQ_ENTER)
5071                 open_view(view, REQ_VIEW_DIFF, flags);
5072         else
5073                 return request;
5075         return REQ_NONE;
5078 static bool
5079 main_grep(struct view *view, struct line *line)
5081         struct commit *commit = line->data;
5082         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5083         char buf[DATE_COLS + 1];
5084         regmatch_t pmatch;
5086         for (state = S_TITLE; state < S_END; state++) {
5087                 char *text;
5089                 switch (state) {
5090                 case S_TITLE:   text = commit->title;   break;
5091                 case S_AUTHOR:  text = commit->author;  break;
5092                 case S_DATE:
5093                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5094                                 continue;
5095                         text = buf;
5096                         break;
5098                 default:
5099                         return FALSE;
5100                 }
5102                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5103                         return TRUE;
5104         }
5106         return FALSE;
5109 static void
5110 main_select(struct view *view, struct line *line)
5112         struct commit *commit = line->data;
5114         string_copy_rev(view->ref, commit->id);
5115         string_copy_rev(ref_commit, view->ref);
5118 static struct view_ops main_ops = {
5119         "commit",
5120         NULL,
5121         main_read,
5122         main_draw,
5123         main_request,
5124         main_grep,
5125         main_select,
5126 };
5129 /*
5130  * Unicode / UTF-8 handling
5131  *
5132  * NOTE: Much of the following code for dealing with unicode is derived from
5133  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5134  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5135  */
5137 /* I've (over)annotated a lot of code snippets because I am not entirely
5138  * confident that the approach taken by this small UTF-8 interface is correct.
5139  * --jonas */
5141 static inline int
5142 unicode_width(unsigned long c)
5144         if (c >= 0x1100 &&
5145            (c <= 0x115f                         /* Hangul Jamo */
5146             || c == 0x2329
5147             || c == 0x232a
5148             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5149                                                 /* CJK ... Yi */
5150             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5151             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5152             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5153             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5154             || (c >= 0xffe0  && c <= 0xffe6)
5155             || (c >= 0x20000 && c <= 0x2fffd)
5156             || (c >= 0x30000 && c <= 0x3fffd)))
5157                 return 2;
5159         if (c == '\t')
5160                 return opt_tab_size;
5162         return 1;
5165 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5166  * Illegal bytes are set one. */
5167 static const unsigned char utf8_bytes[256] = {
5168         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,
5169         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,
5170         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,
5171         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,
5172         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,
5173         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,
5174         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,
5175         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,
5176 };
5178 /* Decode UTF-8 multi-byte representation into a unicode character. */
5179 static inline unsigned long
5180 utf8_to_unicode(const char *string, size_t length)
5182         unsigned long unicode;
5184         switch (length) {
5185         case 1:
5186                 unicode  =   string[0];
5187                 break;
5188         case 2:
5189                 unicode  =  (string[0] & 0x1f) << 6;
5190                 unicode +=  (string[1] & 0x3f);
5191                 break;
5192         case 3:
5193                 unicode  =  (string[0] & 0x0f) << 12;
5194                 unicode += ((string[1] & 0x3f) << 6);
5195                 unicode +=  (string[2] & 0x3f);
5196                 break;
5197         case 4:
5198                 unicode  =  (string[0] & 0x0f) << 18;
5199                 unicode += ((string[1] & 0x3f) << 12);
5200                 unicode += ((string[2] & 0x3f) << 6);
5201                 unicode +=  (string[3] & 0x3f);
5202                 break;
5203         case 5:
5204                 unicode  =  (string[0] & 0x0f) << 24;
5205                 unicode += ((string[1] & 0x3f) << 18);
5206                 unicode += ((string[2] & 0x3f) << 12);
5207                 unicode += ((string[3] & 0x3f) << 6);
5208                 unicode +=  (string[4] & 0x3f);
5209                 break;
5210         case 6:
5211                 unicode  =  (string[0] & 0x01) << 30;
5212                 unicode += ((string[1] & 0x3f) << 24);
5213                 unicode += ((string[2] & 0x3f) << 18);
5214                 unicode += ((string[3] & 0x3f) << 12);
5215                 unicode += ((string[4] & 0x3f) << 6);
5216                 unicode +=  (string[5] & 0x3f);
5217                 break;
5218         default:
5219                 die("Invalid unicode length");
5220         }
5222         /* Invalid characters could return the special 0xfffd value but NUL
5223          * should be just as good. */
5224         return unicode > 0xffff ? 0 : unicode;
5227 /* Calculates how much of string can be shown within the given maximum width
5228  * and sets trimmed parameter to non-zero value if all of string could not be
5229  * shown. If the reserve flag is TRUE, it will reserve at least one
5230  * trailing character, which can be useful when drawing a delimiter.
5231  *
5232  * Returns the number of bytes to output from string to satisfy max_width. */
5233 static size_t
5234 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5236         const char *start = string;
5237         const char *end = strchr(string, '\0');
5238         unsigned char last_bytes = 0;
5239         size_t width = 0;
5241         *trimmed = 0;
5243         while (string < end) {
5244                 int c = *(unsigned char *) string;
5245                 unsigned char bytes = utf8_bytes[c];
5246                 size_t ucwidth;
5247                 unsigned long unicode;
5249                 if (string + bytes > end)
5250                         break;
5252                 /* Change representation to figure out whether
5253                  * it is a single- or double-width character. */
5255                 unicode = utf8_to_unicode(string, bytes);
5256                 /* FIXME: Graceful handling of invalid unicode character. */
5257                 if (!unicode)
5258                         break;
5260                 ucwidth = unicode_width(unicode);
5261                 width  += ucwidth;
5262                 if (width > max_width) {
5263                         *trimmed = 1;
5264                         if (reserve && width - ucwidth == max_width) {
5265                                 string -= last_bytes;
5266                         }
5267                         break;
5268                 }
5270                 string  += bytes;
5271                 last_bytes = bytes;
5272         }
5274         return string - start;
5278 /*
5279  * Status management
5280  */
5282 /* Whether or not the curses interface has been initialized. */
5283 static bool cursed = FALSE;
5285 /* The status window is used for polling keystrokes. */
5286 static WINDOW *status_win;
5288 static bool status_empty = TRUE;
5290 /* Update status and title window. */
5291 static void
5292 report(const char *msg, ...)
5294         struct view *view = display[current_view];
5296         if (input_mode)
5297                 return;
5299         if (!view) {
5300                 char buf[SIZEOF_STR];
5301                 va_list args;
5303                 va_start(args, msg);
5304                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5305                         buf[sizeof(buf) - 1] = 0;
5306                         buf[sizeof(buf) - 2] = '.';
5307                         buf[sizeof(buf) - 3] = '.';
5308                         buf[sizeof(buf) - 4] = '.';
5309                 }
5310                 va_end(args);
5311                 die("%s", buf);
5312         }
5314         if (!status_empty || *msg) {
5315                 va_list args;
5317                 va_start(args, msg);
5319                 wmove(status_win, 0, 0);
5320                 if (*msg) {
5321                         vwprintw(status_win, msg, args);
5322                         status_empty = FALSE;
5323                 } else {
5324                         status_empty = TRUE;
5325                 }
5326                 wclrtoeol(status_win);
5327                 wrefresh(status_win);
5329                 va_end(args);
5330         }
5332         update_view_title(view);
5333         update_display_cursor(view);
5336 /* Controls when nodelay should be in effect when polling user input. */
5337 static void
5338 set_nonblocking_input(bool loading)
5340         static unsigned int loading_views;
5342         if ((loading == FALSE && loading_views-- == 1) ||
5343             (loading == TRUE  && loading_views++ == 0))
5344                 nodelay(status_win, loading);
5347 static void
5348 init_display(void)
5350         int x, y;
5352         /* Initialize the curses library */
5353         if (isatty(STDIN_FILENO)) {
5354                 cursed = !!initscr();
5355         } else {
5356                 /* Leave stdin and stdout alone when acting as a pager. */
5357                 FILE *io = fopen("/dev/tty", "r+");
5359                 if (!io)
5360                         die("Failed to open /dev/tty");
5361                 cursed = !!newterm(NULL, io, io);
5362         }
5364         if (!cursed)
5365                 die("Failed to initialize curses");
5367         nonl();         /* Tell curses not to do NL->CR/NL on output */
5368         cbreak();       /* Take input chars one at a time, no wait for \n */
5369         noecho();       /* Don't echo input */
5370         leaveok(stdscr, TRUE);
5372         if (has_colors())
5373                 init_colors();
5375         getmaxyx(stdscr, y, x);
5376         status_win = newwin(1, 0, y - 1, 0);
5377         if (!status_win)
5378                 die("Failed to create status window");
5380         /* Enable keyboard mapping */
5381         keypad(status_win, TRUE);
5382         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5385 static char *
5386 read_prompt(const char *prompt)
5388         enum { READING, STOP, CANCEL } status = READING;
5389         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5390         int pos = 0;
5392         while (status == READING) {
5393                 struct view *view;
5394                 int i, key;
5396                 input_mode = TRUE;
5398                 foreach_view (view, i)
5399                         update_view(view);
5401                 input_mode = FALSE;
5403                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5404                 wclrtoeol(status_win);
5406                 /* Refresh, accept single keystroke of input */
5407                 key = wgetch(status_win);
5408                 switch (key) {
5409                 case KEY_RETURN:
5410                 case KEY_ENTER:
5411                 case '\n':
5412                         status = pos ? STOP : CANCEL;
5413                         break;
5415                 case KEY_BACKSPACE:
5416                         if (pos > 0)
5417                                 pos--;
5418                         else
5419                                 status = CANCEL;
5420                         break;
5422                 case KEY_ESC:
5423                         status = CANCEL;
5424                         break;
5426                 case ERR:
5427                         break;
5429                 default:
5430                         if (pos >= sizeof(buf)) {
5431                                 report("Input string too long");
5432                                 return NULL;
5433                         }
5435                         if (isprint(key))
5436                                 buf[pos++] = (char) key;
5437                 }
5438         }
5440         /* Clear the status window */
5441         status_empty = FALSE;
5442         report("");
5444         if (status == CANCEL)
5445                 return NULL;
5447         buf[pos++] = 0;
5449         return buf;
5452 /*
5453  * Repository references
5454  */
5456 static struct ref *refs = NULL;
5457 static size_t refs_alloc = 0;
5458 static size_t refs_size = 0;
5460 /* Id <-> ref store */
5461 static struct ref ***id_refs = NULL;
5462 static size_t id_refs_alloc = 0;
5463 static size_t id_refs_size = 0;
5465 static struct ref **
5466 get_refs(char *id)
5468         struct ref ***tmp_id_refs;
5469         struct ref **ref_list = NULL;
5470         size_t ref_list_alloc = 0;
5471         size_t ref_list_size = 0;
5472         size_t i;
5474         for (i = 0; i < id_refs_size; i++)
5475                 if (!strcmp(id, id_refs[i][0]->id))
5476                         return id_refs[i];
5478         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5479                                     sizeof(*id_refs));
5480         if (!tmp_id_refs)
5481                 return NULL;
5483         id_refs = tmp_id_refs;
5485         for (i = 0; i < refs_size; i++) {
5486                 struct ref **tmp;
5488                 if (strcmp(id, refs[i].id))
5489                         continue;
5491                 tmp = realloc_items(ref_list, &ref_list_alloc,
5492                                     ref_list_size + 1, sizeof(*ref_list));
5493                 if (!tmp) {
5494                         if (ref_list)
5495                                 free(ref_list);
5496                         return NULL;
5497                 }
5499                 ref_list = tmp;
5500                 if (ref_list_size > 0)
5501                         ref_list[ref_list_size - 1]->next = 1;
5502                 ref_list[ref_list_size] = &refs[i];
5504                 /* XXX: The properties of the commit chains ensures that we can
5505                  * safely modify the shared ref. The repo references will
5506                  * always be similar for the same id. */
5507                 ref_list[ref_list_size]->next = 0;
5508                 ref_list_size++;
5509         }
5511         if (ref_list)
5512                 id_refs[id_refs_size++] = ref_list;
5514         return ref_list;
5517 static int
5518 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5520         struct ref *ref;
5521         bool tag = FALSE;
5522         bool ltag = FALSE;
5523         bool remote = FALSE;
5524         bool tracked = FALSE;
5525         bool check_replace = FALSE;
5526         bool head = FALSE;
5528         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5529                 if (!strcmp(name + namelen - 3, "^{}")) {
5530                         namelen -= 3;
5531                         name[namelen] = 0;
5532                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5533                                 check_replace = TRUE;
5534                 } else {
5535                         ltag = TRUE;
5536                 }
5538                 tag = TRUE;
5539                 namelen -= STRING_SIZE("refs/tags/");
5540                 name    += STRING_SIZE("refs/tags/");
5542         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5543                 remote = TRUE;
5544                 namelen -= STRING_SIZE("refs/remotes/");
5545                 name    += STRING_SIZE("refs/remotes/");
5546                 tracked  = !strcmp(opt_remote, name);
5548         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5549                 namelen -= STRING_SIZE("refs/heads/");
5550                 name    += STRING_SIZE("refs/heads/");
5551                 head     = !strncmp(opt_head, name, namelen);
5553         } else if (!strcmp(name, "HEAD")) {
5554                 opt_no_head = FALSE;
5555                 return OK;
5556         }
5558         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5559                 /* it's an annotated tag, replace the previous sha1 with the
5560                  * resolved commit id; relies on the fact git-ls-remote lists
5561                  * the commit id of an annotated tag right beofre the commit id
5562                  * it points to. */
5563                 refs[refs_size - 1].ltag = ltag;
5564                 string_copy_rev(refs[refs_size - 1].id, id);
5566                 return OK;
5567         }
5568         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5569         if (!refs)
5570                 return ERR;
5572         ref = &refs[refs_size++];
5573         ref->name = malloc(namelen + 1);
5574         if (!ref->name)
5575                 return ERR;
5577         strncpy(ref->name, name, namelen);
5578         ref->name[namelen] = 0;
5579         ref->head = head;
5580         ref->tag = tag;
5581         ref->ltag = ltag;
5582         ref->remote = remote;
5583         ref->tracked = tracked;
5584         string_copy_rev(ref->id, id);
5586         return OK;
5589 static int
5590 load_refs(void)
5592         const char *cmd_env = getenv("TIG_LS_REMOTE");
5593         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5595         return read_properties(popen(cmd, "r"), "\t", read_ref);
5598 static int
5599 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5601         if (!strcmp(name, "i18n.commitencoding"))
5602                 string_ncopy(opt_encoding, value, valuelen);
5604         if (!strcmp(name, "core.editor"))
5605                 string_ncopy(opt_editor, value, valuelen);
5607         /* branch.<head>.remote */
5608         if (*opt_head &&
5609             !strncmp(name, "branch.", 7) &&
5610             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5611             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5612                 string_ncopy(opt_remote, value, valuelen);
5614         if (*opt_head && *opt_remote &&
5615             !strncmp(name, "branch.", 7) &&
5616             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5617             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5618                 size_t from = strlen(opt_remote);
5620                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5621                         value += STRING_SIZE("refs/heads/");
5622                         valuelen -= STRING_SIZE("refs/heads/");
5623                 }
5625                 if (!string_format_from(opt_remote, &from, "/%s", value))
5626                         opt_remote[0] = 0;
5627         }
5629         return OK;
5632 static int
5633 load_git_config(void)
5635         return read_properties(popen(GIT_CONFIG " --list", "r"),
5636                                "=", read_repo_config_option);
5639 static int
5640 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5642         if (!opt_git_dir[0]) {
5643                 string_ncopy(opt_git_dir, name, namelen);
5645         } else if (opt_is_inside_work_tree == -1) {
5646                 /* This can be 3 different values depending on the
5647                  * version of git being used. If git-rev-parse does not
5648                  * understand --is-inside-work-tree it will simply echo
5649                  * the option else either "true" or "false" is printed.
5650                  * Default to true for the unknown case. */
5651                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5653         } else if (opt_cdup[0] == ' ') {
5654                 string_ncopy(opt_cdup, name, namelen);
5655         } else {
5656                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5657                         namelen -= STRING_SIZE("refs/heads/");
5658                         name    += STRING_SIZE("refs/heads/");
5659                         string_ncopy(opt_head, name, namelen);
5660                 }
5661         }
5663         return OK;
5666 static int
5667 load_repo_info(void)
5669         int result;
5670         FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
5671                            " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
5673         /* XXX: The line outputted by "--show-cdup" can be empty so
5674          * initialize it to something invalid to make it possible to
5675          * detect whether it has been set or not. */
5676         opt_cdup[0] = ' ';
5678         result = read_properties(pipe, "=", read_repo_info);
5679         if (opt_cdup[0] == ' ')
5680                 opt_cdup[0] = 0;
5682         return result;
5685 static int
5686 read_properties(FILE *pipe, const char *separators,
5687                 int (*read_property)(char *, size_t, char *, size_t))
5689         char buffer[BUFSIZ];
5690         char *name;
5691         int state = OK;
5693         if (!pipe)
5694                 return ERR;
5696         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5697                 char *value;
5698                 size_t namelen;
5699                 size_t valuelen;
5701                 name = chomp_string(name);
5702                 namelen = strcspn(name, separators);
5704                 if (name[namelen]) {
5705                         name[namelen] = 0;
5706                         value = chomp_string(name + namelen + 1);
5707                         valuelen = strlen(value);
5709                 } else {
5710                         value = "";
5711                         valuelen = 0;
5712                 }
5714                 state = read_property(name, namelen, value, valuelen);
5715         }
5717         if (state != ERR && ferror(pipe))
5718                 state = ERR;
5720         pclose(pipe);
5722         return state;
5726 /*
5727  * Main
5728  */
5730 static void __NORETURN
5731 quit(int sig)
5733         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5734         if (cursed)
5735                 endwin();
5736         exit(0);
5739 static void __NORETURN
5740 die(const char *err, ...)
5742         va_list args;
5744         endwin();
5746         va_start(args, err);
5747         fputs("tig: ", stderr);
5748         vfprintf(stderr, err, args);
5749         fputs("\n", stderr);
5750         va_end(args);
5752         exit(1);
5755 static void
5756 warn(const char *msg, ...)
5758         va_list args;
5760         va_start(args, msg);
5761         fputs("tig warning: ", stderr);
5762         vfprintf(stderr, msg, args);
5763         fputs("\n", stderr);
5764         va_end(args);
5767 int
5768 main(int argc, char *argv[])
5770         struct view *view;
5771         enum request request;
5772         size_t i;
5774         signal(SIGINT, quit);
5776         if (setlocale(LC_ALL, "")) {
5777                 char *codeset = nl_langinfo(CODESET);
5779                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5780         }
5782         if (load_repo_info() == ERR)
5783                 die("Failed to load repo info.");
5785         if (load_options() == ERR)
5786                 die("Failed to load user config.");
5788         if (load_git_config() == ERR)
5789                 die("Failed to load repo config.");
5791         if (!parse_options(argc, argv))
5792                 return 0;
5794         /* Require a git repository unless when running in pager mode. */
5795         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5796                 die("Not a git repository");
5798         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5799                 opt_utf8 = FALSE;
5801         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5802                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5803                 if (opt_iconv == ICONV_NONE)
5804                         die("Failed to initialize character set conversion");
5805         }
5807         if (*opt_git_dir && load_refs() == ERR)
5808                 die("Failed to load refs.");
5810         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5811                 view->cmd_env = getenv(view->cmd_env);
5813         request = opt_request;
5815         init_display();
5817         while (view_driver(display[current_view], request)) {
5818                 int key;
5819                 int i;
5821                 foreach_view (view, i)
5822                         update_view(view);
5824                 /* Refresh, accept single keystroke of input */
5825                 key = wgetch(status_win);
5827                 /* wgetch() with nodelay() enabled returns ERR when there's no
5828                  * input. */
5829                 if (key == ERR) {
5830                         request = REQ_NONE;
5831                         continue;
5832                 }
5834                 request = get_keybinding(display[current_view]->keymap, key);
5836                 /* Some low-level request handling. This keeps access to
5837                  * status_win restricted. */
5838                 switch (request) {
5839                 case REQ_PROMPT:
5840                 {
5841                         char *cmd = read_prompt(":");
5843                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5844                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5845                                         opt_request = REQ_VIEW_DIFF;
5846                                 } else {
5847                                         opt_request = REQ_VIEW_PAGER;
5848                                 }
5849                                 break;
5850                         }
5852                         request = REQ_NONE;
5853                         break;
5854                 }
5855                 case REQ_SEARCH:
5856                 case REQ_SEARCH_BACK:
5857                 {
5858                         const char *prompt = request == REQ_SEARCH
5859                                            ? "/" : "?";
5860                         char *search = read_prompt(prompt);
5862                         if (search)
5863                                 string_ncopy(opt_search, search, strlen(search));
5864                         else
5865                                 request = REQ_NONE;
5866                         break;
5867                 }
5868                 case REQ_SCREEN_RESIZE:
5869                 {
5870                         int height, width;
5872                         getmaxyx(stdscr, height, width);
5874                         /* Resize the status view and let the view driver take
5875                          * care of resizing the displayed views. */
5876                         wresize(status_win, 1, width);
5877                         mvwin(status_win, height - 1, 0);
5878                         wrefresh(status_win);
5879                         break;
5880                 }
5881                 default:
5882                         break;
5883                 }
5884         }
5886         quit(0);
5888         return 0;