Code

Fix regression from "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(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
590 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
591 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
592 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
593 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
599 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
601 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
604 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
607 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
612 enum line_type {
613 #define LINE(type, line, fg, bg, attr) \
614         LINE_##type
615         LINE_INFO
616 #undef  LINE
617 };
619 struct line_info {
620         const char *name;       /* Option name. */
621         int namelen;            /* Size of option name. */
622         const char *line;       /* The start of line to match. */
623         int linelen;            /* Size of string to match. */
624         int fg, bg, attr;       /* Color and text attributes for the lines. */
625 };
627 static struct line_info line_info[] = {
628 #define LINE(type, line, fg, bg, attr) \
629         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
630         LINE_INFO
631 #undef  LINE
632 };
634 static enum line_type
635 get_line_type(char *line)
637         int linelen = strlen(line);
638         enum line_type type;
640         for (type = 0; type < ARRAY_SIZE(line_info); type++)
641                 /* Case insensitive search matches Signed-off-by lines better. */
642                 if (linelen >= line_info[type].linelen &&
643                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
644                         return type;
646         return LINE_DEFAULT;
649 static inline int
650 get_line_attr(enum line_type type)
652         assert(type < ARRAY_SIZE(line_info));
653         return COLOR_PAIR(type) | line_info[type].attr;
656 static struct line_info *
657 get_line_info(char *name)
659         size_t namelen = strlen(name);
660         enum line_type type;
662         for (type = 0; type < ARRAY_SIZE(line_info); type++)
663                 if (namelen == line_info[type].namelen &&
664                     !string_enum_compare(line_info[type].name, name, namelen))
665                         return &line_info[type];
667         return NULL;
670 static void
671 init_colors(void)
673         int default_bg = line_info[LINE_DEFAULT].bg;
674         int default_fg = line_info[LINE_DEFAULT].fg;
675         enum line_type type;
677         start_color();
679         if (assume_default_colors(default_fg, default_bg) == ERR) {
680                 default_bg = COLOR_BLACK;
681                 default_fg = COLOR_WHITE;
682         }
684         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
685                 struct line_info *info = &line_info[type];
686                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
687                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
689                 init_pair(type, fg, bg);
690         }
693 struct line {
694         enum line_type type;
696         /* State flags */
697         unsigned int selected:1;
698         unsigned int dirty:1;
700         void *data;             /* User data */
701 };
704 /*
705  * Keys
706  */
708 struct keybinding {
709         int alias;
710         enum request request;
711         struct keybinding *next;
712 };
714 static struct keybinding default_keybindings[] = {
715         /* View switching */
716         { 'm',          REQ_VIEW_MAIN },
717         { 'd',          REQ_VIEW_DIFF },
718         { 'l',          REQ_VIEW_LOG },
719         { 't',          REQ_VIEW_TREE },
720         { 'f',          REQ_VIEW_BLOB },
721         { 'B',          REQ_VIEW_BLAME },
722         { 'p',          REQ_VIEW_PAGER },
723         { 'h',          REQ_VIEW_HELP },
724         { 'S',          REQ_VIEW_STATUS },
725         { 'c',          REQ_VIEW_STAGE },
727         /* View manipulation */
728         { 'q',          REQ_VIEW_CLOSE },
729         { KEY_TAB,      REQ_VIEW_NEXT },
730         { KEY_RETURN,   REQ_ENTER },
731         { KEY_UP,       REQ_PREVIOUS },
732         { KEY_DOWN,     REQ_NEXT },
733         { 'R',          REQ_REFRESH },
734         { 'O',          REQ_MAXIMIZE },
736         /* Cursor navigation */
737         { 'k',          REQ_MOVE_UP },
738         { 'j',          REQ_MOVE_DOWN },
739         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
740         { KEY_END,      REQ_MOVE_LAST_LINE },
741         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
742         { ' ',          REQ_MOVE_PAGE_DOWN },
743         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
744         { 'b',          REQ_MOVE_PAGE_UP },
745         { '-',          REQ_MOVE_PAGE_UP },
747         /* Scrolling */
748         { KEY_IC,       REQ_SCROLL_LINE_UP },
749         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
750         { 'w',          REQ_SCROLL_PAGE_UP },
751         { 's',          REQ_SCROLL_PAGE_DOWN },
753         /* Searching */
754         { '/',          REQ_SEARCH },
755         { '?',          REQ_SEARCH_BACK },
756         { 'n',          REQ_FIND_NEXT },
757         { 'N',          REQ_FIND_PREV },
759         /* Misc */
760         { 'Q',          REQ_QUIT },
761         { 'z',          REQ_STOP_LOADING },
762         { 'v',          REQ_SHOW_VERSION },
763         { 'r',          REQ_SCREEN_REDRAW },
764         { '.',          REQ_TOGGLE_LINENO },
765         { 'D',          REQ_TOGGLE_DATE },
766         { 'A',          REQ_TOGGLE_AUTHOR },
767         { 'g',          REQ_TOGGLE_REV_GRAPH },
768         { 'F',          REQ_TOGGLE_REFS },
769         { ':',          REQ_PROMPT },
770         { 'u',          REQ_STATUS_UPDATE },
771         { 'M',          REQ_STATUS_MERGE },
772         { ',',          REQ_TREE_PARENT },
773         { 'e',          REQ_EDIT },
775         /* Using the ncurses SIGWINCH handler. */
776         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
777 };
779 #define KEYMAP_INFO \
780         KEYMAP_(GENERIC), \
781         KEYMAP_(MAIN), \
782         KEYMAP_(DIFF), \
783         KEYMAP_(LOG), \
784         KEYMAP_(TREE), \
785         KEYMAP_(BLOB), \
786         KEYMAP_(BLAME), \
787         KEYMAP_(PAGER), \
788         KEYMAP_(HELP), \
789         KEYMAP_(STATUS), \
790         KEYMAP_(STAGE)
792 enum keymap {
793 #define KEYMAP_(name) KEYMAP_##name
794         KEYMAP_INFO
795 #undef  KEYMAP_
796 };
798 static struct int_map keymap_table[] = {
799 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
800         KEYMAP_INFO
801 #undef  KEYMAP_
802 };
804 #define set_keymap(map, name) \
805         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
807 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
809 static void
810 add_keybinding(enum keymap keymap, enum request request, int key)
812         struct keybinding *keybinding;
814         keybinding = calloc(1, sizeof(*keybinding));
815         if (!keybinding)
816                 die("Failed to allocate keybinding");
818         keybinding->alias = key;
819         keybinding->request = request;
820         keybinding->next = keybindings[keymap];
821         keybindings[keymap] = keybinding;
824 /* Looks for a key binding first in the given map, then in the generic map, and
825  * lastly in the default keybindings. */
826 static enum request
827 get_keybinding(enum keymap keymap, int key)
829         struct keybinding *kbd;
830         int i;
832         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
833                 if (kbd->alias == key)
834                         return kbd->request;
836         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
837                 if (kbd->alias == key)
838                         return kbd->request;
840         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
841                 if (default_keybindings[i].alias == key)
842                         return default_keybindings[i].request;
844         return (enum request) key;
848 struct key {
849         char *name;
850         int value;
851 };
853 static struct key key_table[] = {
854         { "Enter",      KEY_RETURN },
855         { "Space",      ' ' },
856         { "Backspace",  KEY_BACKSPACE },
857         { "Tab",        KEY_TAB },
858         { "Escape",     KEY_ESC },
859         { "Left",       KEY_LEFT },
860         { "Right",      KEY_RIGHT },
861         { "Up",         KEY_UP },
862         { "Down",       KEY_DOWN },
863         { "Insert",     KEY_IC },
864         { "Delete",     KEY_DC },
865         { "Hash",       '#' },
866         { "Home",       KEY_HOME },
867         { "End",        KEY_END },
868         { "PageUp",     KEY_PPAGE },
869         { "PageDown",   KEY_NPAGE },
870         { "F1",         KEY_F(1) },
871         { "F2",         KEY_F(2) },
872         { "F3",         KEY_F(3) },
873         { "F4",         KEY_F(4) },
874         { "F5",         KEY_F(5) },
875         { "F6",         KEY_F(6) },
876         { "F7",         KEY_F(7) },
877         { "F8",         KEY_F(8) },
878         { "F9",         KEY_F(9) },
879         { "F10",        KEY_F(10) },
880         { "F11",        KEY_F(11) },
881         { "F12",        KEY_F(12) },
882 };
884 static int
885 get_key_value(const char *name)
887         int i;
889         for (i = 0; i < ARRAY_SIZE(key_table); i++)
890                 if (!strcasecmp(key_table[i].name, name))
891                         return key_table[i].value;
893         if (strlen(name) == 1 && isprint(*name))
894                 return (int) *name;
896         return ERR;
899 static char *
900 get_key_name(int key_value)
902         static char key_char[] = "'X'";
903         char *seq = NULL;
904         int key;
906         for (key = 0; key < ARRAY_SIZE(key_table); key++)
907                 if (key_table[key].value == key_value)
908                         seq = key_table[key].name;
910         if (seq == NULL &&
911             key_value < 127 &&
912             isprint(key_value)) {
913                 key_char[1] = (char) key_value;
914                 seq = key_char;
915         }
917         return seq ? seq : "'?'";
920 static char *
921 get_key(enum request request)
923         static char buf[BUFSIZ];
924         size_t pos = 0;
925         char *sep = "";
926         int i;
928         buf[pos] = 0;
930         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
931                 struct keybinding *keybinding = &default_keybindings[i];
933                 if (keybinding->request != request)
934                         continue;
936                 if (!string_format_from(buf, &pos, "%s%s", sep,
937                                         get_key_name(keybinding->alias)))
938                         return "Too many keybindings!";
939                 sep = ", ";
940         }
942         return buf;
945 struct run_request {
946         enum keymap keymap;
947         int key;
948         char cmd[SIZEOF_STR];
949 };
951 static struct run_request *run_request;
952 static size_t run_requests;
954 static enum request
955 add_run_request(enum keymap keymap, int key, int argc, char **argv)
957         struct run_request *tmp;
958         struct run_request req = { keymap, key };
959         size_t bufpos;
961         for (bufpos = 0; argc > 0; argc--, argv++)
962                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
963                         return REQ_NONE;
965         req.cmd[bufpos - 1] = 0;
967         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
968         if (!tmp)
969                 return REQ_NONE;
971         run_request = tmp;
972         run_request[run_requests++] = req;
974         return REQ_NONE + run_requests;
977 static struct run_request *
978 get_run_request(enum request request)
980         if (request <= REQ_NONE)
981                 return NULL;
982         return &run_request[request - REQ_NONE - 1];
985 static void
986 add_builtin_run_requests(void)
988         struct {
989                 enum keymap keymap;
990                 int key;
991                 char *argv[1];
992         } reqs[] = {
993                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
994                 { KEYMAP_GENERIC, 'G', { "git gc" } },
995         };
996         int i;
998         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
999                 enum request req;
1001                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1002                 if (req != REQ_NONE)
1003                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1004         }
1007 /*
1008  * User config file handling.
1009  */
1011 static struct int_map color_map[] = {
1012 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1013         COLOR_MAP(DEFAULT),
1014         COLOR_MAP(BLACK),
1015         COLOR_MAP(BLUE),
1016         COLOR_MAP(CYAN),
1017         COLOR_MAP(GREEN),
1018         COLOR_MAP(MAGENTA),
1019         COLOR_MAP(RED),
1020         COLOR_MAP(WHITE),
1021         COLOR_MAP(YELLOW),
1022 };
1024 #define set_color(color, name) \
1025         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1027 static struct int_map attr_map[] = {
1028 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1029         ATTR_MAP(NORMAL),
1030         ATTR_MAP(BLINK),
1031         ATTR_MAP(BOLD),
1032         ATTR_MAP(DIM),
1033         ATTR_MAP(REVERSE),
1034         ATTR_MAP(STANDOUT),
1035         ATTR_MAP(UNDERLINE),
1036 };
1038 #define set_attribute(attr, name) \
1039         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1041 static int   config_lineno;
1042 static bool  config_errors;
1043 static char *config_msg;
1045 /* Wants: object fgcolor bgcolor [attr] */
1046 static int
1047 option_color_command(int argc, char *argv[])
1049         struct line_info *info;
1051         if (argc != 3 && argc != 4) {
1052                 config_msg = "Wrong number of arguments given to color command";
1053                 return ERR;
1054         }
1056         info = get_line_info(argv[0]);
1057         if (!info) {
1058                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1059                         info = get_line_info("delimiter");
1061                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1062                         info = get_line_info("date");
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 digits3 = view->digits < 3 ? 3 : view->digits;
1494         int max_number = MIN(digits3, STRING_SIZE(number));
1495         bool showtrimmed = FALSE;
1496         int col;
1498         lineno += view->offset + 1;
1499         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1500                 if (view->digits <= 9)
1501                         fmt[1] = '0' + digits3;
1503                 if (!string_format(number, fmt, lineno))
1504                         number[0] = 0;
1505                 showtrimmed = TRUE;
1506         }
1508         if (max < max_number)
1509                 max_number = max;
1511         if (!selected)
1512                 wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
1513         col = draw_text(view, number, max_number, showtrimmed, selected);
1514         if (col < max) {
1515                 if (!selected)
1516                         wattrset(view->win, A_NORMAL);
1517                 waddch(view->win, ACS_VLINE);
1518                 col++;
1519         }
1520         if (col < max) {
1521                 waddch(view->win, ' ');
1522                 col++;
1523         }
1525         return col;
1528 static int
1529 draw_date(struct view *view, struct tm *time, int max, bool selected)
1531         char buf[DATE_COLS];
1532         int col;
1533         int timelen = 0;
1535         if (max > DATE_COLS)
1536                 max = DATE_COLS;
1537         if (time)
1538                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1539         if (!timelen) {
1540                 memset(buf, ' ', sizeof(buf) - 1);
1541                 buf[sizeof(buf) - 1] = 0;
1542         }
1544         if (!selected)
1545                 wattrset(view->win, get_line_attr(LINE_DATE));
1546         col = draw_text(view, buf, max, FALSE, selected);
1547         if (col < max) {
1548                 if (!selected)
1549                         wattrset(view->win, get_line_attr(LINE_DEFAULT));
1550                 waddch(view->win, ' ');
1551                 col++;
1552         }
1554         return col;
1557 static bool
1558 draw_view_line(struct view *view, unsigned int lineno)
1560         struct line *line;
1561         bool selected = (view->offset + lineno == view->lineno);
1562         bool draw_ok;
1564         assert(view_is_displayed(view));
1566         if (view->offset + lineno >= view->lines)
1567                 return FALSE;
1569         line = &view->line[view->offset + lineno];
1571         wmove(view->win, lineno, 0);
1573         if (selected) {
1574                 line->selected = TRUE;
1575                 view->ops->select(view, line);
1576                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
1577                 wattrset(view->win, get_line_attr(LINE_CURSOR));
1578         } else if (line->selected) {
1579                 line->selected = FALSE;
1580                 wclrtoeol(view->win);
1581         }
1583         scrollok(view->win, FALSE);
1584         draw_ok = view->ops->draw(view, line, lineno, selected);
1585         scrollok(view->win, TRUE);
1587         return draw_ok;
1590 static void
1591 redraw_view_dirty(struct view *view)
1593         bool dirty = FALSE;
1594         int lineno;
1596         for (lineno = 0; lineno < view->height; lineno++) {
1597                 struct line *line = &view->line[view->offset + lineno];
1599                 if (!line->dirty)
1600                         continue;
1601                 line->dirty = 0;
1602                 dirty = TRUE;
1603                 if (!draw_view_line(view, lineno))
1604                         break;
1605         }
1607         if (!dirty)
1608                 return;
1609         redrawwin(view->win);
1610         if (input_mode)
1611                 wnoutrefresh(view->win);
1612         else
1613                 wrefresh(view->win);
1616 static void
1617 redraw_view_from(struct view *view, int lineno)
1619         assert(0 <= lineno && lineno < view->height);
1621         for (; lineno < view->height; lineno++) {
1622                 if (!draw_view_line(view, lineno))
1623                         break;
1624         }
1626         redrawwin(view->win);
1627         if (input_mode)
1628                 wnoutrefresh(view->win);
1629         else
1630                 wrefresh(view->win);
1633 static void
1634 redraw_view(struct view *view)
1636         wclear(view->win);
1637         redraw_view_from(view, 0);
1641 static void
1642 update_view_title(struct view *view)
1644         char buf[SIZEOF_STR];
1645         char state[SIZEOF_STR];
1646         size_t bufpos = 0, statelen = 0;
1648         assert(view_is_displayed(view));
1650         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1651                 unsigned int view_lines = view->offset + view->height;
1652                 unsigned int lines = view->lines
1653                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1654                                    : 0;
1656                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1657                                    view->ops->type,
1658                                    view->lineno + 1,
1659                                    view->lines,
1660                                    lines);
1662                 if (view->pipe) {
1663                         time_t secs = time(NULL) - view->start_time;
1665                         /* Three git seconds are a long time ... */
1666                         if (secs > 2)
1667                                 string_format_from(state, &statelen, " %lds", secs);
1668                 }
1669         }
1671         string_format_from(buf, &bufpos, "[%s]", view->name);
1672         if (*view->ref && bufpos < view->width) {
1673                 size_t refsize = strlen(view->ref);
1674                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1676                 if (minsize < view->width)
1677                         refsize = view->width - minsize + 7;
1678                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1679         }
1681         if (statelen && bufpos < view->width) {
1682                 string_format_from(buf, &bufpos, " %s", state);
1683         }
1685         if (view == display[current_view])
1686                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1687         else
1688                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1690         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1691         wclrtoeol(view->title);
1692         wmove(view->title, 0, view->width - 1);
1694         if (input_mode)
1695                 wnoutrefresh(view->title);
1696         else
1697                 wrefresh(view->title);
1700 static void
1701 resize_display(void)
1703         int offset, i;
1704         struct view *base = display[0];
1705         struct view *view = display[1] ? display[1] : display[0];
1707         /* Setup window dimensions */
1709         getmaxyx(stdscr, base->height, base->width);
1711         /* Make room for the status window. */
1712         base->height -= 1;
1714         if (view != base) {
1715                 /* Horizontal split. */
1716                 view->width   = base->width;
1717                 view->height  = SCALE_SPLIT_VIEW(base->height);
1718                 base->height -= view->height;
1720                 /* Make room for the title bar. */
1721                 view->height -= 1;
1722         }
1724         /* Make room for the title bar. */
1725         base->height -= 1;
1727         offset = 0;
1729         foreach_displayed_view (view, i) {
1730                 if (!view->win) {
1731                         view->win = newwin(view->height, 0, offset, 0);
1732                         if (!view->win)
1733                                 die("Failed to create %s view", view->name);
1735                         scrollok(view->win, TRUE);
1737                         view->title = newwin(1, 0, offset + view->height, 0);
1738                         if (!view->title)
1739                                 die("Failed to create title window");
1741                 } else {
1742                         wresize(view->win, view->height, view->width);
1743                         mvwin(view->win,   offset, 0);
1744                         mvwin(view->title, offset + view->height, 0);
1745                 }
1747                 offset += view->height + 1;
1748         }
1751 static void
1752 redraw_display(void)
1754         struct view *view;
1755         int i;
1757         foreach_displayed_view (view, i) {
1758                 redraw_view(view);
1759                 update_view_title(view);
1760         }
1763 static void
1764 update_display_cursor(struct view *view)
1766         /* Move the cursor to the right-most column of the cursor line.
1767          *
1768          * XXX: This could turn out to be a bit expensive, but it ensures that
1769          * the cursor does not jump around. */
1770         if (view->lines) {
1771                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1772                 wrefresh(view->win);
1773         }
1776 /*
1777  * Navigation
1778  */
1780 /* Scrolling backend */
1781 static void
1782 do_scroll_view(struct view *view, int lines)
1784         bool redraw_current_line = FALSE;
1786         /* The rendering expects the new offset. */
1787         view->offset += lines;
1789         assert(0 <= view->offset && view->offset < view->lines);
1790         assert(lines);
1792         /* Move current line into the view. */
1793         if (view->lineno < view->offset) {
1794                 view->lineno = view->offset;
1795                 redraw_current_line = TRUE;
1796         } else if (view->lineno >= view->offset + view->height) {
1797                 view->lineno = view->offset + view->height - 1;
1798                 redraw_current_line = TRUE;
1799         }
1801         assert(view->offset <= view->lineno && view->lineno < view->lines);
1803         /* Redraw the whole screen if scrolling is pointless. */
1804         if (view->height < ABS(lines)) {
1805                 redraw_view(view);
1807         } else {
1808                 int line = lines > 0 ? view->height - lines : 0;
1809                 int end = line + ABS(lines);
1811                 wscrl(view->win, lines);
1813                 for (; line < end; line++) {
1814                         if (!draw_view_line(view, line))
1815                                 break;
1816                 }
1818                 if (redraw_current_line)
1819                         draw_view_line(view, view->lineno - view->offset);
1820         }
1822         redrawwin(view->win);
1823         wrefresh(view->win);
1824         report("");
1827 /* Scroll frontend */
1828 static void
1829 scroll_view(struct view *view, enum request request)
1831         int lines = 1;
1833         assert(view_is_displayed(view));
1835         switch (request) {
1836         case REQ_SCROLL_PAGE_DOWN:
1837                 lines = view->height;
1838         case REQ_SCROLL_LINE_DOWN:
1839                 if (view->offset + lines > view->lines)
1840                         lines = view->lines - view->offset;
1842                 if (lines == 0 || view->offset + view->height >= view->lines) {
1843                         report("Cannot scroll beyond the last line");
1844                         return;
1845                 }
1846                 break;
1848         case REQ_SCROLL_PAGE_UP:
1849                 lines = view->height;
1850         case REQ_SCROLL_LINE_UP:
1851                 if (lines > view->offset)
1852                         lines = view->offset;
1854                 if (lines == 0) {
1855                         report("Cannot scroll beyond the first line");
1856                         return;
1857                 }
1859                 lines = -lines;
1860                 break;
1862         default:
1863                 die("request %d not handled in switch", request);
1864         }
1866         do_scroll_view(view, lines);
1869 /* Cursor moving */
1870 static void
1871 move_view(struct view *view, enum request request)
1873         int scroll_steps = 0;
1874         int steps;
1876         switch (request) {
1877         case REQ_MOVE_FIRST_LINE:
1878                 steps = -view->lineno;
1879                 break;
1881         case REQ_MOVE_LAST_LINE:
1882                 steps = view->lines - view->lineno - 1;
1883                 break;
1885         case REQ_MOVE_PAGE_UP:
1886                 steps = view->height > view->lineno
1887                       ? -view->lineno : -view->height;
1888                 break;
1890         case REQ_MOVE_PAGE_DOWN:
1891                 steps = view->lineno + view->height >= view->lines
1892                       ? view->lines - view->lineno - 1 : view->height;
1893                 break;
1895         case REQ_MOVE_UP:
1896                 steps = -1;
1897                 break;
1899         case REQ_MOVE_DOWN:
1900                 steps = 1;
1901                 break;
1903         default:
1904                 die("request %d not handled in switch", request);
1905         }
1907         if (steps <= 0 && view->lineno == 0) {
1908                 report("Cannot move beyond the first line");
1909                 return;
1911         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1912                 report("Cannot move beyond the last line");
1913                 return;
1914         }
1916         /* Move the current line */
1917         view->lineno += steps;
1918         assert(0 <= view->lineno && view->lineno < view->lines);
1920         /* Check whether the view needs to be scrolled */
1921         if (view->lineno < view->offset ||
1922             view->lineno >= view->offset + view->height) {
1923                 scroll_steps = steps;
1924                 if (steps < 0 && -steps > view->offset) {
1925                         scroll_steps = -view->offset;
1927                 } else if (steps > 0) {
1928                         if (view->lineno == view->lines - 1 &&
1929                             view->lines > view->height) {
1930                                 scroll_steps = view->lines - view->offset - 1;
1931                                 if (scroll_steps >= view->height)
1932                                         scroll_steps -= view->height - 1;
1933                         }
1934                 }
1935         }
1937         if (!view_is_displayed(view)) {
1938                 view->offset += scroll_steps;
1939                 assert(0 <= view->offset && view->offset < view->lines);
1940                 view->ops->select(view, &view->line[view->lineno]);
1941                 return;
1942         }
1944         /* Repaint the old "current" line if we be scrolling */
1945         if (ABS(steps) < view->height)
1946                 draw_view_line(view, view->lineno - steps - view->offset);
1948         if (scroll_steps) {
1949                 do_scroll_view(view, scroll_steps);
1950                 return;
1951         }
1953         /* Draw the current line */
1954         draw_view_line(view, view->lineno - view->offset);
1956         redrawwin(view->win);
1957         wrefresh(view->win);
1958         report("");
1962 /*
1963  * Searching
1964  */
1966 static void search_view(struct view *view, enum request request);
1968 static bool
1969 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1971         assert(view_is_displayed(view));
1973         if (!view->ops->grep(view, line))
1974                 return FALSE;
1976         if (lineno - view->offset >= view->height) {
1977                 view->offset = lineno;
1978                 view->lineno = lineno;
1979                 redraw_view(view);
1981         } else {
1982                 unsigned long old_lineno = view->lineno - view->offset;
1984                 view->lineno = lineno;
1985                 draw_view_line(view, old_lineno);
1987                 draw_view_line(view, view->lineno - view->offset);
1988                 redrawwin(view->win);
1989                 wrefresh(view->win);
1990         }
1992         report("Line %ld matches '%s'", lineno + 1, view->grep);
1993         return TRUE;
1996 static void
1997 find_next(struct view *view, enum request request)
1999         unsigned long lineno = view->lineno;
2000         int direction;
2002         if (!*view->grep) {
2003                 if (!*opt_search)
2004                         report("No previous search");
2005                 else
2006                         search_view(view, request);
2007                 return;
2008         }
2010         switch (request) {
2011         case REQ_SEARCH:
2012         case REQ_FIND_NEXT:
2013                 direction = 1;
2014                 break;
2016         case REQ_SEARCH_BACK:
2017         case REQ_FIND_PREV:
2018                 direction = -1;
2019                 break;
2021         default:
2022                 return;
2023         }
2025         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2026                 lineno += direction;
2028         /* Note, lineno is unsigned long so will wrap around in which case it
2029          * will become bigger than view->lines. */
2030         for (; lineno < view->lines; lineno += direction) {
2031                 struct line *line = &view->line[lineno];
2033                 if (find_next_line(view, lineno, line))
2034                         return;
2035         }
2037         report("No match found for '%s'", view->grep);
2040 static void
2041 search_view(struct view *view, enum request request)
2043         int regex_err;
2045         if (view->regex) {
2046                 regfree(view->regex);
2047                 *view->grep = 0;
2048         } else {
2049                 view->regex = calloc(1, sizeof(*view->regex));
2050                 if (!view->regex)
2051                         return;
2052         }
2054         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2055         if (regex_err != 0) {
2056                 char buf[SIZEOF_STR] = "unknown error";
2058                 regerror(regex_err, view->regex, buf, sizeof(buf));
2059                 report("Search failed: %s", buf);
2060                 return;
2061         }
2063         string_copy(view->grep, opt_search);
2065         find_next(view, request);
2068 /*
2069  * Incremental updating
2070  */
2072 static void
2073 end_update(struct view *view)
2075         if (!view->pipe)
2076                 return;
2077         set_nonblocking_input(FALSE);
2078         if (view->pipe == stdin)
2079                 fclose(view->pipe);
2080         else
2081                 pclose(view->pipe);
2082         view->pipe = NULL;
2085 static bool
2086 begin_update(struct view *view)
2088         if (view->pipe)
2089                 end_update(view);
2091         if (opt_cmd[0]) {
2092                 string_copy(view->cmd, opt_cmd);
2093                 opt_cmd[0] = 0;
2094                 /* When running random commands, initially show the
2095                  * command in the title. However, it maybe later be
2096                  * overwritten if a commit line is selected. */
2097                 if (view == VIEW(REQ_VIEW_PAGER))
2098                         string_copy(view->ref, view->cmd);
2099                 else
2100                         view->ref[0] = 0;
2102         } else if (view == VIEW(REQ_VIEW_TREE)) {
2103                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2104                 char path[SIZEOF_STR];
2106                 if (strcmp(view->vid, view->id))
2107                         opt_path[0] = path[0] = 0;
2108                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2109                         return FALSE;
2111                 if (!string_format(view->cmd, format, view->id, path))
2112                         return FALSE;
2114         } else {
2115                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2116                 const char *id = view->id;
2118                 if (!string_format(view->cmd, format, id, id, id, id, id))
2119                         return FALSE;
2121                 /* Put the current ref_* value to the view title ref
2122                  * member. This is needed by the blob view. Most other
2123                  * views sets it automatically after loading because the
2124                  * first line is a commit line. */
2125                 string_copy_rev(view->ref, view->id);
2126         }
2128         /* Special case for the pager view. */
2129         if (opt_pipe) {
2130                 view->pipe = opt_pipe;
2131                 opt_pipe = NULL;
2132         } else {
2133                 view->pipe = popen(view->cmd, "r");
2134         }
2136         if (!view->pipe)
2137                 return FALSE;
2139         set_nonblocking_input(TRUE);
2141         view->offset = 0;
2142         view->lines  = 0;
2143         view->lineno = 0;
2144         string_copy_rev(view->vid, view->id);
2146         if (view->line) {
2147                 int i;
2149                 for (i = 0; i < view->lines; i++)
2150                         if (view->line[i].data)
2151                                 free(view->line[i].data);
2153                 free(view->line);
2154                 view->line = NULL;
2155         }
2157         view->start_time = time(NULL);
2159         return TRUE;
2162 #define ITEM_CHUNK_SIZE 256
2163 static void *
2164 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2166         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2167         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2169         if (mem == NULL || num_chunks != num_chunks_new) {
2170                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2171                 mem = realloc(mem, *size * item_size);
2172         }
2174         return mem;
2177 static struct line *
2178 realloc_lines(struct view *view, size_t line_size)
2180         size_t alloc = view->line_alloc;
2181         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2182                                          sizeof(*view->line));
2184         if (!tmp)
2185                 return NULL;
2187         view->line = tmp;
2188         view->line_alloc = alloc;
2189         view->line_size = line_size;
2190         return view->line;
2193 static bool
2194 update_view(struct view *view)
2196         char in_buffer[BUFSIZ];
2197         char out_buffer[BUFSIZ * 2];
2198         char *line;
2199         /* The number of lines to read. If too low it will cause too much
2200          * redrawing (and possible flickering), if too high responsiveness
2201          * will suffer. */
2202         unsigned long lines = view->height;
2203         int redraw_from = -1;
2205         if (!view->pipe)
2206                 return TRUE;
2208         /* Only redraw if lines are visible. */
2209         if (view->offset + view->height >= view->lines)
2210                 redraw_from = view->lines - view->offset;
2212         /* FIXME: This is probably not perfect for backgrounded views. */
2213         if (!realloc_lines(view, view->lines + lines))
2214                 goto alloc_error;
2216         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2217                 size_t linelen = strlen(line);
2219                 if (linelen)
2220                         line[linelen - 1] = 0;
2222                 if (opt_iconv != ICONV_NONE) {
2223                         ICONV_CONST char *inbuf = line;
2224                         size_t inlen = linelen;
2226                         char *outbuf = out_buffer;
2227                         size_t outlen = sizeof(out_buffer);
2229                         size_t ret;
2231                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2232                         if (ret != (size_t) -1) {
2233                                 line = out_buffer;
2234                                 linelen = strlen(out_buffer);
2235                         }
2236                 }
2238                 if (!view->ops->read(view, line))
2239                         goto alloc_error;
2241                 if (lines-- == 1)
2242                         break;
2243         }
2245         {
2246                 int digits;
2248                 lines = view->lines;
2249                 for (digits = 0; lines; digits++)
2250                         lines /= 10;
2252                 /* Keep the displayed view in sync with line number scaling. */
2253                 if (digits != view->digits) {
2254                         view->digits = digits;
2255                         redraw_from = 0;
2256                 }
2257         }
2259         if (!view_is_displayed(view))
2260                 goto check_pipe;
2262         if (view == VIEW(REQ_VIEW_TREE)) {
2263                 /* Clear the view and redraw everything since the tree sorting
2264                  * might have rearranged things. */
2265                 redraw_view(view);
2267         } else if (redraw_from >= 0) {
2268                 /* If this is an incremental update, redraw the previous line
2269                  * since for commits some members could have changed when
2270                  * loading the main view. */
2271                 if (redraw_from > 0)
2272                         redraw_from--;
2274                 /* Since revision graph visualization requires knowledge
2275                  * about the parent commit, it causes a further one-off
2276                  * needed to be redrawn for incremental updates. */
2277                 if (redraw_from > 0 && opt_rev_graph)
2278                         redraw_from--;
2280                 /* Incrementally draw avoids flickering. */
2281                 redraw_view_from(view, redraw_from);
2282         }
2284         if (view == VIEW(REQ_VIEW_BLAME))
2285                 redraw_view_dirty(view);
2287         /* Update the title _after_ the redraw so that if the redraw picks up a
2288          * commit reference in view->ref it'll be available here. */
2289         update_view_title(view);
2291 check_pipe:
2292         if (ferror(view->pipe)) {
2293                 report("Failed to read: %s", strerror(errno));
2294                 goto end;
2296         } else if (feof(view->pipe)) {
2297                 report("");
2298                 goto end;
2299         }
2301         return TRUE;
2303 alloc_error:
2304         report("Allocation failure");
2306 end:
2307         if (view->ops->read(view, NULL))
2308                 end_update(view);
2309         return FALSE;
2312 static struct line *
2313 add_line_data(struct view *view, void *data, enum line_type type)
2315         struct line *line = &view->line[view->lines++];
2317         memset(line, 0, sizeof(*line));
2318         line->type = type;
2319         line->data = data;
2321         return line;
2324 static struct line *
2325 add_line_text(struct view *view, char *data, enum line_type type)
2327         if (data)
2328                 data = strdup(data);
2330         return data ? add_line_data(view, data, type) : NULL;
2334 /*
2335  * View opening
2336  */
2338 enum open_flags {
2339         OPEN_DEFAULT = 0,       /* Use default view switching. */
2340         OPEN_SPLIT = 1,         /* Split current view. */
2341         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2342         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2343         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2344 };
2346 static void
2347 open_view(struct view *prev, enum request request, enum open_flags flags)
2349         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2350         bool split = !!(flags & OPEN_SPLIT);
2351         bool reload = !!(flags & OPEN_RELOAD);
2352         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2353         struct view *view = VIEW(request);
2354         int nviews = displayed_views();
2355         struct view *base_view = display[0];
2357         if (view == prev && nviews == 1 && !reload) {
2358                 report("Already in %s view", view->name);
2359                 return;
2360         }
2362         if (view->git_dir && !opt_git_dir[0]) {
2363                 report("The %s view is disabled in pager view", view->name);
2364                 return;
2365         }
2367         if (split) {
2368                 display[1] = view;
2369                 if (!backgrounded)
2370                         current_view = 1;
2371         } else if (!nomaximize) {
2372                 /* Maximize the current view. */
2373                 memset(display, 0, sizeof(display));
2374                 current_view = 0;
2375                 display[current_view] = view;
2376         }
2378         /* Resize the view when switching between split- and full-screen,
2379          * or when switching between two different full-screen views. */
2380         if (nviews != displayed_views() ||
2381             (nviews == 1 && base_view != display[0]))
2382                 resize_display();
2384         if (view->ops->open) {
2385                 if (!view->ops->open(view)) {
2386                         report("Failed to load %s view", view->name);
2387                         return;
2388                 }
2390         } else if ((reload || strcmp(view->vid, view->id)) &&
2391                    !begin_update(view)) {
2392                 report("Failed to load %s view", view->name);
2393                 return;
2394         }
2396         if (split && prev->lineno - prev->offset >= prev->height) {
2397                 /* Take the title line into account. */
2398                 int lines = prev->lineno - prev->offset - prev->height + 1;
2400                 /* Scroll the view that was split if the current line is
2401                  * outside the new limited view. */
2402                 do_scroll_view(prev, lines);
2403         }
2405         if (prev && view != prev) {
2406                 if (split && !backgrounded) {
2407                         /* "Blur" the previous view. */
2408                         update_view_title(prev);
2409                 }
2411                 view->parent = prev;
2412         }
2414         if (view->pipe && view->lines == 0) {
2415                 /* Clear the old view and let the incremental updating refill
2416                  * the screen. */
2417                 werase(view->win);
2418                 report("");
2419         } else {
2420                 redraw_view(view);
2421                 report("");
2422         }
2424         /* If the view is backgrounded the above calls to report()
2425          * won't redraw the view title. */
2426         if (backgrounded)
2427                 update_view_title(view);
2430 static void
2431 open_external_viewer(const char *cmd)
2433         def_prog_mode();           /* save current tty modes */
2434         endwin();                  /* restore original tty modes */
2435         system(cmd);
2436         fprintf(stderr, "Press Enter to continue");
2437         getc(stdin);
2438         reset_prog_mode();
2439         redraw_display();
2442 static void
2443 open_mergetool(const char *file)
2445         char cmd[SIZEOF_STR];
2446         char file_sq[SIZEOF_STR];
2448         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2449             string_format(cmd, "git mergetool %s", file_sq)) {
2450                 open_external_viewer(cmd);
2451         }
2454 static void
2455 open_editor(bool from_root, const char *file)
2457         char cmd[SIZEOF_STR];
2458         char file_sq[SIZEOF_STR];
2459         char *editor;
2460         char *prefix = from_root ? opt_cdup : "";
2462         editor = getenv("GIT_EDITOR");
2463         if (!editor && *opt_editor)
2464                 editor = opt_editor;
2465         if (!editor)
2466                 editor = getenv("VISUAL");
2467         if (!editor)
2468                 editor = getenv("EDITOR");
2469         if (!editor)
2470                 editor = "vi";
2472         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2473             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2474                 open_external_viewer(cmd);
2475         }
2478 static void
2479 open_run_request(enum request request)
2481         struct run_request *req = get_run_request(request);
2482         char buf[SIZEOF_STR * 2];
2483         size_t bufpos;
2484         char *cmd;
2486         if (!req) {
2487                 report("Unknown run request");
2488                 return;
2489         }
2491         bufpos = 0;
2492         cmd = req->cmd;
2494         while (cmd) {
2495                 char *next = strstr(cmd, "%(");
2496                 int len = next - cmd;
2497                 char *value;
2499                 if (!next) {
2500                         len = strlen(cmd);
2501                         value = "";
2503                 } else if (!strncmp(next, "%(head)", 7)) {
2504                         value = ref_head;
2506                 } else if (!strncmp(next, "%(commit)", 9)) {
2507                         value = ref_commit;
2509                 } else if (!strncmp(next, "%(blob)", 7)) {
2510                         value = ref_blob;
2512                 } else {
2513                         report("Unknown replacement in run request: `%s`", req->cmd);
2514                         return;
2515                 }
2517                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2518                         return;
2520                 if (next)
2521                         next = strchr(next, ')') + 1;
2522                 cmd = next;
2523         }
2525         open_external_viewer(buf);
2528 /*
2529  * User request switch noodle
2530  */
2532 static int
2533 view_driver(struct view *view, enum request request)
2535         int i;
2537         if (request == REQ_NONE) {
2538                 doupdate();
2539                 return TRUE;
2540         }
2542         if (request > REQ_NONE) {
2543                 open_run_request(request);
2544                 /* FIXME: When all views can refresh always do this. */
2545                 if (view == VIEW(REQ_VIEW_STATUS) ||
2546                     view == VIEW(REQ_VIEW_STAGE))
2547                         request = REQ_REFRESH;
2548                 else
2549                         return TRUE;
2550         }
2552         if (view && view->lines) {
2553                 request = view->ops->request(view, request, &view->line[view->lineno]);
2554                 if (request == REQ_NONE)
2555                         return TRUE;
2556         }
2558         switch (request) {
2559         case REQ_MOVE_UP:
2560         case REQ_MOVE_DOWN:
2561         case REQ_MOVE_PAGE_UP:
2562         case REQ_MOVE_PAGE_DOWN:
2563         case REQ_MOVE_FIRST_LINE:
2564         case REQ_MOVE_LAST_LINE:
2565                 move_view(view, request);
2566                 break;
2568         case REQ_SCROLL_LINE_DOWN:
2569         case REQ_SCROLL_LINE_UP:
2570         case REQ_SCROLL_PAGE_DOWN:
2571         case REQ_SCROLL_PAGE_UP:
2572                 scroll_view(view, request);
2573                 break;
2575         case REQ_VIEW_BLAME:
2576                 if (!opt_file[0]) {
2577                         report("No file chosen, press %s to open tree view",
2578                                get_key(REQ_VIEW_TREE));
2579                         break;
2580                 }
2581                 open_view(view, request, OPEN_DEFAULT);
2582                 break;
2584         case REQ_VIEW_BLOB:
2585                 if (!ref_blob[0]) {
2586                         report("No file chosen, press %s to open tree view",
2587                                get_key(REQ_VIEW_TREE));
2588                         break;
2589                 }
2590                 open_view(view, request, OPEN_DEFAULT);
2591                 break;
2593         case REQ_VIEW_PAGER:
2594                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2595                         report("No pager content, press %s to run command from prompt",
2596                                get_key(REQ_PROMPT));
2597                         break;
2598                 }
2599                 open_view(view, request, OPEN_DEFAULT);
2600                 break;
2602         case REQ_VIEW_STAGE:
2603                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2604                         report("No stage content, press %s to open the status view and choose file",
2605                                get_key(REQ_VIEW_STATUS));
2606                         break;
2607                 }
2608                 open_view(view, request, OPEN_DEFAULT);
2609                 break;
2611         case REQ_VIEW_STATUS:
2612                 if (opt_is_inside_work_tree == FALSE) {
2613                         report("The status view requires a working tree");
2614                         break;
2615                 }
2616                 open_view(view, request, OPEN_DEFAULT);
2617                 break;
2619         case REQ_VIEW_MAIN:
2620         case REQ_VIEW_DIFF:
2621         case REQ_VIEW_LOG:
2622         case REQ_VIEW_TREE:
2623         case REQ_VIEW_HELP:
2624                 open_view(view, request, OPEN_DEFAULT);
2625                 break;
2627         case REQ_NEXT:
2628         case REQ_PREVIOUS:
2629                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2631                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2632                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2633                    (view == VIEW(REQ_VIEW_DIFF) &&
2634                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2635                    (view == VIEW(REQ_VIEW_STAGE) &&
2636                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2637                    (view == VIEW(REQ_VIEW_BLOB) &&
2638                      view->parent == VIEW(REQ_VIEW_TREE))) {
2639                         int line;
2641                         view = view->parent;
2642                         line = view->lineno;
2643                         move_view(view, request);
2644                         if (view_is_displayed(view))
2645                                 update_view_title(view);
2646                         if (line != view->lineno)
2647                                 view->ops->request(view, REQ_ENTER,
2648                                                    &view->line[view->lineno]);
2650                 } else {
2651                         move_view(view, request);
2652                 }
2653                 break;
2655         case REQ_VIEW_NEXT:
2656         {
2657                 int nviews = displayed_views();
2658                 int next_view = (current_view + 1) % nviews;
2660                 if (next_view == current_view) {
2661                         report("Only one view is displayed");
2662                         break;
2663                 }
2665                 current_view = next_view;
2666                 /* Blur out the title of the previous view. */
2667                 update_view_title(view);
2668                 report("");
2669                 break;
2670         }
2671         case REQ_REFRESH:
2672                 report("Refreshing is not yet supported for the %s view", view->name);
2673                 break;
2675         case REQ_MAXIMIZE:
2676                 if (displayed_views() == 2)
2677                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2678                 break;
2680         case REQ_TOGGLE_LINENO:
2681                 opt_line_number = !opt_line_number;
2682                 redraw_display();
2683                 break;
2685         case REQ_TOGGLE_DATE:
2686                 opt_date = !opt_date;
2687                 redraw_display();
2688                 break;
2690         case REQ_TOGGLE_AUTHOR:
2691                 opt_author = !opt_author;
2692                 redraw_display();
2693                 break;
2695         case REQ_TOGGLE_REV_GRAPH:
2696                 opt_rev_graph = !opt_rev_graph;
2697                 redraw_display();
2698                 break;
2700         case REQ_TOGGLE_REFS:
2701                 opt_show_refs = !opt_show_refs;
2702                 redraw_display();
2703                 break;
2705         case REQ_PROMPT:
2706                 /* Always reload^Wrerun commands from the prompt. */
2707                 open_view(view, opt_request, OPEN_RELOAD);
2708                 break;
2710         case REQ_SEARCH:
2711         case REQ_SEARCH_BACK:
2712                 search_view(view, request);
2713                 break;
2715         case REQ_FIND_NEXT:
2716         case REQ_FIND_PREV:
2717                 find_next(view, request);
2718                 break;
2720         case REQ_STOP_LOADING:
2721                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2722                         view = &views[i];
2723                         if (view->pipe)
2724                                 report("Stopped loading the %s view", view->name),
2725                         end_update(view);
2726                 }
2727                 break;
2729         case REQ_SHOW_VERSION:
2730                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2731                 return TRUE;
2733         case REQ_SCREEN_RESIZE:
2734                 resize_display();
2735                 /* Fall-through */
2736         case REQ_SCREEN_REDRAW:
2737                 redraw_display();
2738                 break;
2740         case REQ_EDIT:
2741                 report("Nothing to edit");
2742                 break;
2745         case REQ_ENTER:
2746                 report("Nothing to enter");
2747                 break;
2750         case REQ_VIEW_CLOSE:
2751                 /* XXX: Mark closed views by letting view->parent point to the
2752                  * view itself. Parents to closed view should never be
2753                  * followed. */
2754                 if (view->parent &&
2755                     view->parent->parent != view->parent) {
2756                         memset(display, 0, sizeof(display));
2757                         current_view = 0;
2758                         display[current_view] = view->parent;
2759                         view->parent = view;
2760                         resize_display();
2761                         redraw_display();
2762                         break;
2763                 }
2764                 /* Fall-through */
2765         case REQ_QUIT:
2766                 return FALSE;
2768         default:
2769                 /* An unknown key will show most commonly used commands. */
2770                 report("Unknown key, press 'h' for help");
2771                 return TRUE;
2772         }
2774         return TRUE;
2778 /*
2779  * Pager backend
2780  */
2782 static bool
2783 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2785         static char spaces[] = "                    ";
2786         char *text = line->data;
2787         int col = 0;
2789         if (opt_line_number) {
2790                 col += draw_lineno(view, lineno, view->width, selected);
2791                 if (col >= view->width)
2792                         return TRUE;
2793         }
2795         if (!selected)
2796                 wattrset(view->win, get_line_attr(line->type));
2798         if (opt_tab_size < TABSIZE) {
2799                 int col_offset = col;
2801                 col = 0;
2802                 while (text && col_offset + col < view->width) {
2803                         int cols_max = view->width - col_offset - col;
2804                         char *pos = text;
2805                         int cols;
2807                         if (*text == '\t') {
2808                                 text++;
2809                                 assert(sizeof(spaces) > TABSIZE);
2810                                 pos = spaces;
2811                                 cols = opt_tab_size - (col % opt_tab_size);
2813                         } else {
2814                                 text = strchr(text, '\t');
2815                                 cols = line ? text - pos : strlen(pos);
2816                         }
2818                         waddnstr(view->win, pos, MIN(cols, cols_max));
2819                         col += cols;
2820                 }
2822         } else {
2823                 draw_text(view, text, view->width - col, TRUE, selected);
2824         }
2826         return TRUE;
2829 static bool
2830 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2832         char refbuf[SIZEOF_STR];
2833         char *ref = NULL;
2834         FILE *pipe;
2836         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2837                 return TRUE;
2839         pipe = popen(refbuf, "r");
2840         if (!pipe)
2841                 return TRUE;
2843         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2844                 ref = chomp_string(ref);
2845         pclose(pipe);
2847         if (!ref || !*ref)
2848                 return TRUE;
2850         /* This is the only fatal call, since it can "corrupt" the buffer. */
2851         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2852                 return FALSE;
2854         return TRUE;
2857 static void
2858 add_pager_refs(struct view *view, struct line *line)
2860         char buf[SIZEOF_STR];
2861         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2862         struct ref **refs;
2863         size_t bufpos = 0, refpos = 0;
2864         const char *sep = "Refs: ";
2865         bool is_tag = FALSE;
2867         assert(line->type == LINE_COMMIT);
2869         refs = get_refs(commit_id);
2870         if (!refs) {
2871                 if (view == VIEW(REQ_VIEW_DIFF))
2872                         goto try_add_describe_ref;
2873                 return;
2874         }
2876         do {
2877                 struct ref *ref = refs[refpos];
2878                 char *fmt = ref->tag    ? "%s[%s]" :
2879                             ref->remote ? "%s<%s>" : "%s%s";
2881                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2882                         return;
2883                 sep = ", ";
2884                 if (ref->tag)
2885                         is_tag = TRUE;
2886         } while (refs[refpos++]->next);
2888         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2889 try_add_describe_ref:
2890                 /* Add <tag>-g<commit_id> "fake" reference. */
2891                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2892                         return;
2893         }
2895         if (bufpos == 0)
2896                 return;
2898         if (!realloc_lines(view, view->line_size + 1))
2899                 return;
2901         add_line_text(view, buf, LINE_PP_REFS);
2904 static bool
2905 pager_read(struct view *view, char *data)
2907         struct line *line;
2909         if (!data)
2910                 return TRUE;
2912         line = add_line_text(view, data, get_line_type(data));
2913         if (!line)
2914                 return FALSE;
2916         if (line->type == LINE_COMMIT &&
2917             (view == VIEW(REQ_VIEW_DIFF) ||
2918              view == VIEW(REQ_VIEW_LOG)))
2919                 add_pager_refs(view, line);
2921         return TRUE;
2924 static enum request
2925 pager_request(struct view *view, enum request request, struct line *line)
2927         int split = 0;
2929         if (request != REQ_ENTER)
2930                 return request;
2932         if (line->type == LINE_COMMIT &&
2933            (view == VIEW(REQ_VIEW_LOG) ||
2934             view == VIEW(REQ_VIEW_PAGER))) {
2935                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2936                 split = 1;
2937         }
2939         /* Always scroll the view even if it was split. That way
2940          * you can use Enter to scroll through the log view and
2941          * split open each commit diff. */
2942         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2944         /* FIXME: A minor workaround. Scrolling the view will call report("")
2945          * but if we are scrolling a non-current view this won't properly
2946          * update the view title. */
2947         if (split)
2948                 update_view_title(view);
2950         return REQ_NONE;
2953 static bool
2954 pager_grep(struct view *view, struct line *line)
2956         regmatch_t pmatch;
2957         char *text = line->data;
2959         if (!*text)
2960                 return FALSE;
2962         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2963                 return FALSE;
2965         return TRUE;
2968 static void
2969 pager_select(struct view *view, struct line *line)
2971         if (line->type == LINE_COMMIT) {
2972                 char *text = (char *)line->data + STRING_SIZE("commit ");
2974                 if (view != VIEW(REQ_VIEW_PAGER))
2975                         string_copy_rev(view->ref, text);
2976                 string_copy_rev(ref_commit, text);
2977         }
2980 static struct view_ops pager_ops = {
2981         "line",
2982         NULL,
2983         pager_read,
2984         pager_draw,
2985         pager_request,
2986         pager_grep,
2987         pager_select,
2988 };
2991 /*
2992  * Help backend
2993  */
2995 static bool
2996 help_open(struct view *view)
2998         char buf[BUFSIZ];
2999         int lines = ARRAY_SIZE(req_info) + 2;
3000         int i;
3002         if (view->lines > 0)
3003                 return TRUE;
3005         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3006                 if (!req_info[i].request)
3007                         lines++;
3009         lines += run_requests + 1;
3011         view->line = calloc(lines, sizeof(*view->line));
3012         if (!view->line)
3013                 return FALSE;
3015         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3017         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3018                 char *key;
3020                 if (req_info[i].request == REQ_NONE)
3021                         continue;
3023                 if (!req_info[i].request) {
3024                         add_line_text(view, "", LINE_DEFAULT);
3025                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3026                         continue;
3027                 }
3029                 key = get_key(req_info[i].request);
3030                 if (!*key)
3031                         key = "(no key defined)";
3033                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3034                         continue;
3036                 add_line_text(view, buf, LINE_DEFAULT);
3037         }
3039         if (run_requests) {
3040                 add_line_text(view, "", LINE_DEFAULT);
3041                 add_line_text(view, "External commands:", LINE_DEFAULT);
3042         }
3044         for (i = 0; i < run_requests; i++) {
3045                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3046                 char *key;
3048                 if (!req)
3049                         continue;
3051                 key = get_key_name(req->key);
3052                 if (!*key)
3053                         key = "(no key defined)";
3055                 if (!string_format(buf, "    %-10s %-14s `%s`",
3056                                    keymap_table[req->keymap].name,
3057                                    key, req->cmd))
3058                         continue;
3060                 add_line_text(view, buf, LINE_DEFAULT);
3061         }
3063         return TRUE;
3066 static struct view_ops help_ops = {
3067         "line",
3068         help_open,
3069         NULL,
3070         pager_draw,
3071         pager_request,
3072         pager_grep,
3073         pager_select,
3074 };
3077 /*
3078  * Tree backend
3079  */
3081 struct tree_stack_entry {
3082         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3083         unsigned long lineno;           /* Line number to restore */
3084         char *name;                     /* Position of name in opt_path */
3085 };
3087 /* The top of the path stack. */
3088 static struct tree_stack_entry *tree_stack = NULL;
3089 unsigned long tree_lineno = 0;
3091 static void
3092 pop_tree_stack_entry(void)
3094         struct tree_stack_entry *entry = tree_stack;
3096         tree_lineno = entry->lineno;
3097         entry->name[0] = 0;
3098         tree_stack = entry->prev;
3099         free(entry);
3102 static void
3103 push_tree_stack_entry(char *name, unsigned long lineno)
3105         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3106         size_t pathlen = strlen(opt_path);
3108         if (!entry)
3109                 return;
3111         entry->prev = tree_stack;
3112         entry->name = opt_path + pathlen;
3113         tree_stack = entry;
3115         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3116                 pop_tree_stack_entry();
3117                 return;
3118         }
3120         /* Move the current line to the first tree entry. */
3121         tree_lineno = 1;
3122         entry->lineno = lineno;
3125 /* Parse output from git-ls-tree(1):
3126  *
3127  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3128  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3129  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3130  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3131  */
3133 #define SIZEOF_TREE_ATTR \
3134         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3136 #define TREE_UP_FORMAT "040000 tree %s\t.."
3138 static int
3139 tree_compare_entry(enum line_type type1, char *name1,
3140                    enum line_type type2, char *name2)
3142         if (type1 != type2) {
3143                 if (type1 == LINE_TREE_DIR)
3144                         return -1;
3145                 return 1;
3146         }
3148         return strcmp(name1, name2);
3151 static char *
3152 tree_path(struct line *line)
3154         char *path = line->data;
3156         return path + SIZEOF_TREE_ATTR;
3159 static bool
3160 tree_read(struct view *view, char *text)
3162         size_t textlen = text ? strlen(text) : 0;
3163         char buf[SIZEOF_STR];
3164         unsigned long pos;
3165         enum line_type type;
3166         bool first_read = view->lines == 0;
3168         if (!text)
3169                 return TRUE;
3170         if (textlen <= SIZEOF_TREE_ATTR)
3171                 return FALSE;
3173         type = text[STRING_SIZE("100644 ")] == 't'
3174              ? LINE_TREE_DIR : LINE_TREE_FILE;
3176         if (first_read) {
3177                 /* Add path info line */
3178                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3179                     !realloc_lines(view, view->line_size + 1) ||
3180                     !add_line_text(view, buf, LINE_DEFAULT))
3181                         return FALSE;
3183                 /* Insert "link" to parent directory. */
3184                 if (*opt_path) {
3185                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3186                             !realloc_lines(view, view->line_size + 1) ||
3187                             !add_line_text(view, buf, LINE_TREE_DIR))
3188                                 return FALSE;
3189                 }
3190         }
3192         /* Strip the path part ... */
3193         if (*opt_path) {
3194                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3195                 size_t striplen = strlen(opt_path);
3196                 char *path = text + SIZEOF_TREE_ATTR;
3198                 if (pathlen > striplen)
3199                         memmove(path, path + striplen,
3200                                 pathlen - striplen + 1);
3201         }
3203         /* Skip "Directory ..." and ".." line. */
3204         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3205                 struct line *line = &view->line[pos];
3206                 char *path1 = tree_path(line);
3207                 char *path2 = text + SIZEOF_TREE_ATTR;
3208                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3210                 if (cmp <= 0)
3211                         continue;
3213                 text = strdup(text);
3214                 if (!text)
3215                         return FALSE;
3217                 if (view->lines > pos)
3218                         memmove(&view->line[pos + 1], &view->line[pos],
3219                                 (view->lines - pos) * sizeof(*line));
3221                 line = &view->line[pos];
3222                 line->data = text;
3223                 line->type = type;
3224                 view->lines++;
3225                 return TRUE;
3226         }
3228         if (!add_line_text(view, text, type))
3229                 return FALSE;
3231         if (tree_lineno > view->lineno) {
3232                 view->lineno = tree_lineno;
3233                 tree_lineno = 0;
3234         }
3236         return TRUE;
3239 static enum request
3240 tree_request(struct view *view, enum request request, struct line *line)
3242         enum open_flags flags;
3244         if (request == REQ_VIEW_BLAME) {
3245                 char *filename = tree_path(line);
3247                 if (line->type == LINE_TREE_DIR) {
3248                         report("Cannot show blame for directory %s", opt_path);
3249                         return REQ_NONE;
3250                 }
3252                 string_copy(opt_ref, view->vid);
3253                 string_format(opt_file, "%s%s", opt_path, filename);
3254                 return request;
3255         }
3256         if (request == REQ_TREE_PARENT) {
3257                 if (*opt_path) {
3258                         /* fake 'cd  ..' */
3259                         request = REQ_ENTER;
3260                         line = &view->line[1];
3261                 } else {
3262                         /* quit view if at top of tree */
3263                         return REQ_VIEW_CLOSE;
3264                 }
3265         }
3266         if (request != REQ_ENTER)
3267                 return request;
3269         /* Cleanup the stack if the tree view is at a different tree. */
3270         while (!*opt_path && tree_stack)
3271                 pop_tree_stack_entry();
3273         switch (line->type) {
3274         case LINE_TREE_DIR:
3275                 /* Depending on whether it is a subdir or parent (updir?) link
3276                  * mangle the path buffer. */
3277                 if (line == &view->line[1] && *opt_path) {
3278                         pop_tree_stack_entry();
3280                 } else {
3281                         char *basename = tree_path(line);
3283                         push_tree_stack_entry(basename, view->lineno);
3284                 }
3286                 /* Trees and subtrees share the same ID, so they are not not
3287                  * unique like blobs. */
3288                 flags = OPEN_RELOAD;
3289                 request = REQ_VIEW_TREE;
3290                 break;
3292         case LINE_TREE_FILE:
3293                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3294                 request = REQ_VIEW_BLOB;
3295                 break;
3297         default:
3298                 return TRUE;
3299         }
3301         open_view(view, request, flags);
3302         if (request == REQ_VIEW_TREE) {
3303                 view->lineno = tree_lineno;
3304         }
3306         return REQ_NONE;
3309 static void
3310 tree_select(struct view *view, struct line *line)
3312         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3314         if (line->type == LINE_TREE_FILE) {
3315                 string_copy_rev(ref_blob, text);
3317         } else if (line->type != LINE_TREE_DIR) {
3318                 return;
3319         }
3321         string_copy_rev(view->ref, text);
3324 static struct view_ops tree_ops = {
3325         "file",
3326         NULL,
3327         tree_read,
3328         pager_draw,
3329         tree_request,
3330         pager_grep,
3331         tree_select,
3332 };
3334 static bool
3335 blob_read(struct view *view, char *line)
3337         if (!line)
3338                 return TRUE;
3339         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3342 static struct view_ops blob_ops = {
3343         "line",
3344         NULL,
3345         blob_read,
3346         pager_draw,
3347         pager_request,
3348         pager_grep,
3349         pager_select,
3350 };
3352 /*
3353  * Blame backend
3354  *
3355  * Loading the blame view is a two phase job:
3356  *
3357  *  1. File content is read either using opt_file from the
3358  *     filesystem or using git-cat-file.
3359  *  2. Then blame information is incrementally added by
3360  *     reading output from git-blame.
3361  */
3363 struct blame_commit {
3364         char id[SIZEOF_REV];            /* SHA1 ID. */
3365         char title[128];                /* First line of the commit message. */
3366         char author[75];                /* Author of the commit. */
3367         struct tm time;                 /* Date from the author ident. */
3368         char filename[128];             /* Name of file. */
3369 };
3371 struct blame {
3372         struct blame_commit *commit;
3373         unsigned int header:1;
3374         char text[1];
3375 };
3377 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3378 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3380 static bool
3381 blame_open(struct view *view)
3383         char path[SIZEOF_STR];
3384         char ref[SIZEOF_STR] = "";
3386         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3387                 return FALSE;
3389         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3390                 return FALSE;
3392         if (*opt_ref) {
3393                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3394                         return FALSE;
3395         } else {
3396                 view->pipe = fopen(opt_file, "r");
3397                 if (!view->pipe &&
3398                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3399                         return FALSE;
3400         }
3402         if (!view->pipe)
3403                 view->pipe = popen(view->cmd, "r");
3404         if (!view->pipe)
3405                 return FALSE;
3407         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3408                 return FALSE;
3410         string_format(view->ref, "%s ...", opt_file);
3411         string_copy_rev(view->vid, opt_file);
3412         set_nonblocking_input(TRUE);
3414         if (view->line) {
3415                 int i;
3417                 for (i = 0; i < view->lines; i++)
3418                         free(view->line[i].data);
3419                 free(view->line);
3420         }
3422         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3423         view->offset = view->lines  = view->lineno = 0;
3424         view->line = NULL;
3425         view->start_time = time(NULL);
3427         return TRUE;
3430 static struct blame_commit *
3431 get_blame_commit(struct view *view, const char *id)
3433         size_t i;
3435         for (i = 0; i < view->lines; i++) {
3436                 struct blame *blame = view->line[i].data;
3438                 if (!blame->commit)
3439                         continue;
3441                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3442                         return blame->commit;
3443         }
3445         {
3446                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3448                 if (commit)
3449                         string_ncopy(commit->id, id, SIZEOF_REV);
3450                 return commit;
3451         }
3454 static bool
3455 parse_number(char **posref, size_t *number, size_t min, size_t max)
3457         char *pos = *posref;
3459         *posref = NULL;
3460         pos = strchr(pos + 1, ' ');
3461         if (!pos || !isdigit(pos[1]))
3462                 return FALSE;
3463         *number = atoi(pos + 1);
3464         if (*number < min || *number > max)
3465                 return FALSE;
3467         *posref = pos;
3468         return TRUE;
3471 static struct blame_commit *
3472 parse_blame_commit(struct view *view, char *text, int *blamed)
3474         struct blame_commit *commit;
3475         struct blame *blame;
3476         char *pos = text + SIZEOF_REV - 1;
3477         size_t lineno;
3478         size_t group;
3480         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3481                 return NULL;
3483         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3484             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3485                 return NULL;
3487         commit = get_blame_commit(view, text);
3488         if (!commit)
3489                 return NULL;
3491         *blamed += group;
3492         while (group--) {
3493                 struct line *line = &view->line[lineno + group - 1];
3495                 blame = line->data;
3496                 blame->commit = commit;
3497                 blame->header = !group;
3498                 line->dirty = 1;
3499         }
3501         return commit;
3504 static bool
3505 blame_read_file(struct view *view, char *line)
3507         if (!line) {
3508                 FILE *pipe = NULL;
3510                 if (view->lines > 0)
3511                         pipe = popen(view->cmd, "r");
3512                 else if (!view->parent)
3513                         die("No blame exist for %s", view->vid);
3514                 view->cmd[0] = 0;
3515                 if (!pipe) {
3516                         report("Failed to load blame data");
3517                         return TRUE;
3518                 }
3520                 fclose(view->pipe);
3521                 view->pipe = pipe;
3522                 return FALSE;
3524         } else {
3525                 size_t linelen = strlen(line);
3526                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3528                 if (!line)
3529                         return FALSE;
3531                 blame->commit = NULL;
3532                 strncpy(blame->text, line, linelen);
3533                 blame->text[linelen] = 0;
3534                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3535         }
3538 static bool
3539 match_blame_header(const char *name, char **line)
3541         size_t namelen = strlen(name);
3542         bool matched = !strncmp(name, *line, namelen);
3544         if (matched)
3545                 *line += namelen;
3547         return matched;
3550 static bool
3551 blame_read(struct view *view, char *line)
3553         static struct blame_commit *commit = NULL;
3554         static int blamed = 0;
3555         static time_t author_time;
3557         if (*view->cmd)
3558                 return blame_read_file(view, line);
3560         if (!line) {
3561                 /* Reset all! */
3562                 commit = NULL;
3563                 blamed = 0;
3564                 string_format(view->ref, "%s", view->vid);
3565                 if (view_is_displayed(view)) {
3566                         update_view_title(view);
3567                         redraw_view_from(view, 0);
3568                 }
3569                 return TRUE;
3570         }
3572         if (!commit) {
3573                 commit = parse_blame_commit(view, line, &blamed);
3574                 string_format(view->ref, "%s %2d%%", view->vid,
3575                               blamed * 100 / view->lines);
3577         } else if (match_blame_header("author ", &line)) {
3578                 string_ncopy(commit->author, line, strlen(line));
3580         } else if (match_blame_header("author-time ", &line)) {
3581                 author_time = (time_t) atol(line);
3583         } else if (match_blame_header("author-tz ", &line)) {
3584                 long tz;
3586                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3587                 tz += ('0' - line[2]) * 60 * 60;
3588                 tz += ('0' - line[3]) * 60;
3589                 tz += ('0' - line[4]) * 60;
3591                 if (line[0] == '-')
3592                         tz = -tz;
3594                 author_time -= tz;
3595                 gmtime_r(&author_time, &commit->time);
3597         } else if (match_blame_header("summary ", &line)) {
3598                 string_ncopy(commit->title, line, strlen(line));
3600         } else if (match_blame_header("filename ", &line)) {
3601                 string_ncopy(commit->filename, line, strlen(line));
3602                 commit = NULL;
3603         }
3605         return TRUE;
3608 static bool
3609 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3611         struct blame *blame = line->data;
3612         int col = 0;
3614         if (opt_date) {
3615                 struct tm *time = blame->commit && *blame->commit->filename
3616                                 ? &blame->commit->time : NULL;
3618                 col += draw_date(view, time, view->width, selected);
3619                 if (col >= view->width)
3620                         return TRUE;
3621         }
3623         if (opt_author) {
3624                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3626                 if (!selected)
3627                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3628                 if (blame->commit)
3629                         draw_text(view, blame->commit->author, max, TRUE, selected);
3630                 col += AUTHOR_COLS;
3631                 if (col >= view->width)
3632                         return TRUE;
3633                 wmove(view->win, lineno, col);
3634         }
3636         {
3637                 int max = MIN(ID_COLS - 1, view->width - col);
3639                 if (!selected)
3640                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3641                 if (blame->commit)
3642                         draw_text(view, blame->commit->id, max, FALSE, -1);
3643                 col += ID_COLS;
3644                 if (col >= view->width)
3645                         return TRUE;
3646                 wmove(view->win, lineno, col);
3647         }
3649         col += draw_lineno(view, lineno, view->width - col, selected);
3650         if (col >= view->width)
3651                 return TRUE;
3653         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 >/dev/null 2>/dev/null");
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         if (selected) {
4019                 /* No attributes. */
4021         } else if (line->type == LINE_STAT_HEAD) {
4022                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
4023                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
4025         } else if (!status && line->type != LINE_STAT_NONE) {
4026                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4027                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4029         } else {
4030                 wattrset(view->win, get_line_attr(line->type));
4031         }
4033         if (!status) {
4034                 switch (line->type) {
4035                 case LINE_STAT_STAGED:
4036                         text = "Changes to be committed:";
4037                         break;
4039                 case LINE_STAT_UNSTAGED:
4040                         text = "Changed but not updated:";
4041                         break;
4043                 case LINE_STAT_UNTRACKED:
4044                         text = "Untracked files:";
4045                         break;
4047                 case LINE_STAT_NONE:
4048                         text = "    (no files)";
4049                         break;
4051                 case LINE_STAT_HEAD:
4052                         text = status_onbranch;
4053                         break;
4055                 default:
4056                         return FALSE;
4057                 }
4058         } else {
4059                 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4061                 col += draw_text(view, buf, view->width, TRUE, selected);
4062                 if (!selected)
4063                         wattrset(view->win, A_NORMAL);
4064                 text = status->new.name;
4065         }
4067         draw_text(view, text, view->width - col, TRUE, selected);
4068         return TRUE;
4071 static enum request
4072 status_enter(struct view *view, struct line *line)
4074         struct status *status = line->data;
4075         char oldpath[SIZEOF_STR] = "";
4076         char newpath[SIZEOF_STR] = "";
4077         char *info;
4078         size_t cmdsize = 0;
4079         enum open_flags split;
4081         if (line->type == LINE_STAT_NONE ||
4082             (!status && line[1].type == LINE_STAT_NONE)) {
4083                 report("No file to diff");
4084                 return REQ_NONE;
4085         }
4087         if (status) {
4088                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4089                         return REQ_QUIT;
4090                 /* Diffs for unmerged entries are empty when pasing the
4091                  * new path, so leave it empty. */
4092                 if (status->status != 'U' &&
4093                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4094                         return REQ_QUIT;
4095         }
4097         if (opt_cdup[0] &&
4098             line->type != LINE_STAT_UNTRACKED &&
4099             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4100                 return REQ_QUIT;
4102         switch (line->type) {
4103         case LINE_STAT_STAGED:
4104                 if (opt_no_head) {
4105                         if (!string_format_from(opt_cmd, &cmdsize,
4106                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4107                                                 newpath))
4108                                 return REQ_QUIT;
4109                 } else {
4110                         if (!string_format_from(opt_cmd, &cmdsize,
4111                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4112                                                 oldpath, newpath))
4113                                 return REQ_QUIT;
4114                 }
4116                 if (status)
4117                         info = "Staged changes to %s";
4118                 else
4119                         info = "Staged changes";
4120                 break;
4122         case LINE_STAT_UNSTAGED:
4123                 if (!string_format_from(opt_cmd, &cmdsize,
4124                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4125                         return REQ_QUIT;
4126                 if (status)
4127                         info = "Unstaged changes to %s";
4128                 else
4129                         info = "Unstaged changes";
4130                 break;
4132         case LINE_STAT_UNTRACKED:
4133                 if (opt_pipe)
4134                         return REQ_QUIT;
4136                 if (!status) {
4137                         report("No file to show");
4138                         return REQ_NONE;
4139                 }
4141                 opt_pipe = fopen(status->new.name, "r");
4142                 info = "Untracked file %s";
4143                 break;
4145         case LINE_STAT_HEAD:
4146                 return REQ_NONE;
4148         default:
4149                 die("line type %d not handled in switch", line->type);
4150         }
4152         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4153         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4154         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4155                 if (status) {
4156                         stage_status = *status;
4157                 } else {
4158                         memset(&stage_status, 0, sizeof(stage_status));
4159                 }
4161                 stage_line_type = line->type;
4162                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4163         }
4165         return REQ_NONE;
4168 static bool
4169 status_exists(struct status *status, enum line_type type)
4171         struct view *view = VIEW(REQ_VIEW_STATUS);
4172         struct line *line;
4174         for (line = view->line; line < view->line + view->lines; line++) {
4175                 struct status *pos = line->data;
4177                 if (line->type == type && pos &&
4178                     !strcmp(status->new.name, pos->new.name))
4179                         return TRUE;
4180         }
4182         return FALSE;
4186 static FILE *
4187 status_update_prepare(enum line_type type)
4189         char cmd[SIZEOF_STR];
4190         size_t cmdsize = 0;
4192         if (opt_cdup[0] &&
4193             type != LINE_STAT_UNTRACKED &&
4194             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4195                 return NULL;
4197         switch (type) {
4198         case LINE_STAT_STAGED:
4199                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4200                 break;
4202         case LINE_STAT_UNSTAGED:
4203         case LINE_STAT_UNTRACKED:
4204                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4205                 break;
4207         default:
4208                 die("line type %d not handled in switch", type);
4209         }
4211         return popen(cmd, "w");
4214 static bool
4215 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4217         char buf[SIZEOF_STR];
4218         size_t bufsize = 0;
4219         size_t written = 0;
4221         switch (type) {
4222         case LINE_STAT_STAGED:
4223                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4224                                         status->old.mode,
4225                                         status->old.rev,
4226                                         status->old.name, 0))
4227                         return FALSE;
4228                 break;
4230         case LINE_STAT_UNSTAGED:
4231         case LINE_STAT_UNTRACKED:
4232                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4233                         return FALSE;
4234                 break;
4236         default:
4237                 die("line type %d not handled in switch", type);
4238         }
4240         while (!ferror(pipe) && written < bufsize) {
4241                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4242         }
4244         return written == bufsize;
4247 static bool
4248 status_update_file(struct status *status, enum line_type type)
4250         FILE *pipe = status_update_prepare(type);
4251         bool result;
4253         if (!pipe)
4254                 return FALSE;
4256         result = status_update_write(pipe, status, type);
4257         pclose(pipe);
4258         return result;
4261 static bool
4262 status_update_files(struct view *view, struct line *line)
4264         FILE *pipe = status_update_prepare(line->type);
4265         bool result = TRUE;
4266         struct line *pos = view->line + view->lines;
4267         int files = 0;
4268         int file, done;
4270         if (!pipe)
4271                 return FALSE;
4273         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4274                 files++;
4276         for (file = 0, done = 0; result && file < files; line++, file++) {
4277                 int almost_done = file * 100 / files;
4279                 if (almost_done > done) {
4280                         done = almost_done;
4281                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4282                                       file, files, done);
4283                         update_view_title(view);
4284                 }
4285                 result = status_update_write(pipe, line->data, line->type);
4286         }
4288         pclose(pipe);
4289         return result;
4292 static bool
4293 status_update(struct view *view)
4295         struct line *line = &view->line[view->lineno];
4297         assert(view->lines);
4299         if (!line->data) {
4300                 /* This should work even for the "On branch" line. */
4301                 if (line < view->line + view->lines && !line[1].data) {
4302                         report("Nothing to update");
4303                         return FALSE;
4304                 }
4306                 if (!status_update_files(view, line + 1)) {
4307                         report("Failed to update file status");
4308                         return FALSE;
4309                 }
4311         } else if (!status_update_file(line->data, line->type)) {
4312                 report("Failed to update file status");
4313                 return FALSE;
4314         }
4316         return TRUE;
4319 static enum request
4320 status_request(struct view *view, enum request request, struct line *line)
4322         struct status *status = line->data;
4324         switch (request) {
4325         case REQ_STATUS_UPDATE:
4326                 if (!status_update(view))
4327                         return REQ_NONE;
4328                 break;
4330         case REQ_STATUS_MERGE:
4331                 if (!status || status->status != 'U') {
4332                         report("Merging only possible for files with unmerged status ('U').");
4333                         return REQ_NONE;
4334                 }
4335                 open_mergetool(status->new.name);
4336                 break;
4338         case REQ_EDIT:
4339                 if (!status)
4340                         return request;
4342                 open_editor(status->status != '?', status->new.name);
4343                 break;
4345         case REQ_VIEW_BLAME:
4346                 if (status) {
4347                         string_copy(opt_file, status->new.name);
4348                         opt_ref[0] = 0;
4349                 }
4350                 return request;
4352         case REQ_ENTER:
4353                 /* After returning the status view has been split to
4354                  * show the stage view. No further reloading is
4355                  * necessary. */
4356                 status_enter(view, line);
4357                 return REQ_NONE;
4359         case REQ_REFRESH:
4360                 /* Simply reload the view. */
4361                 break;
4363         default:
4364                 return request;
4365         }
4367         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4369         return REQ_NONE;
4372 static void
4373 status_select(struct view *view, struct line *line)
4375         struct status *status = line->data;
4376         char file[SIZEOF_STR] = "all files";
4377         char *text;
4378         char *key;
4380         if (status && !string_format(file, "'%s'", status->new.name))
4381                 return;
4383         if (!status && line[1].type == LINE_STAT_NONE)
4384                 line++;
4386         switch (line->type) {
4387         case LINE_STAT_STAGED:
4388                 text = "Press %s to unstage %s for commit";
4389                 break;
4391         case LINE_STAT_UNSTAGED:
4392                 text = "Press %s to stage %s for commit";
4393                 break;
4395         case LINE_STAT_UNTRACKED:
4396                 text = "Press %s to stage %s for addition";
4397                 break;
4399         case LINE_STAT_HEAD:
4400         case LINE_STAT_NONE:
4401                 text = "Nothing to update";
4402                 break;
4404         default:
4405                 die("line type %d not handled in switch", line->type);
4406         }
4408         if (status && status->status == 'U') {
4409                 text = "Press %s to resolve conflict in %s";
4410                 key = get_key(REQ_STATUS_MERGE);
4412         } else {
4413                 key = get_key(REQ_STATUS_UPDATE);
4414         }
4416         string_format(view->ref, text, key, file);
4419 static bool
4420 status_grep(struct view *view, struct line *line)
4422         struct status *status = line->data;
4423         enum { S_STATUS, S_NAME, S_END } state;
4424         char buf[2] = "?";
4425         regmatch_t pmatch;
4427         if (!status)
4428                 return FALSE;
4430         for (state = S_STATUS; state < S_END; state++) {
4431                 char *text;
4433                 switch (state) {
4434                 case S_NAME:    text = status->new.name;        break;
4435                 case S_STATUS:
4436                         buf[0] = status->status;
4437                         text = buf;
4438                         break;
4440                 default:
4441                         return FALSE;
4442                 }
4444                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4445                         return TRUE;
4446         }
4448         return FALSE;
4451 static struct view_ops status_ops = {
4452         "file",
4453         status_open,
4454         NULL,
4455         status_draw,
4456         status_request,
4457         status_grep,
4458         status_select,
4459 };
4462 static bool
4463 stage_diff_line(FILE *pipe, struct line *line)
4465         char *buf = line->data;
4466         size_t bufsize = strlen(buf);
4467         size_t written = 0;
4469         while (!ferror(pipe) && written < bufsize) {
4470                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4471         }
4473         fputc('\n', pipe);
4475         return written == bufsize;
4478 static bool
4479 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4481         while (line < end) {
4482                 if (!stage_diff_line(pipe, line++))
4483                         return FALSE;
4484                 if (line->type == LINE_DIFF_CHUNK ||
4485                     line->type == LINE_DIFF_HEADER)
4486                         break;
4487         }
4489         return TRUE;
4492 static struct line *
4493 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4495         for (; view->line < line; line--)
4496                 if (line->type == type)
4497                         return line;
4499         return NULL;
4502 static bool
4503 stage_update_chunk(struct view *view, struct line *chunk)
4505         char cmd[SIZEOF_STR];
4506         size_t cmdsize = 0;
4507         struct line *diff_hdr;
4508         FILE *pipe;
4510         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4511         if (!diff_hdr)
4512                 return FALSE;
4514         if (opt_cdup[0] &&
4515             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4516                 return FALSE;
4518         if (!string_format_from(cmd, &cmdsize,
4519                                 "git apply --whitespace=nowarn --cached %s - && "
4520                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4521                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4522                 return FALSE;
4524         pipe = popen(cmd, "w");
4525         if (!pipe)
4526                 return FALSE;
4528         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4529             !stage_diff_write(pipe, chunk, view->line + view->lines))
4530                 chunk = NULL;
4532         pclose(pipe);
4534         return chunk ? TRUE : FALSE;
4537 static bool
4538 stage_update(struct view *view, struct line *line)
4540         struct line *chunk = NULL;
4542         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4543                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4545         if (chunk) {
4546                 if (!stage_update_chunk(view, chunk)) {
4547                         report("Failed to apply chunk");
4548                         return FALSE;
4549                 }
4551         } else if (!stage_status.status) {
4552                 view = VIEW(REQ_VIEW_STATUS);
4554                 for (line = view->line; line < view->line + view->lines; line++)
4555                         if (line->type == stage_line_type)
4556                                 break;
4558                 if (!status_update_files(view, line + 1)) {
4559                         report("Failed to update files");
4560                         return FALSE;
4561                 }
4563         } else if (!status_update_file(&stage_status, stage_line_type)) {
4564                 report("Failed to update file");
4565                 return FALSE;
4566         }
4568         return TRUE;
4571 static enum request
4572 stage_request(struct view *view, enum request request, struct line *line)
4574         switch (request) {
4575         case REQ_STATUS_UPDATE:
4576                 if (!stage_update(view, line))
4577                         return REQ_NONE;
4578                 break;
4580         case REQ_EDIT:
4581                 if (!stage_status.new.name[0])
4582                         return request;
4584                 open_editor(stage_status.status != '?', stage_status.new.name);
4585                 break;
4587         case REQ_REFRESH:
4588                 /* Reload everything ... */
4589                 break;
4591         case REQ_VIEW_BLAME:
4592                 if (stage_status.new.name[0]) {
4593                         string_copy(opt_file, stage_status.new.name);
4594                         opt_ref[0] = 0;
4595                 }
4596                 return request;
4598         case REQ_ENTER:
4599                 return pager_request(view, request, line);
4601         default:
4602                 return request;
4603         }
4605         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4607         /* Check whether the staged entry still exists, and close the
4608          * stage view if it doesn't. */
4609         if (!status_exists(&stage_status, stage_line_type))
4610                 return REQ_VIEW_CLOSE;
4612         if (stage_line_type == LINE_STAT_UNTRACKED)
4613                 opt_pipe = fopen(stage_status.new.name, "r");
4614         else
4615                 string_copy(opt_cmd, view->cmd);
4616         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4618         return REQ_NONE;
4621 static struct view_ops stage_ops = {
4622         "line",
4623         NULL,
4624         pager_read,
4625         pager_draw,
4626         stage_request,
4627         pager_grep,
4628         pager_select,
4629 };
4632 /*
4633  * Revision graph
4634  */
4636 struct commit {
4637         char id[SIZEOF_REV];            /* SHA1 ID. */
4638         char title[128];                /* First line of the commit message. */
4639         char author[75];                /* Author of the commit. */
4640         struct tm time;                 /* Date from the author ident. */
4641         struct ref **refs;              /* Repository references. */
4642         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4643         size_t graph_size;              /* The width of the graph array. */
4644         bool has_parents;               /* Rewritten --parents seen. */
4645 };
4647 /* Size of rev graph with no  "padding" columns */
4648 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4650 struct rev_graph {
4651         struct rev_graph *prev, *next, *parents;
4652         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4653         size_t size;
4654         struct commit *commit;
4655         size_t pos;
4656         unsigned int boundary:1;
4657 };
4659 /* Parents of the commit being visualized. */
4660 static struct rev_graph graph_parents[4];
4662 /* The current stack of revisions on the graph. */
4663 static struct rev_graph graph_stacks[4] = {
4664         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4665         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4666         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4667         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4668 };
4670 static inline bool
4671 graph_parent_is_merge(struct rev_graph *graph)
4673         return graph->parents->size > 1;
4676 static inline void
4677 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4679         struct commit *commit = graph->commit;
4681         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4682                 commit->graph[commit->graph_size++] = symbol;
4685 static void
4686 done_rev_graph(struct rev_graph *graph)
4688         if (graph_parent_is_merge(graph) &&
4689             graph->pos < graph->size - 1 &&
4690             graph->next->size == graph->size + graph->parents->size - 1) {
4691                 size_t i = graph->pos + graph->parents->size - 1;
4693                 graph->commit->graph_size = i * 2;
4694                 while (i < graph->next->size - 1) {
4695                         append_to_rev_graph(graph, ' ');
4696                         append_to_rev_graph(graph, '\\');
4697                         i++;
4698                 }
4699         }
4701         graph->size = graph->pos = 0;
4702         graph->commit = NULL;
4703         memset(graph->parents, 0, sizeof(*graph->parents));
4706 static void
4707 push_rev_graph(struct rev_graph *graph, char *parent)
4709         int i;
4711         /* "Collapse" duplicate parents lines.
4712          *
4713          * FIXME: This needs to also update update the drawn graph but
4714          * for now it just serves as a method for pruning graph lines. */
4715         for (i = 0; i < graph->size; i++)
4716                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4717                         return;
4719         if (graph->size < SIZEOF_REVITEMS) {
4720                 string_copy_rev(graph->rev[graph->size++], parent);
4721         }
4724 static chtype
4725 get_rev_graph_symbol(struct rev_graph *graph)
4727         chtype symbol;
4729         if (graph->boundary)
4730                 symbol = REVGRAPH_BOUND;
4731         else if (graph->parents->size == 0)
4732                 symbol = REVGRAPH_INIT;
4733         else if (graph_parent_is_merge(graph))
4734                 symbol = REVGRAPH_MERGE;
4735         else if (graph->pos >= graph->size)
4736                 symbol = REVGRAPH_BRANCH;
4737         else
4738                 symbol = REVGRAPH_COMMIT;
4740         return symbol;
4743 static void
4744 draw_rev_graph(struct rev_graph *graph)
4746         struct rev_filler {
4747                 chtype separator, line;
4748         };
4749         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4750         static struct rev_filler fillers[] = {
4751                 { ' ',  REVGRAPH_LINE },
4752                 { '`',  '.' },
4753                 { '\'', ' ' },
4754                 { '/',  ' ' },
4755         };
4756         chtype symbol = get_rev_graph_symbol(graph);
4757         struct rev_filler *filler;
4758         size_t i;
4760         filler = &fillers[DEFAULT];
4762         for (i = 0; i < graph->pos; i++) {
4763                 append_to_rev_graph(graph, filler->line);
4764                 if (graph_parent_is_merge(graph->prev) &&
4765                     graph->prev->pos == i)
4766                         filler = &fillers[RSHARP];
4768                 append_to_rev_graph(graph, filler->separator);
4769         }
4771         /* Place the symbol for this revision. */
4772         append_to_rev_graph(graph, symbol);
4774         if (graph->prev->size > graph->size)
4775                 filler = &fillers[RDIAG];
4776         else
4777                 filler = &fillers[DEFAULT];
4779         i++;
4781         for (; i < graph->size; i++) {
4782                 append_to_rev_graph(graph, filler->separator);
4783                 append_to_rev_graph(graph, filler->line);
4784                 if (graph_parent_is_merge(graph->prev) &&
4785                     i < graph->prev->pos + graph->parents->size)
4786                         filler = &fillers[RSHARP];
4787                 if (graph->prev->size > graph->size)
4788                         filler = &fillers[LDIAG];
4789         }
4791         if (graph->prev->size > graph->size) {
4792                 append_to_rev_graph(graph, filler->separator);
4793                 if (filler->line != ' ')
4794                         append_to_rev_graph(graph, filler->line);
4795         }
4798 /* Prepare the next rev graph */
4799 static void
4800 prepare_rev_graph(struct rev_graph *graph)
4802         size_t i;
4804         /* First, traverse all lines of revisions up to the active one. */
4805         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4806                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4807                         break;
4809                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4810         }
4812         /* Interleave the new revision parent(s). */
4813         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4814                 push_rev_graph(graph->next, graph->parents->rev[i]);
4816         /* Lastly, put any remaining revisions. */
4817         for (i = graph->pos + 1; i < graph->size; i++)
4818                 push_rev_graph(graph->next, graph->rev[i]);
4821 static void
4822 update_rev_graph(struct rev_graph *graph)
4824         /* If this is the finalizing update ... */
4825         if (graph->commit)
4826                 prepare_rev_graph(graph);
4828         /* Graph visualization needs a one rev look-ahead,
4829          * so the first update doesn't visualize anything. */
4830         if (!graph->prev->commit)
4831                 return;
4833         draw_rev_graph(graph->prev);
4834         done_rev_graph(graph->prev->prev);
4838 /*
4839  * Main view backend
4840  */
4842 static bool
4843 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4845         struct commit *commit = line->data;
4846         enum line_type type;
4847         int col = 0;
4849         if (!*commit->author)
4850                 return FALSE;
4852         if (selected) {
4853                 type = LINE_CURSOR;
4854         } else {
4855                 type = LINE_MAIN_COMMIT;
4856         }
4858         if (opt_date) {
4859                 col += draw_date(view, &commit->time, view->width, selected);
4860                 if (col >= view->width)
4861                         return TRUE;
4862         }
4863         if (type != LINE_CURSOR)
4864                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4866         if (opt_author) {
4867                 int max_len;
4869                 max_len = view->width - col;
4870                 if (max_len > AUTHOR_COLS - 1)
4871                         max_len = AUTHOR_COLS - 1;
4872                 draw_text(view, commit->author, max_len, TRUE, selected);
4873                 col += AUTHOR_COLS;
4874                 if (col >= view->width)
4875                         return TRUE;
4876         }
4878         if (opt_rev_graph && commit->graph_size) {
4879                 size_t graph_size = view->width - col;
4880                 size_t i;
4882                 if (type != LINE_CURSOR)
4883                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4884                 wmove(view->win, lineno, col);
4885                 if (graph_size > commit->graph_size)
4886                         graph_size = commit->graph_size;
4887                 /* Using waddch() instead of waddnstr() ensures that
4888                  * they'll be rendered correctly for the cursor line. */
4889                 for (i = 0; i < graph_size; i++)
4890                         waddch(view->win, commit->graph[i]);
4892                 col += commit->graph_size + 1;
4893                 if (col >= view->width)
4894                         return TRUE;
4895                 waddch(view->win, ' ');
4896         }
4897         if (type != LINE_CURSOR)
4898                 wattrset(view->win, A_NORMAL);
4900         wmove(view->win, lineno, col);
4902         if (opt_show_refs && commit->refs) {
4903                 size_t i = 0;
4905                 do {
4906                         if (type == LINE_CURSOR)
4907                                 ;
4908                         else if (commit->refs[i]->head)
4909                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4910                         else if (commit->refs[i]->ltag)
4911                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4912                         else if (commit->refs[i]->tag)
4913                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4914                         else if (commit->refs[i]->tracked)
4915                                 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
4916                         else if (commit->refs[i]->remote)
4917                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4918                         else
4919                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4921                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4922                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4923                                          TRUE, selected);
4924                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4925                         if (type != LINE_CURSOR)
4926                                 wattrset(view->win, A_NORMAL);
4927                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4928                         if (col >= view->width)
4929                                 return TRUE;
4930                 } while (commit->refs[i++]->next);
4931         }
4933         if (type != LINE_CURSOR)
4934                 wattrset(view->win, get_line_attr(type));
4936         draw_text(view, commit->title, view->width - col, TRUE, selected);
4937         return TRUE;
4940 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4941 static bool
4942 main_read(struct view *view, char *line)
4944         static struct rev_graph *graph = graph_stacks;
4945         enum line_type type;
4946         struct commit *commit;
4948         if (!line) {
4949                 if (!view->lines && !view->parent)
4950                         die("No revisions match the given arguments.");
4951                 update_rev_graph(graph);
4952                 return TRUE;
4953         }
4955         type = get_line_type(line);
4956         if (type == LINE_COMMIT) {
4957                 commit = calloc(1, sizeof(struct commit));
4958                 if (!commit)
4959                         return FALSE;
4961                 line += STRING_SIZE("commit ");
4962                 if (*line == '-') {
4963                         graph->boundary = 1;
4964                         line++;
4965                 }
4967                 string_copy_rev(commit->id, line);
4968                 commit->refs = get_refs(commit->id);
4969                 graph->commit = commit;
4970                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4972                 while ((line = strchr(line, ' '))) {
4973                         line++;
4974                         push_rev_graph(graph->parents, line);
4975                         commit->has_parents = TRUE;
4976                 }
4977                 return TRUE;
4978         }
4980         if (!view->lines)
4981                 return TRUE;
4982         commit = view->line[view->lines - 1].data;
4984         switch (type) {
4985         case LINE_PARENT:
4986                 if (commit->has_parents)
4987                         break;
4988                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4989                 break;
4991         case LINE_AUTHOR:
4992         {
4993                 /* Parse author lines where the name may be empty:
4994                  *      author  <email@address.tld> 1138474660 +0100
4995                  */
4996                 char *ident = line + STRING_SIZE("author ");
4997                 char *nameend = strchr(ident, '<');
4998                 char *emailend = strchr(ident, '>');
5000                 if (!nameend || !emailend)
5001                         break;
5003                 update_rev_graph(graph);
5004                 graph = graph->next;
5006                 *nameend = *emailend = 0;
5007                 ident = chomp_string(ident);
5008                 if (!*ident) {
5009                         ident = chomp_string(nameend + 1);
5010                         if (!*ident)
5011                                 ident = "Unknown";
5012                 }
5014                 string_ncopy(commit->author, ident, strlen(ident));
5016                 /* Parse epoch and timezone */
5017                 if (emailend[1] == ' ') {
5018                         char *secs = emailend + 2;
5019                         char *zone = strchr(secs, ' ');
5020                         time_t time = (time_t) atol(secs);
5022                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5023                                 long tz;
5025                                 zone++;
5026                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5027                                 tz += ('0' - zone[2]) * 60 * 60;
5028                                 tz += ('0' - zone[3]) * 60;
5029                                 tz += ('0' - zone[4]) * 60;
5031                                 if (zone[0] == '-')
5032                                         tz = -tz;
5034                                 time -= tz;
5035                         }
5037                         gmtime_r(&time, &commit->time);
5038                 }
5039                 break;
5040         }
5041         default:
5042                 /* Fill in the commit title if it has not already been set. */
5043                 if (commit->title[0])
5044                         break;
5046                 /* Require titles to start with a non-space character at the
5047                  * offset used by git log. */
5048                 if (strncmp(line, "    ", 4))
5049                         break;
5050                 line += 4;
5051                 /* Well, if the title starts with a whitespace character,
5052                  * try to be forgiving.  Otherwise we end up with no title. */
5053                 while (isspace(*line))
5054                         line++;
5055                 if (*line == '\0')
5056                         break;
5057                 /* FIXME: More graceful handling of titles; append "..." to
5058                  * shortened titles, etc. */
5060                 string_ncopy(commit->title, line, strlen(line));
5061         }
5063         return TRUE;
5066 static enum request
5067 main_request(struct view *view, enum request request, struct line *line)
5069         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5071         if (request == REQ_ENTER)
5072                 open_view(view, REQ_VIEW_DIFF, flags);
5073         else
5074                 return request;
5076         return REQ_NONE;
5079 static bool
5080 main_grep(struct view *view, struct line *line)
5082         struct commit *commit = line->data;
5083         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5084         char buf[DATE_COLS + 1];
5085         regmatch_t pmatch;
5087         for (state = S_TITLE; state < S_END; state++) {
5088                 char *text;
5090                 switch (state) {
5091                 case S_TITLE:   text = commit->title;   break;
5092                 case S_AUTHOR:  text = commit->author;  break;
5093                 case S_DATE:
5094                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5095                                 continue;
5096                         text = buf;
5097                         break;
5099                 default:
5100                         return FALSE;
5101                 }
5103                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5104                         return TRUE;
5105         }
5107         return FALSE;
5110 static void
5111 main_select(struct view *view, struct line *line)
5113         struct commit *commit = line->data;
5115         string_copy_rev(view->ref, commit->id);
5116         string_copy_rev(ref_commit, view->ref);
5119 static struct view_ops main_ops = {
5120         "commit",
5121         NULL,
5122         main_read,
5123         main_draw,
5124         main_request,
5125         main_grep,
5126         main_select,
5127 };
5130 /*
5131  * Unicode / UTF-8 handling
5132  *
5133  * NOTE: Much of the following code for dealing with unicode is derived from
5134  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5135  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5136  */
5138 /* I've (over)annotated a lot of code snippets because I am not entirely
5139  * confident that the approach taken by this small UTF-8 interface is correct.
5140  * --jonas */
5142 static inline int
5143 unicode_width(unsigned long c)
5145         if (c >= 0x1100 &&
5146            (c <= 0x115f                         /* Hangul Jamo */
5147             || c == 0x2329
5148             || c == 0x232a
5149             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5150                                                 /* CJK ... Yi */
5151             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5152             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5153             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5154             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5155             || (c >= 0xffe0  && c <= 0xffe6)
5156             || (c >= 0x20000 && c <= 0x2fffd)
5157             || (c >= 0x30000 && c <= 0x3fffd)))
5158                 return 2;
5160         if (c == '\t')
5161                 return opt_tab_size;
5163         return 1;
5166 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5167  * Illegal bytes are set one. */
5168 static const unsigned char utf8_bytes[256] = {
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         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,
5175         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,
5176         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,
5177 };
5179 /* Decode UTF-8 multi-byte representation into a unicode character. */
5180 static inline unsigned long
5181 utf8_to_unicode(const char *string, size_t length)
5183         unsigned long unicode;
5185         switch (length) {
5186         case 1:
5187                 unicode  =   string[0];
5188                 break;
5189         case 2:
5190                 unicode  =  (string[0] & 0x1f) << 6;
5191                 unicode +=  (string[1] & 0x3f);
5192                 break;
5193         case 3:
5194                 unicode  =  (string[0] & 0x0f) << 12;
5195                 unicode += ((string[1] & 0x3f) << 6);
5196                 unicode +=  (string[2] & 0x3f);
5197                 break;
5198         case 4:
5199                 unicode  =  (string[0] & 0x0f) << 18;
5200                 unicode += ((string[1] & 0x3f) << 12);
5201                 unicode += ((string[2] & 0x3f) << 6);
5202                 unicode +=  (string[3] & 0x3f);
5203                 break;
5204         case 5:
5205                 unicode  =  (string[0] & 0x0f) << 24;
5206                 unicode += ((string[1] & 0x3f) << 18);
5207                 unicode += ((string[2] & 0x3f) << 12);
5208                 unicode += ((string[3] & 0x3f) << 6);
5209                 unicode +=  (string[4] & 0x3f);
5210                 break;
5211         case 6:
5212                 unicode  =  (string[0] & 0x01) << 30;
5213                 unicode += ((string[1] & 0x3f) << 24);
5214                 unicode += ((string[2] & 0x3f) << 18);
5215                 unicode += ((string[3] & 0x3f) << 12);
5216                 unicode += ((string[4] & 0x3f) << 6);
5217                 unicode +=  (string[5] & 0x3f);
5218                 break;
5219         default:
5220                 die("Invalid unicode length");
5221         }
5223         /* Invalid characters could return the special 0xfffd value but NUL
5224          * should be just as good. */
5225         return unicode > 0xffff ? 0 : unicode;
5228 /* Calculates how much of string can be shown within the given maximum width
5229  * and sets trimmed parameter to non-zero value if all of string could not be
5230  * shown. If the reserve flag is TRUE, it will reserve at least one
5231  * trailing character, which can be useful when drawing a delimiter.
5232  *
5233  * Returns the number of bytes to output from string to satisfy max_width. */
5234 static size_t
5235 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5237         const char *start = string;
5238         const char *end = strchr(string, '\0');
5239         unsigned char last_bytes = 0;
5240         size_t width = 0;
5242         *trimmed = 0;
5244         while (string < end) {
5245                 int c = *(unsigned char *) string;
5246                 unsigned char bytes = utf8_bytes[c];
5247                 size_t ucwidth;
5248                 unsigned long unicode;
5250                 if (string + bytes > end)
5251                         break;
5253                 /* Change representation to figure out whether
5254                  * it is a single- or double-width character. */
5256                 unicode = utf8_to_unicode(string, bytes);
5257                 /* FIXME: Graceful handling of invalid unicode character. */
5258                 if (!unicode)
5259                         break;
5261                 ucwidth = unicode_width(unicode);
5262                 width  += ucwidth;
5263                 if (width > max_width) {
5264                         *trimmed = 1;
5265                         if (reserve && width - ucwidth == max_width) {
5266                                 string -= last_bytes;
5267                         }
5268                         break;
5269                 }
5271                 string  += bytes;
5272                 last_bytes = bytes;
5273         }
5275         return string - start;
5279 /*
5280  * Status management
5281  */
5283 /* Whether or not the curses interface has been initialized. */
5284 static bool cursed = FALSE;
5286 /* The status window is used for polling keystrokes. */
5287 static WINDOW *status_win;
5289 static bool status_empty = TRUE;
5291 /* Update status and title window. */
5292 static void
5293 report(const char *msg, ...)
5295         struct view *view = display[current_view];
5297         if (input_mode)
5298                 return;
5300         if (!view) {
5301                 char buf[SIZEOF_STR];
5302                 va_list args;
5304                 va_start(args, msg);
5305                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5306                         buf[sizeof(buf) - 1] = 0;
5307                         buf[sizeof(buf) - 2] = '.';
5308                         buf[sizeof(buf) - 3] = '.';
5309                         buf[sizeof(buf) - 4] = '.';
5310                 }
5311                 va_end(args);
5312                 die("%s", buf);
5313         }
5315         if (!status_empty || *msg) {
5316                 va_list args;
5318                 va_start(args, msg);
5320                 wmove(status_win, 0, 0);
5321                 if (*msg) {
5322                         vwprintw(status_win, msg, args);
5323                         status_empty = FALSE;
5324                 } else {
5325                         status_empty = TRUE;
5326                 }
5327                 wclrtoeol(status_win);
5328                 wrefresh(status_win);
5330                 va_end(args);
5331         }
5333         update_view_title(view);
5334         update_display_cursor(view);
5337 /* Controls when nodelay should be in effect when polling user input. */
5338 static void
5339 set_nonblocking_input(bool loading)
5341         static unsigned int loading_views;
5343         if ((loading == FALSE && loading_views-- == 1) ||
5344             (loading == TRUE  && loading_views++ == 0))
5345                 nodelay(status_win, loading);
5348 static void
5349 init_display(void)
5351         int x, y;
5353         /* Initialize the curses library */
5354         if (isatty(STDIN_FILENO)) {
5355                 cursed = !!initscr();
5356         } else {
5357                 /* Leave stdin and stdout alone when acting as a pager. */
5358                 FILE *io = fopen("/dev/tty", "r+");
5360                 if (!io)
5361                         die("Failed to open /dev/tty");
5362                 cursed = !!newterm(NULL, io, io);
5363         }
5365         if (!cursed)
5366                 die("Failed to initialize curses");
5368         nonl();         /* Tell curses not to do NL->CR/NL on output */
5369         cbreak();       /* Take input chars one at a time, no wait for \n */
5370         noecho();       /* Don't echo input */
5371         leaveok(stdscr, TRUE);
5373         if (has_colors())
5374                 init_colors();
5376         getmaxyx(stdscr, y, x);
5377         status_win = newwin(1, 0, y - 1, 0);
5378         if (!status_win)
5379                 die("Failed to create status window");
5381         /* Enable keyboard mapping */
5382         keypad(status_win, TRUE);
5383         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5386 static char *
5387 read_prompt(const char *prompt)
5389         enum { READING, STOP, CANCEL } status = READING;
5390         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5391         int pos = 0;
5393         while (status == READING) {
5394                 struct view *view;
5395                 int i, key;
5397                 input_mode = TRUE;
5399                 foreach_view (view, i)
5400                         update_view(view);
5402                 input_mode = FALSE;
5404                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5405                 wclrtoeol(status_win);
5407                 /* Refresh, accept single keystroke of input */
5408                 key = wgetch(status_win);
5409                 switch (key) {
5410                 case KEY_RETURN:
5411                 case KEY_ENTER:
5412                 case '\n':
5413                         status = pos ? STOP : CANCEL;
5414                         break;
5416                 case KEY_BACKSPACE:
5417                         if (pos > 0)
5418                                 pos--;
5419                         else
5420                                 status = CANCEL;
5421                         break;
5423                 case KEY_ESC:
5424                         status = CANCEL;
5425                         break;
5427                 case ERR:
5428                         break;
5430                 default:
5431                         if (pos >= sizeof(buf)) {
5432                                 report("Input string too long");
5433                                 return NULL;
5434                         }
5436                         if (isprint(key))
5437                                 buf[pos++] = (char) key;
5438                 }
5439         }
5441         /* Clear the status window */
5442         status_empty = FALSE;
5443         report("");
5445         if (status == CANCEL)
5446                 return NULL;
5448         buf[pos++] = 0;
5450         return buf;
5453 /*
5454  * Repository references
5455  */
5457 static struct ref *refs = NULL;
5458 static size_t refs_alloc = 0;
5459 static size_t refs_size = 0;
5461 /* Id <-> ref store */
5462 static struct ref ***id_refs = NULL;
5463 static size_t id_refs_alloc = 0;
5464 static size_t id_refs_size = 0;
5466 static struct ref **
5467 get_refs(char *id)
5469         struct ref ***tmp_id_refs;
5470         struct ref **ref_list = NULL;
5471         size_t ref_list_alloc = 0;
5472         size_t ref_list_size = 0;
5473         size_t i;
5475         for (i = 0; i < id_refs_size; i++)
5476                 if (!strcmp(id, id_refs[i][0]->id))
5477                         return id_refs[i];
5479         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5480                                     sizeof(*id_refs));
5481         if (!tmp_id_refs)
5482                 return NULL;
5484         id_refs = tmp_id_refs;
5486         for (i = 0; i < refs_size; i++) {
5487                 struct ref **tmp;
5489                 if (strcmp(id, refs[i].id))
5490                         continue;
5492                 tmp = realloc_items(ref_list, &ref_list_alloc,
5493                                     ref_list_size + 1, sizeof(*ref_list));
5494                 if (!tmp) {
5495                         if (ref_list)
5496                                 free(ref_list);
5497                         return NULL;
5498                 }
5500                 ref_list = tmp;
5501                 if (ref_list_size > 0)
5502                         ref_list[ref_list_size - 1]->next = 1;
5503                 ref_list[ref_list_size] = &refs[i];
5505                 /* XXX: The properties of the commit chains ensures that we can
5506                  * safely modify the shared ref. The repo references will
5507                  * always be similar for the same id. */
5508                 ref_list[ref_list_size]->next = 0;
5509                 ref_list_size++;
5510         }
5512         if (ref_list)
5513                 id_refs[id_refs_size++] = ref_list;
5515         return ref_list;
5518 static int
5519 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5521         struct ref *ref;
5522         bool tag = FALSE;
5523         bool ltag = FALSE;
5524         bool remote = FALSE;
5525         bool tracked = FALSE;
5526         bool check_replace = FALSE;
5527         bool head = FALSE;
5529         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5530                 if (!strcmp(name + namelen - 3, "^{}")) {
5531                         namelen -= 3;
5532                         name[namelen] = 0;
5533                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5534                                 check_replace = TRUE;
5535                 } else {
5536                         ltag = TRUE;
5537                 }
5539                 tag = TRUE;
5540                 namelen -= STRING_SIZE("refs/tags/");
5541                 name    += STRING_SIZE("refs/tags/");
5543         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5544                 remote = TRUE;
5545                 namelen -= STRING_SIZE("refs/remotes/");
5546                 name    += STRING_SIZE("refs/remotes/");
5547                 tracked  = !strcmp(opt_remote, name);
5549         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5550                 namelen -= STRING_SIZE("refs/heads/");
5551                 name    += STRING_SIZE("refs/heads/");
5552                 head     = !strncmp(opt_head, name, namelen);
5554         } else if (!strcmp(name, "HEAD")) {
5555                 opt_no_head = FALSE;
5556                 return OK;
5557         }
5559         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5560                 /* it's an annotated tag, replace the previous sha1 with the
5561                  * resolved commit id; relies on the fact git-ls-remote lists
5562                  * the commit id of an annotated tag right beofre the commit id
5563                  * it points to. */
5564                 refs[refs_size - 1].ltag = ltag;
5565                 string_copy_rev(refs[refs_size - 1].id, id);
5567                 return OK;
5568         }
5569         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5570         if (!refs)
5571                 return ERR;
5573         ref = &refs[refs_size++];
5574         ref->name = malloc(namelen + 1);
5575         if (!ref->name)
5576                 return ERR;
5578         strncpy(ref->name, name, namelen);
5579         ref->name[namelen] = 0;
5580         ref->head = head;
5581         ref->tag = tag;
5582         ref->ltag = ltag;
5583         ref->remote = remote;
5584         ref->tracked = tracked;
5585         string_copy_rev(ref->id, id);
5587         return OK;
5590 static int
5591 load_refs(void)
5593         const char *cmd_env = getenv("TIG_LS_REMOTE");
5594         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5596         return read_properties(popen(cmd, "r"), "\t", read_ref);
5599 static int
5600 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5602         if (!strcmp(name, "i18n.commitencoding"))
5603                 string_ncopy(opt_encoding, value, valuelen);
5605         if (!strcmp(name, "core.editor"))
5606                 string_ncopy(opt_editor, value, valuelen);
5608         /* branch.<head>.remote */
5609         if (*opt_head &&
5610             !strncmp(name, "branch.", 7) &&
5611             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5612             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5613                 string_ncopy(opt_remote, value, valuelen);
5615         if (*opt_head && *opt_remote &&
5616             !strncmp(name, "branch.", 7) &&
5617             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5618             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5619                 size_t from = strlen(opt_remote);
5621                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5622                         value += STRING_SIZE("refs/heads/");
5623                         valuelen -= STRING_SIZE("refs/heads/");
5624                 }
5626                 if (!string_format_from(opt_remote, &from, "/%s", value))
5627                         opt_remote[0] = 0;
5628         }
5630         return OK;
5633 static int
5634 load_git_config(void)
5636         return read_properties(popen(GIT_CONFIG " --list", "r"),
5637                                "=", read_repo_config_option);
5640 static int
5641 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5643         if (!opt_git_dir[0]) {
5644                 string_ncopy(opt_git_dir, name, namelen);
5646         } else if (opt_is_inside_work_tree == -1) {
5647                 /* This can be 3 different values depending on the
5648                  * version of git being used. If git-rev-parse does not
5649                  * understand --is-inside-work-tree it will simply echo
5650                  * the option else either "true" or "false" is printed.
5651                  * Default to true for the unknown case. */
5652                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5654         } else if (opt_cdup[0] == ' ') {
5655                 string_ncopy(opt_cdup, name, namelen);
5656         } else {
5657                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5658                         namelen -= STRING_SIZE("refs/heads/");
5659                         name    += STRING_SIZE("refs/heads/");
5660                         string_ncopy(opt_head, name, namelen);
5661                 }
5662         }
5664         return OK;
5667 static int
5668 load_repo_info(void)
5670         int result;
5671         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5672                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5674         /* XXX: The line outputted by "--show-cdup" can be empty so
5675          * initialize it to something invalid to make it possible to
5676          * detect whether it has been set or not. */
5677         opt_cdup[0] = ' ';
5679         result = read_properties(pipe, "=", read_repo_info);
5680         if (opt_cdup[0] == ' ')
5681                 opt_cdup[0] = 0;
5683         return result;
5686 static int
5687 read_properties(FILE *pipe, const char *separators,
5688                 int (*read_property)(char *, size_t, char *, size_t))
5690         char buffer[BUFSIZ];
5691         char *name;
5692         int state = OK;
5694         if (!pipe)
5695                 return ERR;
5697         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5698                 char *value;
5699                 size_t namelen;
5700                 size_t valuelen;
5702                 name = chomp_string(name);
5703                 namelen = strcspn(name, separators);
5705                 if (name[namelen]) {
5706                         name[namelen] = 0;
5707                         value = chomp_string(name + namelen + 1);
5708                         valuelen = strlen(value);
5710                 } else {
5711                         value = "";
5712                         valuelen = 0;
5713                 }
5715                 state = read_property(name, namelen, value, valuelen);
5716         }
5718         if (state != ERR && ferror(pipe))
5719                 state = ERR;
5721         pclose(pipe);
5723         return state;
5727 /*
5728  * Main
5729  */
5731 static void __NORETURN
5732 quit(int sig)
5734         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5735         if (cursed)
5736                 endwin();
5737         exit(0);
5740 static void __NORETURN
5741 die(const char *err, ...)
5743         va_list args;
5745         endwin();
5747         va_start(args, err);
5748         fputs("tig: ", stderr);
5749         vfprintf(stderr, err, args);
5750         fputs("\n", stderr);
5751         va_end(args);
5753         exit(1);
5756 static void
5757 warn(const char *msg, ...)
5759         va_list args;
5761         va_start(args, msg);
5762         fputs("tig warning: ", stderr);
5763         vfprintf(stderr, msg, args);
5764         fputs("\n", stderr);
5765         va_end(args);
5768 int
5769 main(int argc, char *argv[])
5771         struct view *view;
5772         enum request request;
5773         size_t i;
5775         signal(SIGINT, quit);
5777         if (setlocale(LC_ALL, "")) {
5778                 char *codeset = nl_langinfo(CODESET);
5780                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5781         }
5783         if (load_repo_info() == ERR)
5784                 die("Failed to load repo info.");
5786         if (load_options() == ERR)
5787                 die("Failed to load user config.");
5789         if (load_git_config() == ERR)
5790                 die("Failed to load repo config.");
5792         if (!parse_options(argc, argv))
5793                 return 0;
5795         /* Require a git repository unless when running in pager mode. */
5796         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5797                 die("Not a git repository");
5799         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5800                 opt_utf8 = FALSE;
5802         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5803                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5804                 if (opt_iconv == ICONV_NONE)
5805                         die("Failed to initialize character set conversion");
5806         }
5808         if (*opt_git_dir && load_refs() == ERR)
5809                 die("Failed to load refs.");
5811         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5812                 view->cmd_env = getenv(view->cmd_env);
5814         request = opt_request;
5816         init_display();
5818         while (view_driver(display[current_view], request)) {
5819                 int key;
5820                 int i;
5822                 foreach_view (view, i)
5823                         update_view(view);
5825                 /* Refresh, accept single keystroke of input */
5826                 key = wgetch(status_win);
5828                 /* wgetch() with nodelay() enabled returns ERR when there's no
5829                  * input. */
5830                 if (key == ERR) {
5831                         request = REQ_NONE;
5832                         continue;
5833                 }
5835                 request = get_keybinding(display[current_view]->keymap, key);
5837                 /* Some low-level request handling. This keeps access to
5838                  * status_win restricted. */
5839                 switch (request) {
5840                 case REQ_PROMPT:
5841                 {
5842                         char *cmd = read_prompt(":");
5844                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5845                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5846                                         opt_request = REQ_VIEW_DIFF;
5847                                 } else {
5848                                         opt_request = REQ_VIEW_PAGER;
5849                                 }
5850                                 break;
5851                         }
5853                         request = REQ_NONE;
5854                         break;
5855                 }
5856                 case REQ_SEARCH:
5857                 case REQ_SEARCH_BACK:
5858                 {
5859                         const char *prompt = request == REQ_SEARCH
5860                                            ? "/" : "?";
5861                         char *search = read_prompt(prompt);
5863                         if (search)
5864                                 string_ncopy(opt_search, search, strlen(search));
5865                         else
5866                                 request = REQ_NONE;
5867                         break;
5868                 }
5869                 case REQ_SCREEN_RESIZE:
5870                 {
5871                         int height, width;
5873                         getmaxyx(stdscr, height, width);
5875                         /* Resize the status view and let the view driver take
5876                          * care of resizing the displayed views. */
5877                         wresize(status_win, 1, width);
5878                         mvwin(status_win, height - 1, 0);
5879                         wrefresh(status_win);
5880                         break;
5881                 }
5882                 default:
5883                         break;
5884                 }
5885         }
5887         quit(0);
5889         return 0;