Code

Remove unused blame line attributes
[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         { 'M',          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 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");
4309         } else if (!status_update_file(line->data, line->type)) {
4310                 report("Failed to update file status");
4311         }
4313         return TRUE;
4316 static enum request
4317 status_request(struct view *view, enum request request, struct line *line)
4319         struct status *status = line->data;
4321         switch (request) {
4322         case REQ_STATUS_UPDATE:
4323                 if (!status_update(view))
4324                         return REQ_NONE;
4325                 break;
4327         case REQ_STATUS_MERGE:
4328                 if (!status || status->status != 'U') {
4329                         report("Merging only possible for files with unmerged status ('U').");
4330                         return REQ_NONE;
4331                 }
4332                 open_mergetool(status->new.name);
4333                 break;
4335         case REQ_EDIT:
4336                 if (!status)
4337                         return request;
4339                 open_editor(status->status != '?', status->new.name);
4340                 break;
4342         case REQ_VIEW_BLAME:
4343                 if (status) {
4344                         string_copy(opt_file, status->new.name);
4345                         opt_ref[0] = 0;
4346                 }
4347                 return request;
4349         case REQ_ENTER:
4350                 /* After returning the status view has been split to
4351                  * show the stage view. No further reloading is
4352                  * necessary. */
4353                 status_enter(view, line);
4354                 return REQ_NONE;
4356         case REQ_REFRESH:
4357                 /* Simply reload the view. */
4358                 break;
4360         default:
4361                 return request;
4362         }
4364         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4366         return REQ_NONE;
4369 static void
4370 status_select(struct view *view, struct line *line)
4372         struct status *status = line->data;
4373         char file[SIZEOF_STR] = "all files";
4374         char *text;
4375         char *key;
4377         if (status && !string_format(file, "'%s'", status->new.name))
4378                 return;
4380         if (!status && line[1].type == LINE_STAT_NONE)
4381                 line++;
4383         switch (line->type) {
4384         case LINE_STAT_STAGED:
4385                 text = "Press %s to unstage %s for commit";
4386                 break;
4388         case LINE_STAT_UNSTAGED:
4389                 text = "Press %s to stage %s for commit";
4390                 break;
4392         case LINE_STAT_UNTRACKED:
4393                 text = "Press %s to stage %s for addition";
4394                 break;
4396         case LINE_STAT_HEAD:
4397         case LINE_STAT_NONE:
4398                 text = "Nothing to update";
4399                 break;
4401         default:
4402                 die("line type %d not handled in switch", line->type);
4403         }
4405         if (status && status->status == 'U') {
4406                 text = "Press %s to resolve conflict in %s";
4407                 key = get_key(REQ_STATUS_MERGE);
4409         } else {
4410                 key = get_key(REQ_STATUS_UPDATE);
4411         }
4413         string_format(view->ref, text, key, file);
4416 static bool
4417 status_grep(struct view *view, struct line *line)
4419         struct status *status = line->data;
4420         enum { S_STATUS, S_NAME, S_END } state;
4421         char buf[2] = "?";
4422         regmatch_t pmatch;
4424         if (!status)
4425                 return FALSE;
4427         for (state = S_STATUS; state < S_END; state++) {
4428                 char *text;
4430                 switch (state) {
4431                 case S_NAME:    text = status->new.name;        break;
4432                 case S_STATUS:
4433                         buf[0] = status->status;
4434                         text = buf;
4435                         break;
4437                 default:
4438                         return FALSE;
4439                 }
4441                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4442                         return TRUE;
4443         }
4445         return FALSE;
4448 static struct view_ops status_ops = {
4449         "file",
4450         status_open,
4451         NULL,
4452         status_draw,
4453         status_request,
4454         status_grep,
4455         status_select,
4456 };
4459 static bool
4460 stage_diff_line(FILE *pipe, struct line *line)
4462         char *buf = line->data;
4463         size_t bufsize = strlen(buf);
4464         size_t written = 0;
4466         while (!ferror(pipe) && written < bufsize) {
4467                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4468         }
4470         fputc('\n', pipe);
4472         return written == bufsize;
4475 static bool
4476 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4478         while (line < end) {
4479                 if (!stage_diff_line(pipe, line++))
4480                         return FALSE;
4481                 if (line->type == LINE_DIFF_CHUNK ||
4482                     line->type == LINE_DIFF_HEADER)
4483                         break;
4484         }
4486         return TRUE;
4489 static struct line *
4490 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4492         for (; view->line < line; line--)
4493                 if (line->type == type)
4494                         return line;
4496         return NULL;
4499 static bool
4500 stage_update_chunk(struct view *view, struct line *chunk)
4502         char cmd[SIZEOF_STR];
4503         size_t cmdsize = 0;
4504         struct line *diff_hdr;
4505         FILE *pipe;
4507         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4508         if (!diff_hdr)
4509                 return FALSE;
4511         if (opt_cdup[0] &&
4512             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4513                 return FALSE;
4515         if (!string_format_from(cmd, &cmdsize,
4516                                 "git apply --whitespace=nowarn --cached %s - && "
4517                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4518                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4519                 return FALSE;
4521         pipe = popen(cmd, "w");
4522         if (!pipe)
4523                 return FALSE;
4525         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4526             !stage_diff_write(pipe, chunk, view->line + view->lines))
4527                 chunk = NULL;
4529         pclose(pipe);
4531         return chunk ? TRUE : FALSE;
4534 static bool
4535 stage_update(struct view *view, struct line *line)
4537         struct line *chunk = NULL;
4539         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4540                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4542         if (chunk) {
4543                 if (!stage_update_chunk(view, chunk)) {
4544                         report("Failed to apply chunk");
4545                         return FALSE;
4546                 }
4548         } else if (!status_update_file(&stage_status, stage_line_type)) {
4549                 report("Failed to update file");
4550                 return FALSE;
4551         }
4553         return TRUE;
4556 static enum request
4557 stage_request(struct view *view, enum request request, struct line *line)
4559         switch (request) {
4560         case REQ_STATUS_UPDATE:
4561                 stage_update(view, line);
4562                 break;
4564         case REQ_EDIT:
4565                 if (!stage_status.new.name[0])
4566                         return request;
4568                 open_editor(stage_status.status != '?', stage_status.new.name);
4569                 break;
4571         case REQ_REFRESH:
4572                 /* Reload everything ... */
4573                 break;
4575         case REQ_VIEW_BLAME:
4576                 if (stage_status.new.name[0]) {
4577                         string_copy(opt_file, stage_status.new.name);
4578                         opt_ref[0] = 0;
4579                 }
4580                 return request;
4582         case REQ_ENTER:
4583                 return pager_request(view, request, line);
4585         default:
4586                 return request;
4587         }
4589         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4591         /* Check whether the staged entry still exists, and close the
4592          * stage view if it doesn't. */
4593         if (!status_exists(&stage_status, stage_line_type))
4594                 return REQ_VIEW_CLOSE;
4596         if (stage_line_type == LINE_STAT_UNTRACKED)
4597                 opt_pipe = fopen(stage_status.new.name, "r");
4598         else
4599                 string_copy(opt_cmd, view->cmd);
4600         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4602         return REQ_NONE;
4605 static struct view_ops stage_ops = {
4606         "line",
4607         NULL,
4608         pager_read,
4609         pager_draw,
4610         stage_request,
4611         pager_grep,
4612         pager_select,
4613 };
4616 /*
4617  * Revision graph
4618  */
4620 struct commit {
4621         char id[SIZEOF_REV];            /* SHA1 ID. */
4622         char title[128];                /* First line of the commit message. */
4623         char author[75];                /* Author of the commit. */
4624         struct tm time;                 /* Date from the author ident. */
4625         struct ref **refs;              /* Repository references. */
4626         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4627         size_t graph_size;              /* The width of the graph array. */
4628         bool has_parents;               /* Rewritten --parents seen. */
4629 };
4631 /* Size of rev graph with no  "padding" columns */
4632 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4634 struct rev_graph {
4635         struct rev_graph *prev, *next, *parents;
4636         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4637         size_t size;
4638         struct commit *commit;
4639         size_t pos;
4640         unsigned int boundary:1;
4641 };
4643 /* Parents of the commit being visualized. */
4644 static struct rev_graph graph_parents[4];
4646 /* The current stack of revisions on the graph. */
4647 static struct rev_graph graph_stacks[4] = {
4648         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4649         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4650         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4651         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4652 };
4654 static inline bool
4655 graph_parent_is_merge(struct rev_graph *graph)
4657         return graph->parents->size > 1;
4660 static inline void
4661 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4663         struct commit *commit = graph->commit;
4665         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4666                 commit->graph[commit->graph_size++] = symbol;
4669 static void
4670 done_rev_graph(struct rev_graph *graph)
4672         if (graph_parent_is_merge(graph) &&
4673             graph->pos < graph->size - 1 &&
4674             graph->next->size == graph->size + graph->parents->size - 1) {
4675                 size_t i = graph->pos + graph->parents->size - 1;
4677                 graph->commit->graph_size = i * 2;
4678                 while (i < graph->next->size - 1) {
4679                         append_to_rev_graph(graph, ' ');
4680                         append_to_rev_graph(graph, '\\');
4681                         i++;
4682                 }
4683         }
4685         graph->size = graph->pos = 0;
4686         graph->commit = NULL;
4687         memset(graph->parents, 0, sizeof(*graph->parents));
4690 static void
4691 push_rev_graph(struct rev_graph *graph, char *parent)
4693         int i;
4695         /* "Collapse" duplicate parents lines.
4696          *
4697          * FIXME: This needs to also update update the drawn graph but
4698          * for now it just serves as a method for pruning graph lines. */
4699         for (i = 0; i < graph->size; i++)
4700                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4701                         return;
4703         if (graph->size < SIZEOF_REVITEMS) {
4704                 string_copy_rev(graph->rev[graph->size++], parent);
4705         }
4708 static chtype
4709 get_rev_graph_symbol(struct rev_graph *graph)
4711         chtype symbol;
4713         if (graph->boundary)
4714                 symbol = REVGRAPH_BOUND;
4715         else if (graph->parents->size == 0)
4716                 symbol = REVGRAPH_INIT;
4717         else if (graph_parent_is_merge(graph))
4718                 symbol = REVGRAPH_MERGE;
4719         else if (graph->pos >= graph->size)
4720                 symbol = REVGRAPH_BRANCH;
4721         else
4722                 symbol = REVGRAPH_COMMIT;
4724         return symbol;
4727 static void
4728 draw_rev_graph(struct rev_graph *graph)
4730         struct rev_filler {
4731                 chtype separator, line;
4732         };
4733         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4734         static struct rev_filler fillers[] = {
4735                 { ' ',  REVGRAPH_LINE },
4736                 { '`',  '.' },
4737                 { '\'', ' ' },
4738                 { '/',  ' ' },
4739         };
4740         chtype symbol = get_rev_graph_symbol(graph);
4741         struct rev_filler *filler;
4742         size_t i;
4744         filler = &fillers[DEFAULT];
4746         for (i = 0; i < graph->pos; i++) {
4747                 append_to_rev_graph(graph, filler->line);
4748                 if (graph_parent_is_merge(graph->prev) &&
4749                     graph->prev->pos == i)
4750                         filler = &fillers[RSHARP];
4752                 append_to_rev_graph(graph, filler->separator);
4753         }
4755         /* Place the symbol for this revision. */
4756         append_to_rev_graph(graph, symbol);
4758         if (graph->prev->size > graph->size)
4759                 filler = &fillers[RDIAG];
4760         else
4761                 filler = &fillers[DEFAULT];
4763         i++;
4765         for (; i < graph->size; i++) {
4766                 append_to_rev_graph(graph, filler->separator);
4767                 append_to_rev_graph(graph, filler->line);
4768                 if (graph_parent_is_merge(graph->prev) &&
4769                     i < graph->prev->pos + graph->parents->size)
4770                         filler = &fillers[RSHARP];
4771                 if (graph->prev->size > graph->size)
4772                         filler = &fillers[LDIAG];
4773         }
4775         if (graph->prev->size > graph->size) {
4776                 append_to_rev_graph(graph, filler->separator);
4777                 if (filler->line != ' ')
4778                         append_to_rev_graph(graph, filler->line);
4779         }
4782 /* Prepare the next rev graph */
4783 static void
4784 prepare_rev_graph(struct rev_graph *graph)
4786         size_t i;
4788         /* First, traverse all lines of revisions up to the active one. */
4789         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4790                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4791                         break;
4793                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4794         }
4796         /* Interleave the new revision parent(s). */
4797         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4798                 push_rev_graph(graph->next, graph->parents->rev[i]);
4800         /* Lastly, put any remaining revisions. */
4801         for (i = graph->pos + 1; i < graph->size; i++)
4802                 push_rev_graph(graph->next, graph->rev[i]);
4805 static void
4806 update_rev_graph(struct rev_graph *graph)
4808         /* If this is the finalizing update ... */
4809         if (graph->commit)
4810                 prepare_rev_graph(graph);
4812         /* Graph visualization needs a one rev look-ahead,
4813          * so the first update doesn't visualize anything. */
4814         if (!graph->prev->commit)
4815                 return;
4817         draw_rev_graph(graph->prev);
4818         done_rev_graph(graph->prev->prev);
4822 /*
4823  * Main view backend
4824  */
4826 static bool
4827 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4829         struct commit *commit = line->data;
4830         enum line_type type;
4831         int col = 0;
4833         if (!*commit->author)
4834                 return FALSE;
4836         if (selected) {
4837                 type = LINE_CURSOR;
4838         } else {
4839                 type = LINE_MAIN_COMMIT;
4840         }
4842         if (opt_date) {
4843                 col += draw_date(view, &commit->time, view->width, selected);
4844                 if (col >= view->width)
4845                         return TRUE;
4846         }
4847         if (type != LINE_CURSOR)
4848                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4850         if (opt_author) {
4851                 int max_len;
4853                 max_len = view->width - col;
4854                 if (max_len > AUTHOR_COLS - 1)
4855                         max_len = AUTHOR_COLS - 1;
4856                 draw_text(view, commit->author, max_len, TRUE, selected);
4857                 col += AUTHOR_COLS;
4858                 if (col >= view->width)
4859                         return TRUE;
4860         }
4862         if (opt_rev_graph && commit->graph_size) {
4863                 size_t graph_size = view->width - col;
4864                 size_t i;
4866                 if (type != LINE_CURSOR)
4867                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4868                 wmove(view->win, lineno, col);
4869                 if (graph_size > commit->graph_size)
4870                         graph_size = commit->graph_size;
4871                 /* Using waddch() instead of waddnstr() ensures that
4872                  * they'll be rendered correctly for the cursor line. */
4873                 for (i = 0; i < graph_size; i++)
4874                         waddch(view->win, commit->graph[i]);
4876                 col += commit->graph_size + 1;
4877                 if (col >= view->width)
4878                         return TRUE;
4879                 waddch(view->win, ' ');
4880         }
4881         if (type != LINE_CURSOR)
4882                 wattrset(view->win, A_NORMAL);
4884         wmove(view->win, lineno, col);
4886         if (opt_show_refs && commit->refs) {
4887                 size_t i = 0;
4889                 do {
4890                         if (type == LINE_CURSOR)
4891                                 ;
4892                         else if (commit->refs[i]->head)
4893                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4894                         else if (commit->refs[i]->ltag)
4895                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4896                         else if (commit->refs[i]->tag)
4897                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4898                         else if (commit->refs[i]->tracked)
4899                                 wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
4900                         else if (commit->refs[i]->remote)
4901                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4902                         else
4903                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4905                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4906                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4907                                          TRUE, selected);
4908                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4909                         if (type != LINE_CURSOR)
4910                                 wattrset(view->win, A_NORMAL);
4911                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4912                         if (col >= view->width)
4913                                 return TRUE;
4914                 } while (commit->refs[i++]->next);
4915         }
4917         if (type != LINE_CURSOR)
4918                 wattrset(view->win, get_line_attr(type));
4920         draw_text(view, commit->title, view->width - col, TRUE, selected);
4921         return TRUE;
4924 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4925 static bool
4926 main_read(struct view *view, char *line)
4928         static struct rev_graph *graph = graph_stacks;
4929         enum line_type type;
4930         struct commit *commit;
4932         if (!line) {
4933                 if (!view->lines && !view->parent)
4934                         die("No revisions match the given arguments.");
4935                 update_rev_graph(graph);
4936                 return TRUE;
4937         }
4939         type = get_line_type(line);
4940         if (type == LINE_COMMIT) {
4941                 commit = calloc(1, sizeof(struct commit));
4942                 if (!commit)
4943                         return FALSE;
4945                 line += STRING_SIZE("commit ");
4946                 if (*line == '-') {
4947                         graph->boundary = 1;
4948                         line++;
4949                 }
4951                 string_copy_rev(commit->id, line);
4952                 commit->refs = get_refs(commit->id);
4953                 graph->commit = commit;
4954                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4956                 while ((line = strchr(line, ' '))) {
4957                         line++;
4958                         push_rev_graph(graph->parents, line);
4959                         commit->has_parents = TRUE;
4960                 }
4961                 return TRUE;
4962         }
4964         if (!view->lines)
4965                 return TRUE;
4966         commit = view->line[view->lines - 1].data;
4968         switch (type) {
4969         case LINE_PARENT:
4970                 if (commit->has_parents)
4971                         break;
4972                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4973                 break;
4975         case LINE_AUTHOR:
4976         {
4977                 /* Parse author lines where the name may be empty:
4978                  *      author  <email@address.tld> 1138474660 +0100
4979                  */
4980                 char *ident = line + STRING_SIZE("author ");
4981                 char *nameend = strchr(ident, '<');
4982                 char *emailend = strchr(ident, '>');
4984                 if (!nameend || !emailend)
4985                         break;
4987                 update_rev_graph(graph);
4988                 graph = graph->next;
4990                 *nameend = *emailend = 0;
4991                 ident = chomp_string(ident);
4992                 if (!*ident) {
4993                         ident = chomp_string(nameend + 1);
4994                         if (!*ident)
4995                                 ident = "Unknown";
4996                 }
4998                 string_ncopy(commit->author, ident, strlen(ident));
5000                 /* Parse epoch and timezone */
5001                 if (emailend[1] == ' ') {
5002                         char *secs = emailend + 2;
5003                         char *zone = strchr(secs, ' ');
5004                         time_t time = (time_t) atol(secs);
5006                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5007                                 long tz;
5009                                 zone++;
5010                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5011                                 tz += ('0' - zone[2]) * 60 * 60;
5012                                 tz += ('0' - zone[3]) * 60;
5013                                 tz += ('0' - zone[4]) * 60;
5015                                 if (zone[0] == '-')
5016                                         tz = -tz;
5018                                 time -= tz;
5019                         }
5021                         gmtime_r(&time, &commit->time);
5022                 }
5023                 break;
5024         }
5025         default:
5026                 /* Fill in the commit title if it has not already been set. */
5027                 if (commit->title[0])
5028                         break;
5030                 /* Require titles to start with a non-space character at the
5031                  * offset used by git log. */
5032                 if (strncmp(line, "    ", 4))
5033                         break;
5034                 line += 4;
5035                 /* Well, if the title starts with a whitespace character,
5036                  * try to be forgiving.  Otherwise we end up with no title. */
5037                 while (isspace(*line))
5038                         line++;
5039                 if (*line == '\0')
5040                         break;
5041                 /* FIXME: More graceful handling of titles; append "..." to
5042                  * shortened titles, etc. */
5044                 string_ncopy(commit->title, line, strlen(line));
5045         }
5047         return TRUE;
5050 static enum request
5051 main_request(struct view *view, enum request request, struct line *line)
5053         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5055         if (request == REQ_ENTER)
5056                 open_view(view, REQ_VIEW_DIFF, flags);
5057         else
5058                 return request;
5060         return REQ_NONE;
5063 static bool
5064 main_grep(struct view *view, struct line *line)
5066         struct commit *commit = line->data;
5067         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5068         char buf[DATE_COLS + 1];
5069         regmatch_t pmatch;
5071         for (state = S_TITLE; state < S_END; state++) {
5072                 char *text;
5074                 switch (state) {
5075                 case S_TITLE:   text = commit->title;   break;
5076                 case S_AUTHOR:  text = commit->author;  break;
5077                 case S_DATE:
5078                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5079                                 continue;
5080                         text = buf;
5081                         break;
5083                 default:
5084                         return FALSE;
5085                 }
5087                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5088                         return TRUE;
5089         }
5091         return FALSE;
5094 static void
5095 main_select(struct view *view, struct line *line)
5097         struct commit *commit = line->data;
5099         string_copy_rev(view->ref, commit->id);
5100         string_copy_rev(ref_commit, view->ref);
5103 static struct view_ops main_ops = {
5104         "commit",
5105         NULL,
5106         main_read,
5107         main_draw,
5108         main_request,
5109         main_grep,
5110         main_select,
5111 };
5114 /*
5115  * Unicode / UTF-8 handling
5116  *
5117  * NOTE: Much of the following code for dealing with unicode is derived from
5118  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5119  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5120  */
5122 /* I've (over)annotated a lot of code snippets because I am not entirely
5123  * confident that the approach taken by this small UTF-8 interface is correct.
5124  * --jonas */
5126 static inline int
5127 unicode_width(unsigned long c)
5129         if (c >= 0x1100 &&
5130            (c <= 0x115f                         /* Hangul Jamo */
5131             || c == 0x2329
5132             || c == 0x232a
5133             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5134                                                 /* CJK ... Yi */
5135             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5136             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5137             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5138             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5139             || (c >= 0xffe0  && c <= 0xffe6)
5140             || (c >= 0x20000 && c <= 0x2fffd)
5141             || (c >= 0x30000 && c <= 0x3fffd)))
5142                 return 2;
5144         if (c == '\t')
5145                 return opt_tab_size;
5147         return 1;
5150 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5151  * Illegal bytes are set one. */
5152 static const unsigned char utf8_bytes[256] = {
5153         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,
5154         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,
5155         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,
5156         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,
5157         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,
5158         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,
5159         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,
5160         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,
5161 };
5163 /* Decode UTF-8 multi-byte representation into a unicode character. */
5164 static inline unsigned long
5165 utf8_to_unicode(const char *string, size_t length)
5167         unsigned long unicode;
5169         switch (length) {
5170         case 1:
5171                 unicode  =   string[0];
5172                 break;
5173         case 2:
5174                 unicode  =  (string[0] & 0x1f) << 6;
5175                 unicode +=  (string[1] & 0x3f);
5176                 break;
5177         case 3:
5178                 unicode  =  (string[0] & 0x0f) << 12;
5179                 unicode += ((string[1] & 0x3f) << 6);
5180                 unicode +=  (string[2] & 0x3f);
5181                 break;
5182         case 4:
5183                 unicode  =  (string[0] & 0x0f) << 18;
5184                 unicode += ((string[1] & 0x3f) << 12);
5185                 unicode += ((string[2] & 0x3f) << 6);
5186                 unicode +=  (string[3] & 0x3f);
5187                 break;
5188         case 5:
5189                 unicode  =  (string[0] & 0x0f) << 24;
5190                 unicode += ((string[1] & 0x3f) << 18);
5191                 unicode += ((string[2] & 0x3f) << 12);
5192                 unicode += ((string[3] & 0x3f) << 6);
5193                 unicode +=  (string[4] & 0x3f);
5194                 break;
5195         case 6:
5196                 unicode  =  (string[0] & 0x01) << 30;
5197                 unicode += ((string[1] & 0x3f) << 24);
5198                 unicode += ((string[2] & 0x3f) << 18);
5199                 unicode += ((string[3] & 0x3f) << 12);
5200                 unicode += ((string[4] & 0x3f) << 6);
5201                 unicode +=  (string[5] & 0x3f);
5202                 break;
5203         default:
5204                 die("Invalid unicode length");
5205         }
5207         /* Invalid characters could return the special 0xfffd value but NUL
5208          * should be just as good. */
5209         return unicode > 0xffff ? 0 : unicode;
5212 /* Calculates how much of string can be shown within the given maximum width
5213  * and sets trimmed parameter to non-zero value if all of string could not be
5214  * shown. If the reserve flag is TRUE, it will reserve at least one
5215  * trailing character, which can be useful when drawing a delimiter.
5216  *
5217  * Returns the number of bytes to output from string to satisfy max_width. */
5218 static size_t
5219 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5221         const char *start = string;
5222         const char *end = strchr(string, '\0');
5223         unsigned char last_bytes = 0;
5224         size_t width = 0;
5226         *trimmed = 0;
5228         while (string < end) {
5229                 int c = *(unsigned char *) string;
5230                 unsigned char bytes = utf8_bytes[c];
5231                 size_t ucwidth;
5232                 unsigned long unicode;
5234                 if (string + bytes > end)
5235                         break;
5237                 /* Change representation to figure out whether
5238                  * it is a single- or double-width character. */
5240                 unicode = utf8_to_unicode(string, bytes);
5241                 /* FIXME: Graceful handling of invalid unicode character. */
5242                 if (!unicode)
5243                         break;
5245                 ucwidth = unicode_width(unicode);
5246                 width  += ucwidth;
5247                 if (width > max_width) {
5248                         *trimmed = 1;
5249                         if (reserve && width - ucwidth == max_width) {
5250                                 string -= last_bytes;
5251                         }
5252                         break;
5253                 }
5255                 string  += bytes;
5256                 last_bytes = bytes;
5257         }
5259         return string - start;
5263 /*
5264  * Status management
5265  */
5267 /* Whether or not the curses interface has been initialized. */
5268 static bool cursed = FALSE;
5270 /* The status window is used for polling keystrokes. */
5271 static WINDOW *status_win;
5273 static bool status_empty = TRUE;
5275 /* Update status and title window. */
5276 static void
5277 report(const char *msg, ...)
5279         struct view *view = display[current_view];
5281         if (input_mode)
5282                 return;
5284         if (!view) {
5285                 char buf[SIZEOF_STR];
5286                 va_list args;
5288                 va_start(args, msg);
5289                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5290                         buf[sizeof(buf) - 1] = 0;
5291                         buf[sizeof(buf) - 2] = '.';
5292                         buf[sizeof(buf) - 3] = '.';
5293                         buf[sizeof(buf) - 4] = '.';
5294                 }
5295                 va_end(args);
5296                 die("%s", buf);
5297         }
5299         if (!status_empty || *msg) {
5300                 va_list args;
5302                 va_start(args, msg);
5304                 wmove(status_win, 0, 0);
5305                 if (*msg) {
5306                         vwprintw(status_win, msg, args);
5307                         status_empty = FALSE;
5308                 } else {
5309                         status_empty = TRUE;
5310                 }
5311                 wclrtoeol(status_win);
5312                 wrefresh(status_win);
5314                 va_end(args);
5315         }
5317         update_view_title(view);
5318         update_display_cursor(view);
5321 /* Controls when nodelay should be in effect when polling user input. */
5322 static void
5323 set_nonblocking_input(bool loading)
5325         static unsigned int loading_views;
5327         if ((loading == FALSE && loading_views-- == 1) ||
5328             (loading == TRUE  && loading_views++ == 0))
5329                 nodelay(status_win, loading);
5332 static void
5333 init_display(void)
5335         int x, y;
5337         /* Initialize the curses library */
5338         if (isatty(STDIN_FILENO)) {
5339                 cursed = !!initscr();
5340         } else {
5341                 /* Leave stdin and stdout alone when acting as a pager. */
5342                 FILE *io = fopen("/dev/tty", "r+");
5344                 if (!io)
5345                         die("Failed to open /dev/tty");
5346                 cursed = !!newterm(NULL, io, io);
5347         }
5349         if (!cursed)
5350                 die("Failed to initialize curses");
5352         nonl();         /* Tell curses not to do NL->CR/NL on output */
5353         cbreak();       /* Take input chars one at a time, no wait for \n */
5354         noecho();       /* Don't echo input */
5355         leaveok(stdscr, TRUE);
5357         if (has_colors())
5358                 init_colors();
5360         getmaxyx(stdscr, y, x);
5361         status_win = newwin(1, 0, y - 1, 0);
5362         if (!status_win)
5363                 die("Failed to create status window");
5365         /* Enable keyboard mapping */
5366         keypad(status_win, TRUE);
5367         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5370 static char *
5371 read_prompt(const char *prompt)
5373         enum { READING, STOP, CANCEL } status = READING;
5374         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5375         int pos = 0;
5377         while (status == READING) {
5378                 struct view *view;
5379                 int i, key;
5381                 input_mode = TRUE;
5383                 foreach_view (view, i)
5384                         update_view(view);
5386                 input_mode = FALSE;
5388                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5389                 wclrtoeol(status_win);
5391                 /* Refresh, accept single keystroke of input */
5392                 key = wgetch(status_win);
5393                 switch (key) {
5394                 case KEY_RETURN:
5395                 case KEY_ENTER:
5396                 case '\n':
5397                         status = pos ? STOP : CANCEL;
5398                         break;
5400                 case KEY_BACKSPACE:
5401                         if (pos > 0)
5402                                 pos--;
5403                         else
5404                                 status = CANCEL;
5405                         break;
5407                 case KEY_ESC:
5408                         status = CANCEL;
5409                         break;
5411                 case ERR:
5412                         break;
5414                 default:
5415                         if (pos >= sizeof(buf)) {
5416                                 report("Input string too long");
5417                                 return NULL;
5418                         }
5420                         if (isprint(key))
5421                                 buf[pos++] = (char) key;
5422                 }
5423         }
5425         /* Clear the status window */
5426         status_empty = FALSE;
5427         report("");
5429         if (status == CANCEL)
5430                 return NULL;
5432         buf[pos++] = 0;
5434         return buf;
5437 /*
5438  * Repository references
5439  */
5441 static struct ref *refs = NULL;
5442 static size_t refs_alloc = 0;
5443 static size_t refs_size = 0;
5445 /* Id <-> ref store */
5446 static struct ref ***id_refs = NULL;
5447 static size_t id_refs_alloc = 0;
5448 static size_t id_refs_size = 0;
5450 static struct ref **
5451 get_refs(char *id)
5453         struct ref ***tmp_id_refs;
5454         struct ref **ref_list = NULL;
5455         size_t ref_list_alloc = 0;
5456         size_t ref_list_size = 0;
5457         size_t i;
5459         for (i = 0; i < id_refs_size; i++)
5460                 if (!strcmp(id, id_refs[i][0]->id))
5461                         return id_refs[i];
5463         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5464                                     sizeof(*id_refs));
5465         if (!tmp_id_refs)
5466                 return NULL;
5468         id_refs = tmp_id_refs;
5470         for (i = 0; i < refs_size; i++) {
5471                 struct ref **tmp;
5473                 if (strcmp(id, refs[i].id))
5474                         continue;
5476                 tmp = realloc_items(ref_list, &ref_list_alloc,
5477                                     ref_list_size + 1, sizeof(*ref_list));
5478                 if (!tmp) {
5479                         if (ref_list)
5480                                 free(ref_list);
5481                         return NULL;
5482                 }
5484                 ref_list = tmp;
5485                 if (ref_list_size > 0)
5486                         ref_list[ref_list_size - 1]->next = 1;
5487                 ref_list[ref_list_size] = &refs[i];
5489                 /* XXX: The properties of the commit chains ensures that we can
5490                  * safely modify the shared ref. The repo references will
5491                  * always be similar for the same id. */
5492                 ref_list[ref_list_size]->next = 0;
5493                 ref_list_size++;
5494         }
5496         if (ref_list)
5497                 id_refs[id_refs_size++] = ref_list;
5499         return ref_list;
5502 static int
5503 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5505         struct ref *ref;
5506         bool tag = FALSE;
5507         bool ltag = FALSE;
5508         bool remote = FALSE;
5509         bool tracked = FALSE;
5510         bool check_replace = FALSE;
5511         bool head = FALSE;
5513         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5514                 if (!strcmp(name + namelen - 3, "^{}")) {
5515                         namelen -= 3;
5516                         name[namelen] = 0;
5517                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5518                                 check_replace = TRUE;
5519                 } else {
5520                         ltag = TRUE;
5521                 }
5523                 tag = TRUE;
5524                 namelen -= STRING_SIZE("refs/tags/");
5525                 name    += STRING_SIZE("refs/tags/");
5527         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5528                 remote = TRUE;
5529                 namelen -= STRING_SIZE("refs/remotes/");
5530                 name    += STRING_SIZE("refs/remotes/");
5531                 tracked  = !strcmp(opt_remote, name);
5533         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5534                 namelen -= STRING_SIZE("refs/heads/");
5535                 name    += STRING_SIZE("refs/heads/");
5536                 head     = !strncmp(opt_head, name, namelen);
5538         } else if (!strcmp(name, "HEAD")) {
5539                 opt_no_head = FALSE;
5540                 return OK;
5541         }
5543         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5544                 /* it's an annotated tag, replace the previous sha1 with the
5545                  * resolved commit id; relies on the fact git-ls-remote lists
5546                  * the commit id of an annotated tag right beofre the commit id
5547                  * it points to. */
5548                 refs[refs_size - 1].ltag = ltag;
5549                 string_copy_rev(refs[refs_size - 1].id, id);
5551                 return OK;
5552         }
5553         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5554         if (!refs)
5555                 return ERR;
5557         ref = &refs[refs_size++];
5558         ref->name = malloc(namelen + 1);
5559         if (!ref->name)
5560                 return ERR;
5562         strncpy(ref->name, name, namelen);
5563         ref->name[namelen] = 0;
5564         ref->head = head;
5565         ref->tag = tag;
5566         ref->ltag = ltag;
5567         ref->remote = remote;
5568         ref->tracked = tracked;
5569         string_copy_rev(ref->id, id);
5571         return OK;
5574 static int
5575 load_refs(void)
5577         const char *cmd_env = getenv("TIG_LS_REMOTE");
5578         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5580         return read_properties(popen(cmd, "r"), "\t", read_ref);
5583 static int
5584 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5586         if (!strcmp(name, "i18n.commitencoding"))
5587                 string_ncopy(opt_encoding, value, valuelen);
5589         if (!strcmp(name, "core.editor"))
5590                 string_ncopy(opt_editor, value, valuelen);
5592         /* branch.<head>.remote */
5593         if (*opt_head &&
5594             !strncmp(name, "branch.", 7) &&
5595             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5596             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5597                 string_ncopy(opt_remote, value, valuelen);
5599         if (*opt_head && *opt_remote &&
5600             !strncmp(name, "branch.", 7) &&
5601             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5602             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5603                 size_t from = strlen(opt_remote);
5605                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5606                         value += STRING_SIZE("refs/heads/");
5607                         valuelen -= STRING_SIZE("refs/heads/");
5608                 }
5610                 if (!string_format_from(opt_remote, &from, "/%s", value))
5611                         opt_remote[0] = 0;
5612         }
5614         return OK;
5617 static int
5618 load_git_config(void)
5620         return read_properties(popen(GIT_CONFIG " --list", "r"),
5621                                "=", read_repo_config_option);
5624 static int
5625 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5627         if (!opt_git_dir[0]) {
5628                 string_ncopy(opt_git_dir, name, namelen);
5630         } else if (opt_is_inside_work_tree == -1) {
5631                 /* This can be 3 different values depending on the
5632                  * version of git being used. If git-rev-parse does not
5633                  * understand --is-inside-work-tree it will simply echo
5634                  * the option else either "true" or "false" is printed.
5635                  * Default to true for the unknown case. */
5636                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5638         } else if (opt_cdup[0] == ' ') {
5639                 string_ncopy(opt_cdup, name, namelen);
5640         } else {
5641                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5642                         namelen -= STRING_SIZE("refs/heads/");
5643                         name    += STRING_SIZE("refs/heads/");
5644                         string_ncopy(opt_head, name, namelen);
5645                 }
5646         }
5648         return OK;
5651 static int
5652 load_repo_info(void)
5654         int result;
5655         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5656                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5658         /* XXX: The line outputted by "--show-cdup" can be empty so
5659          * initialize it to something invalid to make it possible to
5660          * detect whether it has been set or not. */
5661         opt_cdup[0] = ' ';
5663         result = read_properties(pipe, "=", read_repo_info);
5664         if (opt_cdup[0] == ' ')
5665                 opt_cdup[0] = 0;
5667         return result;
5670 static int
5671 read_properties(FILE *pipe, const char *separators,
5672                 int (*read_property)(char *, size_t, char *, size_t))
5674         char buffer[BUFSIZ];
5675         char *name;
5676         int state = OK;
5678         if (!pipe)
5679                 return ERR;
5681         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5682                 char *value;
5683                 size_t namelen;
5684                 size_t valuelen;
5686                 name = chomp_string(name);
5687                 namelen = strcspn(name, separators);
5689                 if (name[namelen]) {
5690                         name[namelen] = 0;
5691                         value = chomp_string(name + namelen + 1);
5692                         valuelen = strlen(value);
5694                 } else {
5695                         value = "";
5696                         valuelen = 0;
5697                 }
5699                 state = read_property(name, namelen, value, valuelen);
5700         }
5702         if (state != ERR && ferror(pipe))
5703                 state = ERR;
5705         pclose(pipe);
5707         return state;
5711 /*
5712  * Main
5713  */
5715 static void __NORETURN
5716 quit(int sig)
5718         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5719         if (cursed)
5720                 endwin();
5721         exit(0);
5724 static void __NORETURN
5725 die(const char *err, ...)
5727         va_list args;
5729         endwin();
5731         va_start(args, err);
5732         fputs("tig: ", stderr);
5733         vfprintf(stderr, err, args);
5734         fputs("\n", stderr);
5735         va_end(args);
5737         exit(1);
5740 static void
5741 warn(const char *msg, ...)
5743         va_list args;
5745         va_start(args, msg);
5746         fputs("tig warning: ", stderr);
5747         vfprintf(stderr, msg, args);
5748         fputs("\n", stderr);
5749         va_end(args);
5752 int
5753 main(int argc, char *argv[])
5755         struct view *view;
5756         enum request request;
5757         size_t i;
5759         signal(SIGINT, quit);
5761         if (setlocale(LC_ALL, "")) {
5762                 char *codeset = nl_langinfo(CODESET);
5764                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5765         }
5767         if (load_repo_info() == ERR)
5768                 die("Failed to load repo info.");
5770         if (load_options() == ERR)
5771                 die("Failed to load user config.");
5773         if (load_git_config() == ERR)
5774                 die("Failed to load repo config.");
5776         if (!parse_options(argc, argv))
5777                 return 0;
5779         /* Require a git repository unless when running in pager mode. */
5780         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5781                 die("Not a git repository");
5783         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5784                 opt_utf8 = FALSE;
5786         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5787                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5788                 if (opt_iconv == ICONV_NONE)
5789                         die("Failed to initialize character set conversion");
5790         }
5792         if (*opt_git_dir && load_refs() == ERR)
5793                 die("Failed to load refs.");
5795         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5796                 view->cmd_env = getenv(view->cmd_env);
5798         request = opt_request;
5800         init_display();
5802         while (view_driver(display[current_view], request)) {
5803                 int key;
5804                 int i;
5806                 foreach_view (view, i)
5807                         update_view(view);
5809                 /* Refresh, accept single keystroke of input */
5810                 key = wgetch(status_win);
5812                 /* wgetch() with nodelay() enabled returns ERR when there's no
5813                  * input. */
5814                 if (key == ERR) {
5815                         request = REQ_NONE;
5816                         continue;
5817                 }
5819                 request = get_keybinding(display[current_view]->keymap, key);
5821                 /* Some low-level request handling. This keeps access to
5822                  * status_win restricted. */
5823                 switch (request) {
5824                 case REQ_PROMPT:
5825                 {
5826                         char *cmd = read_prompt(":");
5828                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5829                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5830                                         opt_request = REQ_VIEW_DIFF;
5831                                 } else {
5832                                         opt_request = REQ_VIEW_PAGER;
5833                                 }
5834                                 break;
5835                         }
5837                         request = REQ_NONE;
5838                         break;
5839                 }
5840                 case REQ_SEARCH:
5841                 case REQ_SEARCH_BACK:
5842                 {
5843                         const char *prompt = request == REQ_SEARCH
5844                                            ? "/" : "?";
5845                         char *search = read_prompt(prompt);
5847                         if (search)
5848                                 string_ncopy(opt_search, search, strlen(search));
5849                         else
5850                                 request = REQ_NONE;
5851                         break;
5852                 }
5853                 case REQ_SCREEN_RESIZE:
5854                 {
5855                         int height, width;
5857                         getmaxyx(stdscr, height, width);
5859                         /* Resize the status view and let the view driver take
5860                          * care of resizing the displayed views. */
5861                         wresize(status_win, 1, width);
5862                         mvwin(status_win, height - 1, 0);
5863                         wrefresh(status_win);
5864                         break;
5865                 }
5866                 default:
5867                         break;
5868                 }
5869         }
5871         quit(0);
5873         return 0;