Code

Add support for refreshing/reloading the status view
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 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 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
60 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
62 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x)  (sizeof(x) - 1)
65 #define SIZEOF_STR      1024    /* Default string size. */
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT   'I'
72 #define REVGRAPH_MERGE  'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE   '|'
77 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT   (-1)
82 #define ICONV_NONE      ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST     /* nothing */
85 #endif
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
89 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS     20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
96 #define TABSIZE         8
98 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
104 #define TIG_LS_REMOTE \
105         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108         "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD     \
111         "git log --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114         "git log --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD    \
117         "git ls-tree %s %s"
119 #define TIG_BLOB_CMD    \
120         "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD    ""
124 #define TIG_PAGER_CMD   ""
125 #define TIG_STATUS_CMD  ""
126 #define TIG_STAGE_CMD   ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int tag:1;     /* Is it a tag? */
138         unsigned int remote:1;  /* Is it a remote ref? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(char *id);
144 struct int_map {
145         const char *name;
146         int namelen;
147         int value;
148 };
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152                  int *value, const char *name, int namelen)
155         int i;
157         for (i = 0; i < map_size; i++)
158                 if (namelen == map[i].namelen &&
159                     !strncasecmp(name, map[i].name, namelen)) {
160                         *value = map[i].value;
161                         return OK;
162                 }
164         return ERR;
168 /*
169  * String helpers
170  */
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175         if (srclen > dstlen - 1)
176                 srclen = dstlen - 1;
178         strncpy(dst, src, srclen);
179         dst[srclen] = 0;
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188         string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
196 static char *
197 chomp_string(char *name)
199         int namelen;
201         while (isspace(*name))
202                 name++;
204         namelen = strlen(name) - 1;
205         while (namelen > 0 && isspace(name[namelen]))
206                 name[namelen--] = 0;
208         return name;
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
214         va_list args;
215         size_t pos = bufpos ? *bufpos : 0;
217         va_start(args, fmt);
218         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219         va_end(args);
221         if (bufpos)
222                 *bufpos = pos;
224         return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228         string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231         string_nformat(buf, sizeof(buf), from, fmt, args)
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
236         size_t i;
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240         /* Diff-Header == DIFF_HEADER */
241         for (i = 0; i < len; i++) {
242                 if (toupper(str1[i]) == toupper(str2[i]))
243                         continue;
245                 if (string_enum_sep(str1[i]) &&
246                     string_enum_sep(str2[i]))
247                         continue;
249                 return str1[i] - str2[i];
250         }
252         return 0;
255 /* Shell quoting
256  *
257  * NOTE: The following is a slightly modified copy of the git project's shell
258  * quoting routines found in the quote.c file.
259  *
260  * Help to copy the thing properly quoted for the shell safety.  any single
261  * quote is replaced with '\'', any exclamation point is replaced with '\!',
262  * and the whole thing is enclosed in a
263  *
264  * E.g.
265  *  original     sq_quote     result
266  *  name     ==> name      ==> 'name'
267  *  a b      ==> a b       ==> 'a b'
268  *  a'b      ==> a'\''b    ==> 'a'\''b'
269  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
270  */
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
275         char c;
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
279         BUFPUT('\'');
280         while ((c = *src++)) {
281                 if (c == '\'' || c == '!') {
282                         BUFPUT('\'');
283                         BUFPUT('\\');
284                         BUFPUT(c);
285                         BUFPUT('\'');
286                 } else {
287                         BUFPUT(c);
288                 }
289         }
290         BUFPUT('\'');
292         if (bufsize < SIZEOF_STR)
293                 buf[bufsize] = 0;
295         return bufsize;
299 /*
300  * User requests
301  */
303 #define REQ_INFO \
304         /* XXX: Keep the view request first and in sync with views[]. */ \
305         REQ_GROUP("View switching") \
306         REQ_(VIEW_MAIN,         "Show main view"), \
307         REQ_(VIEW_DIFF,         "Show diff view"), \
308         REQ_(VIEW_LOG,          "Show log view"), \
309         REQ_(VIEW_TREE,         "Show tree view"), \
310         REQ_(VIEW_BLOB,         "Show blob view"), \
311         REQ_(VIEW_HELP,         "Show help page"), \
312         REQ_(VIEW_PAGER,        "Show pager view"), \
313         REQ_(VIEW_STATUS,       "Show status view"), \
314         REQ_(VIEW_STAGE,        "Show stage view"), \
315         \
316         REQ_GROUP("View manipulation") \
317         REQ_(ENTER,             "Enter current line and scroll"), \
318         REQ_(NEXT,              "Move to next"), \
319         REQ_(PREVIOUS,          "Move to previous"), \
320         REQ_(VIEW_NEXT,         "Move focus to next view"), \
321         REQ_(REFRESH,           "Reload and refresh"), \
322         REQ_(VIEW_CLOSE,        "Close the current view"), \
323         REQ_(QUIT,              "Close all views and quit"), \
324         \
325         REQ_GROUP("Cursor navigation") \
326         REQ_(MOVE_UP,           "Move cursor one line up"), \
327         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
328         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
329         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
330         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
331         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
332         \
333         REQ_GROUP("Scrolling") \
334         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
335         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
336         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
337         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
338         \
339         REQ_GROUP("Searching") \
340         REQ_(SEARCH,            "Search the view"), \
341         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
342         REQ_(FIND_NEXT,         "Find next search match"), \
343         REQ_(FIND_PREV,         "Find previous search match"), \
344         \
345         REQ_GROUP("Misc") \
346         REQ_(NONE,              "Do nothing"), \
347         REQ_(PROMPT,            "Bring up the prompt"), \
348         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
349         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
350         REQ_(SHOW_VERSION,      "Show version information"), \
351         REQ_(STOP_LOADING,      "Stop all loading views"), \
352         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
353         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
354         REQ_(STATUS_UPDATE,     "Update file status"), \
355         REQ_(EDIT,              "Open in editor"), \
356         REQ_(CHERRY_PICK,       "Cherry-pick commit to current branch")
359 /* User action requests. */
360 enum request {
361 #define REQ_GROUP(help)
362 #define REQ_(req, help) REQ_##req
364         /* Offset all requests to avoid conflicts with ncurses getch values. */
365         REQ_OFFSET = KEY_MAX + 1,
366         REQ_INFO,
367         REQ_UNKNOWN,
369 #undef  REQ_GROUP
370 #undef  REQ_
371 };
373 struct request_info {
374         enum request request;
375         char *name;
376         int namelen;
377         char *help;
378 };
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
383         REQ_INFO
384 #undef  REQ_GROUP
385 #undef  REQ_
386 };
388 static enum request
389 get_request(const char *name)
391         int namelen = strlen(name);
392         int i;
394         for (i = 0; i < ARRAY_SIZE(req_info); i++)
395                 if (req_info[i].namelen == namelen &&
396                     !string_enum_compare(req_info[i].name, name, namelen))
397                         return req_info[i].request;
399         return REQ_UNKNOWN;
403 /*
404  * Options
405  */
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
409 "\n"
410 "Usage: tig [options]\n"
411 "   or: tig [options] [--] [git log options]\n"
412 "   or: tig [options] log  [git log options]\n"
413 "   or: tig [options] diff [git diff options]\n"
414 "   or: tig [options] show [git show options]\n"
415 "   or: tig [options] <    [git command output]\n"
416 "\n"
417 "Options:\n"
418 "  -l                          Start up in log view\n"
419 "  -d                          Start up in diff view\n"
420 "  -S                          Start up in status view\n"
421 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
422 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
423 "  --                          Mark end of tig options\n"
424 "  -v, --version               Show version and exit\n"
425 "  -h, --help                  Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number             = FALSE;
429 static bool opt_rev_graph               = FALSE;
430 static int opt_num_interval             = NUMBER_INTERVAL;
431 static int opt_tab_size                 = TABSIZE;
432 static enum request opt_request         = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR]         = "";
434 static char opt_path[SIZEOF_STR]        = "";
435 static FILE *opt_pipe                   = NULL;
436 static char opt_encoding[20]            = "UTF-8";
437 static bool opt_utf8                    = TRUE;
438 static char opt_codeset[20]             = "UTF-8";
439 static iconv_t opt_iconv                = ICONV_NONE;
440 static char opt_search[SIZEOF_STR]      = "";
441 static char opt_cdup[SIZEOF_STR]        = "";
442 static char opt_git_dir[SIZEOF_STR]     = "";
443 static char opt_editor[SIZEOF_STR]      = "";
445 enum option_type {
446         OPT_NONE,
447         OPT_INT,
448 };
450 static bool
451 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
453         va_list args;
454         char *value = "";
455         int *number;
457         if (opt[0] != '-')
458                 return FALSE;
460         if (opt[1] == '-') {
461                 int namelen = strlen(name);
463                 opt += 2;
465                 if (strncmp(opt, name, namelen))
466                         return FALSE;
468                 if (opt[namelen] == '=')
469                         value = opt + namelen + 1;
471         } else {
472                 if (!short_name || opt[1] != short_name)
473                         return FALSE;
474                 value = opt + 2;
475         }
477         va_start(args, type);
478         if (type == OPT_INT) {
479                 number = va_arg(args, int *);
480                 if (isdigit(*value))
481                         *number = atoi(value);
482         }
483         va_end(args);
485         return TRUE;
488 /* Returns the index of log or diff command or -1 to exit. */
489 static bool
490 parse_options(int argc, char *argv[])
492         int i;
494         for (i = 1; i < argc; i++) {
495                 char *opt = argv[i];
497                 if (!strcmp(opt, "log") ||
498                     !strcmp(opt, "diff") ||
499                     !strcmp(opt, "show")) {
500                         opt_request = opt[0] == 'l'
501                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
502                         break;
503                 }
505                 if (opt[0] && opt[0] != '-')
506                         break;
508                 if (!strcmp(opt, "-l")) {
509                         opt_request = REQ_VIEW_LOG;
510                         continue;
511                 }
513                 if (!strcmp(opt, "-d")) {
514                         opt_request = REQ_VIEW_DIFF;
515                         continue;
516                 }
518                 if (!strcmp(opt, "-S")) {
519                         opt_request = REQ_VIEW_STATUS;
520                         continue;
521                 }
523                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
524                         opt_line_number = TRUE;
525                         continue;
526                 }
528                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
529                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
530                         continue;
531                 }
533                 if (check_option(opt, 'v', "version", OPT_NONE)) {
534                         printf("tig version %s\n", TIG_VERSION);
535                         return FALSE;
536                 }
538                 if (check_option(opt, 'h', "help", OPT_NONE)) {
539                         printf(usage);
540                         return FALSE;
541                 }
543                 if (!strcmp(opt, "--")) {
544                         i++;
545                         break;
546                 }
548                 die("unknown option '%s'\n\n%s", opt, usage);
549         }
551         if (!isatty(STDIN_FILENO)) {
552                 opt_request = REQ_VIEW_PAGER;
553                 opt_pipe = stdin;
555         } else if (i < argc) {
556                 size_t buf_size;
558                 if (opt_request == REQ_VIEW_MAIN)
559                         /* XXX: This is vulnerable to the user overriding
560                          * options required for the main view parser. */
561                         string_copy(opt_cmd, "git log --pretty=raw");
562                 else
563                         string_copy(opt_cmd, "git");
564                 buf_size = strlen(opt_cmd);
566                 while (buf_size < sizeof(opt_cmd) && i < argc) {
567                         opt_cmd[buf_size++] = ' ';
568                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
569                 }
571                 if (buf_size >= sizeof(opt_cmd))
572                         die("command too long");
574                 opt_cmd[buf_size] = 0;
575         }
577         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
578                 opt_utf8 = FALSE;
580         return TRUE;
584 /*
585  * Line-oriented content detection.
586  */
588 #define LINE_INFO \
589 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
593 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
594 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
603 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
604 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
610 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
612 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
616 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
618 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
619 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
620 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
621 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
622 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
623 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
624 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
625 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
626 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
627 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
629 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
630 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
631 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
632 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
633 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
634 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
635 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
637 enum line_type {
638 #define LINE(type, line, fg, bg, attr) \
639         LINE_##type
640         LINE_INFO
641 #undef  LINE
642 };
644 struct line_info {
645         const char *name;       /* Option name. */
646         int namelen;            /* Size of option name. */
647         const char *line;       /* The start of line to match. */
648         int linelen;            /* Size of string to match. */
649         int fg, bg, attr;       /* Color and text attributes for the lines. */
650 };
652 static struct line_info line_info[] = {
653 #define LINE(type, line, fg, bg, attr) \
654         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
655         LINE_INFO
656 #undef  LINE
657 };
659 static enum line_type
660 get_line_type(char *line)
662         int linelen = strlen(line);
663         enum line_type type;
665         for (type = 0; type < ARRAY_SIZE(line_info); type++)
666                 /* Case insensitive search matches Signed-off-by lines better. */
667                 if (linelen >= line_info[type].linelen &&
668                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
669                         return type;
671         return LINE_DEFAULT;
674 static inline int
675 get_line_attr(enum line_type type)
677         assert(type < ARRAY_SIZE(line_info));
678         return COLOR_PAIR(type) | line_info[type].attr;
681 static struct line_info *
682 get_line_info(char *name, int namelen)
684         enum line_type type;
686         for (type = 0; type < ARRAY_SIZE(line_info); type++)
687                 if (namelen == line_info[type].namelen &&
688                     !string_enum_compare(line_info[type].name, name, namelen))
689                         return &line_info[type];
691         return NULL;
694 static void
695 init_colors(void)
697         int default_bg = COLOR_BLACK;
698         int default_fg = COLOR_WHITE;
699         enum line_type type;
701         start_color();
703         if (use_default_colors() != ERR) {
704                 default_bg = -1;
705                 default_fg = -1;
706         }
708         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
709                 struct line_info *info = &line_info[type];
710                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
711                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
713                 init_pair(type, fg, bg);
714         }
717 struct line {
718         enum line_type type;
720         /* State flags */
721         unsigned int selected:1;
723         void *data;             /* User data */
724 };
727 /*
728  * Keys
729  */
731 struct keybinding {
732         int alias;
733         enum request request;
734         struct keybinding *next;
735 };
737 static struct keybinding default_keybindings[] = {
738         /* View switching */
739         { 'm',          REQ_VIEW_MAIN },
740         { 'd',          REQ_VIEW_DIFF },
741         { 'l',          REQ_VIEW_LOG },
742         { 't',          REQ_VIEW_TREE },
743         { 'f',          REQ_VIEW_BLOB },
744         { 'p',          REQ_VIEW_PAGER },
745         { 'h',          REQ_VIEW_HELP },
746         { 'S',          REQ_VIEW_STATUS },
747         { 'c',          REQ_VIEW_STAGE },
749         /* View manipulation */
750         { 'q',          REQ_VIEW_CLOSE },
751         { KEY_TAB,      REQ_VIEW_NEXT },
752         { KEY_RETURN,   REQ_ENTER },
753         { KEY_UP,       REQ_PREVIOUS },
754         { KEY_DOWN,     REQ_NEXT },
755         { 'R',          REQ_REFRESH },
757         /* Cursor navigation */
758         { 'k',          REQ_MOVE_UP },
759         { 'j',          REQ_MOVE_DOWN },
760         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
761         { KEY_END,      REQ_MOVE_LAST_LINE },
762         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
763         { ' ',          REQ_MOVE_PAGE_DOWN },
764         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
765         { 'b',          REQ_MOVE_PAGE_UP },
766         { '-',          REQ_MOVE_PAGE_UP },
768         /* Scrolling */
769         { KEY_IC,       REQ_SCROLL_LINE_UP },
770         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
771         { 'w',          REQ_SCROLL_PAGE_UP },
772         { 's',          REQ_SCROLL_PAGE_DOWN },
774         /* Searching */
775         { '/',          REQ_SEARCH },
776         { '?',          REQ_SEARCH_BACK },
777         { 'n',          REQ_FIND_NEXT },
778         { 'N',          REQ_FIND_PREV },
780         /* Misc */
781         { 'Q',          REQ_QUIT },
782         { 'z',          REQ_STOP_LOADING },
783         { 'v',          REQ_SHOW_VERSION },
784         { 'r',          REQ_SCREEN_REDRAW },
785         { '.',          REQ_TOGGLE_LINENO },
786         { 'g',          REQ_TOGGLE_REV_GRAPH },
787         { ':',          REQ_PROMPT },
788         { 'u',          REQ_STATUS_UPDATE },
789         { 'e',          REQ_EDIT },
790         { 'C',          REQ_CHERRY_PICK },
792         /* Using the ncurses SIGWINCH handler. */
793         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
794 };
796 #define KEYMAP_INFO \
797         KEYMAP_(GENERIC), \
798         KEYMAP_(MAIN), \
799         KEYMAP_(DIFF), \
800         KEYMAP_(LOG), \
801         KEYMAP_(TREE), \
802         KEYMAP_(BLOB), \
803         KEYMAP_(PAGER), \
804         KEYMAP_(HELP), \
805         KEYMAP_(STATUS), \
806         KEYMAP_(STAGE)
808 enum keymap {
809 #define KEYMAP_(name) KEYMAP_##name
810         KEYMAP_INFO
811 #undef  KEYMAP_
812 };
814 static struct int_map keymap_table[] = {
815 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
816         KEYMAP_INFO
817 #undef  KEYMAP_
818 };
820 #define set_keymap(map, name) \
821         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
823 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
825 static void
826 add_keybinding(enum keymap keymap, enum request request, int key)
828         struct keybinding *keybinding;
830         keybinding = calloc(1, sizeof(*keybinding));
831         if (!keybinding)
832                 die("Failed to allocate keybinding");
834         keybinding->alias = key;
835         keybinding->request = request;
836         keybinding->next = keybindings[keymap];
837         keybindings[keymap] = keybinding;
840 /* Looks for a key binding first in the given map, then in the generic map, and
841  * lastly in the default keybindings. */
842 static enum request
843 get_keybinding(enum keymap keymap, int key)
845         struct keybinding *kbd;
846         int i;
848         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
849                 if (kbd->alias == key)
850                         return kbd->request;
852         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
853                 if (kbd->alias == key)
854                         return kbd->request;
856         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
857                 if (default_keybindings[i].alias == key)
858                         return default_keybindings[i].request;
860         return (enum request) key;
864 struct key {
865         char *name;
866         int value;
867 };
869 static struct key key_table[] = {
870         { "Enter",      KEY_RETURN },
871         { "Space",      ' ' },
872         { "Backspace",  KEY_BACKSPACE },
873         { "Tab",        KEY_TAB },
874         { "Escape",     KEY_ESC },
875         { "Left",       KEY_LEFT },
876         { "Right",      KEY_RIGHT },
877         { "Up",         KEY_UP },
878         { "Down",       KEY_DOWN },
879         { "Insert",     KEY_IC },
880         { "Delete",     KEY_DC },
881         { "Hash",       '#' },
882         { "Home",       KEY_HOME },
883         { "End",        KEY_END },
884         { "PageUp",     KEY_PPAGE },
885         { "PageDown",   KEY_NPAGE },
886         { "F1",         KEY_F(1) },
887         { "F2",         KEY_F(2) },
888         { "F3",         KEY_F(3) },
889         { "F4",         KEY_F(4) },
890         { "F5",         KEY_F(5) },
891         { "F6",         KEY_F(6) },
892         { "F7",         KEY_F(7) },
893         { "F8",         KEY_F(8) },
894         { "F9",         KEY_F(9) },
895         { "F10",        KEY_F(10) },
896         { "F11",        KEY_F(11) },
897         { "F12",        KEY_F(12) },
898 };
900 static int
901 get_key_value(const char *name)
903         int i;
905         for (i = 0; i < ARRAY_SIZE(key_table); i++)
906                 if (!strcasecmp(key_table[i].name, name))
907                         return key_table[i].value;
909         if (strlen(name) == 1 && isprint(*name))
910                 return (int) *name;
912         return ERR;
915 static char *
916 get_key(enum request request)
918         static char buf[BUFSIZ];
919         static char key_char[] = "'X'";
920         size_t pos = 0;
921         char *sep = "";
922         int i;
924         buf[pos] = 0;
926         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
927                 struct keybinding *keybinding = &default_keybindings[i];
928                 char *seq = NULL;
929                 int key;
931                 if (keybinding->request != request)
932                         continue;
934                 for (key = 0; key < ARRAY_SIZE(key_table); key++)
935                         if (key_table[key].value == keybinding->alias)
936                                 seq = key_table[key].name;
938                 if (seq == NULL &&
939                     keybinding->alias < 127 &&
940                     isprint(keybinding->alias)) {
941                         key_char[1] = (char) keybinding->alias;
942                         seq = key_char;
943                 }
945                 if (!seq)
946                         seq = "'?'";
948                 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
949                         return "Too many keybindings!";
950                 sep = ", ";
951         }
953         return buf;
957 /*
958  * User config file handling.
959  */
961 static struct int_map color_map[] = {
962 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
963         COLOR_MAP(DEFAULT),
964         COLOR_MAP(BLACK),
965         COLOR_MAP(BLUE),
966         COLOR_MAP(CYAN),
967         COLOR_MAP(GREEN),
968         COLOR_MAP(MAGENTA),
969         COLOR_MAP(RED),
970         COLOR_MAP(WHITE),
971         COLOR_MAP(YELLOW),
972 };
974 #define set_color(color, name) \
975         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
977 static struct int_map attr_map[] = {
978 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
979         ATTR_MAP(NORMAL),
980         ATTR_MAP(BLINK),
981         ATTR_MAP(BOLD),
982         ATTR_MAP(DIM),
983         ATTR_MAP(REVERSE),
984         ATTR_MAP(STANDOUT),
985         ATTR_MAP(UNDERLINE),
986 };
988 #define set_attribute(attr, name) \
989         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
991 static int   config_lineno;
992 static bool  config_errors;
993 static char *config_msg;
995 /* Wants: object fgcolor bgcolor [attr] */
996 static int
997 option_color_command(int argc, char *argv[])
999         struct line_info *info;
1001         if (argc != 3 && argc != 4) {
1002                 config_msg = "Wrong number of arguments given to color command";
1003                 return ERR;
1004         }
1006         info = get_line_info(argv[0], strlen(argv[0]));
1007         if (!info) {
1008                 config_msg = "Unknown color name";
1009                 return ERR;
1010         }
1012         if (set_color(&info->fg, argv[1]) == ERR ||
1013             set_color(&info->bg, argv[2]) == ERR) {
1014                 config_msg = "Unknown color";
1015                 return ERR;
1016         }
1018         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1019                 config_msg = "Unknown attribute";
1020                 return ERR;
1021         }
1023         return OK;
1026 /* Wants: name = value */
1027 static int
1028 option_set_command(int argc, char *argv[])
1030         if (argc != 3) {
1031                 config_msg = "Wrong number of arguments given to set command";
1032                 return ERR;
1033         }
1035         if (strcmp(argv[1], "=")) {
1036                 config_msg = "No value assigned";
1037                 return ERR;
1038         }
1040         if (!strcmp(argv[0], "show-rev-graph")) {
1041                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1042                                  !strcmp(argv[2], "true") ||
1043                                  !strcmp(argv[2], "yes"));
1044                 return OK;
1045         }
1047         if (!strcmp(argv[0], "line-number-interval")) {
1048                 opt_num_interval = atoi(argv[2]);
1049                 return OK;
1050         }
1052         if (!strcmp(argv[0], "tab-size")) {
1053                 opt_tab_size = atoi(argv[2]);
1054                 return OK;
1055         }
1057         if (!strcmp(argv[0], "commit-encoding")) {
1058                 char *arg = argv[2];
1059                 int delimiter = *arg;
1060                 int i;
1062                 switch (delimiter) {
1063                 case '"':
1064                 case '\'':
1065                         for (arg++, i = 0; arg[i]; i++)
1066                                 if (arg[i] == delimiter) {
1067                                         arg[i] = 0;
1068                                         break;
1069                                 }
1070                 default:
1071                         string_ncopy(opt_encoding, arg, strlen(arg));
1072                         return OK;
1073                 }
1074         }
1076         config_msg = "Unknown variable name";
1077         return ERR;
1080 /* Wants: mode request key */
1081 static int
1082 option_bind_command(int argc, char *argv[])
1084         enum request request;
1085         int keymap;
1086         int key;
1088         if (argc != 3) {
1089                 config_msg = "Wrong number of arguments given to bind command";
1090                 return ERR;
1091         }
1093         if (set_keymap(&keymap, argv[0]) == ERR) {
1094                 config_msg = "Unknown key map";
1095                 return ERR;
1096         }
1098         key = get_key_value(argv[1]);
1099         if (key == ERR) {
1100                 config_msg = "Unknown key";
1101                 return ERR;
1102         }
1104         request = get_request(argv[2]);
1105         if (request == REQ_UNKNOWN) {
1106                 config_msg = "Unknown request name";
1107                 return ERR;
1108         }
1110         add_keybinding(keymap, request, key);
1112         return OK;
1115 static int
1116 set_option(char *opt, char *value)
1118         char *argv[16];
1119         int valuelen;
1120         int argc = 0;
1122         /* Tokenize */
1123         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1124                 argv[argc++] = value;
1126                 value += valuelen;
1127                 if (!*value)
1128                         break;
1130                 *value++ = 0;
1131                 while (isspace(*value))
1132                         value++;
1133         }
1135         if (!strcmp(opt, "color"))
1136                 return option_color_command(argc, argv);
1138         if (!strcmp(opt, "set"))
1139                 return option_set_command(argc, argv);
1141         if (!strcmp(opt, "bind"))
1142                 return option_bind_command(argc, argv);
1144         config_msg = "Unknown option command";
1145         return ERR;
1148 static int
1149 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1151         int status = OK;
1153         config_lineno++;
1154         config_msg = "Internal error";
1156         /* Check for comment markers, since read_properties() will
1157          * only ensure opt and value are split at first " \t". */
1158         optlen = strcspn(opt, "#");
1159         if (optlen == 0)
1160                 return OK;
1162         if (opt[optlen] != 0) {
1163                 config_msg = "No option value";
1164                 status = ERR;
1166         }  else {
1167                 /* Look for comment endings in the value. */
1168                 size_t len = strcspn(value, "#");
1170                 if (len < valuelen) {
1171                         valuelen = len;
1172                         value[valuelen] = 0;
1173                 }
1175                 status = set_option(opt, value);
1176         }
1178         if (status == ERR) {
1179                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1180                         config_lineno, (int) optlen, opt, config_msg);
1181                 config_errors = TRUE;
1182         }
1184         /* Always keep going if errors are encountered. */
1185         return OK;
1188 static int
1189 load_options(void)
1191         char *home = getenv("HOME");
1192         char buf[SIZEOF_STR];
1193         FILE *file;
1195         config_lineno = 0;
1196         config_errors = FALSE;
1198         if (!home || !string_format(buf, "%s/.tigrc", home))
1199                 return ERR;
1201         /* It's ok that the file doesn't exist. */
1202         file = fopen(buf, "r");
1203         if (!file)
1204                 return OK;
1206         if (read_properties(file, " \t", read_option) == ERR ||
1207             config_errors == TRUE)
1208                 fprintf(stderr, "Errors while loading %s.\n", buf);
1210         return OK;
1214 /*
1215  * The viewer
1216  */
1218 struct view;
1219 struct view_ops;
1221 /* The display array of active views and the index of the current view. */
1222 static struct view *display[2];
1223 static unsigned int current_view;
1225 /* Reading from the prompt? */
1226 static bool input_mode = FALSE;
1228 #define foreach_displayed_view(view, i) \
1229         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1231 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1233 /* Current head and commit ID */
1234 static char ref_blob[SIZEOF_REF]        = "";
1235 static char ref_commit[SIZEOF_REF]      = "HEAD";
1236 static char ref_head[SIZEOF_REF]        = "HEAD";
1238 struct view {
1239         const char *name;       /* View name */
1240         const char *cmd_fmt;    /* Default command line format */
1241         const char *cmd_env;    /* Command line set via environment */
1242         const char *id;         /* Points to either of ref_{head,commit,blob} */
1244         struct view_ops *ops;   /* View operations */
1246         enum keymap keymap;     /* What keymap does this view have */
1248         char cmd[SIZEOF_STR];   /* Command buffer */
1249         char ref[SIZEOF_REF];   /* Hovered commit reference */
1250         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1252         int height, width;      /* The width and height of the main window */
1253         WINDOW *win;            /* The main window */
1254         WINDOW *title;          /* The title window living below the main window */
1256         /* Navigation */
1257         unsigned long offset;   /* Offset of the window top */
1258         unsigned long lineno;   /* Current line number */
1260         /* Searching */
1261         char grep[SIZEOF_STR];  /* Search string */
1262         regex_t *regex;         /* Pre-compiled regex */
1264         /* If non-NULL, points to the view that opened this view. If this view
1265          * is closed tig will switch back to the parent view. */
1266         struct view *parent;
1268         /* Buffering */
1269         unsigned long lines;    /* Total number of lines */
1270         struct line *line;      /* Line index */
1271         unsigned long line_size;/* Total number of allocated lines */
1272         unsigned int digits;    /* Number of digits in the lines member. */
1274         /* Loading */
1275         FILE *pipe;
1276         time_t start_time;
1277 };
1279 struct view_ops {
1280         /* What type of content being displayed. Used in the title bar. */
1281         const char *type;
1282         /* Open and reads in all view content. */
1283         bool (*open)(struct view *view);
1284         /* Read one line; updates view->line. */
1285         bool (*read)(struct view *view, char *data);
1286         /* Draw one line; @lineno must be < view->height. */
1287         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1288         /* Depending on view handle a special requests. */
1289         enum request (*request)(struct view *view, enum request request, struct line *line);
1290         /* Search for regex in a line. */
1291         bool (*grep)(struct view *view, struct line *line);
1292         /* Select line */
1293         void (*select)(struct view *view, struct line *line);
1294 };
1296 static struct view_ops pager_ops;
1297 static struct view_ops main_ops;
1298 static struct view_ops tree_ops;
1299 static struct view_ops blob_ops;
1300 static struct view_ops help_ops;
1301 static struct view_ops status_ops;
1302 static struct view_ops stage_ops;
1304 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1305         { name, cmd, #env, ref, ops, map}
1307 #define VIEW_(id, name, ops, ref) \
1308         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1311 static struct view views[] = {
1312         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1313         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1314         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1315         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1316         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1317         VIEW_(HELP,   "help",   &help_ops,   ""),
1318         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1319         VIEW_(STATUS, "status", &status_ops, ""),
1320         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1321 };
1323 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1325 #define foreach_view(view, i) \
1326         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1328 #define view_is_displayed(view) \
1329         (view == display[0] || view == display[1])
1331 static bool
1332 draw_view_line(struct view *view, unsigned int lineno)
1334         struct line *line;
1335         bool selected = (view->offset + lineno == view->lineno);
1336         bool draw_ok;
1338         assert(view_is_displayed(view));
1340         if (view->offset + lineno >= view->lines)
1341                 return FALSE;
1343         line = &view->line[view->offset + lineno];
1345         if (selected) {
1346                 line->selected = TRUE;
1347                 view->ops->select(view, line);
1348         } else if (line->selected) {
1349                 line->selected = FALSE;
1350                 wmove(view->win, lineno, 0);
1351                 wclrtoeol(view->win);
1352         }
1354         scrollok(view->win, FALSE);
1355         draw_ok = view->ops->draw(view, line, lineno, selected);
1356         scrollok(view->win, TRUE);
1358         return draw_ok;
1361 static void
1362 redraw_view_from(struct view *view, int lineno)
1364         assert(0 <= lineno && lineno < view->height);
1366         for (; lineno < view->height; lineno++) {
1367                 if (!draw_view_line(view, lineno))
1368                         break;
1369         }
1371         redrawwin(view->win);
1372         if (input_mode)
1373                 wnoutrefresh(view->win);
1374         else
1375                 wrefresh(view->win);
1378 static void
1379 redraw_view(struct view *view)
1381         wclear(view->win);
1382         redraw_view_from(view, 0);
1386 static void
1387 update_view_title(struct view *view)
1389         char buf[SIZEOF_STR];
1390         char state[SIZEOF_STR];
1391         size_t bufpos = 0, statelen = 0;
1393         assert(view_is_displayed(view));
1395         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1396                 unsigned int view_lines = view->offset + view->height;
1397                 unsigned int lines = view->lines
1398                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1399                                    : 0;
1401                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1402                                    view->ops->type,
1403                                    view->lineno + 1,
1404                                    view->lines,
1405                                    lines);
1407                 if (view->pipe) {
1408                         time_t secs = time(NULL) - view->start_time;
1410                         /* Three git seconds are a long time ... */
1411                         if (secs > 2)
1412                                 string_format_from(state, &statelen, " %lds", secs);
1413                 }
1414         }
1416         string_format_from(buf, &bufpos, "[%s]", view->name);
1417         if (*view->ref && bufpos < view->width) {
1418                 size_t refsize = strlen(view->ref);
1419                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1421                 if (minsize < view->width)
1422                         refsize = view->width - minsize + 7;
1423                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1424         }
1426         if (statelen && bufpos < view->width) {
1427                 string_format_from(buf, &bufpos, " %s", state);
1428         }
1430         if (view == display[current_view])
1431                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1432         else
1433                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1435         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1436         wclrtoeol(view->title);
1437         wmove(view->title, 0, view->width - 1);
1439         if (input_mode)
1440                 wnoutrefresh(view->title);
1441         else
1442                 wrefresh(view->title);
1445 static void
1446 resize_display(void)
1448         int offset, i;
1449         struct view *base = display[0];
1450         struct view *view = display[1] ? display[1] : display[0];
1452         /* Setup window dimensions */
1454         getmaxyx(stdscr, base->height, base->width);
1456         /* Make room for the status window. */
1457         base->height -= 1;
1459         if (view != base) {
1460                 /* Horizontal split. */
1461                 view->width   = base->width;
1462                 view->height  = SCALE_SPLIT_VIEW(base->height);
1463                 base->height -= view->height;
1465                 /* Make room for the title bar. */
1466                 view->height -= 1;
1467         }
1469         /* Make room for the title bar. */
1470         base->height -= 1;
1472         offset = 0;
1474         foreach_displayed_view (view, i) {
1475                 if (!view->win) {
1476                         view->win = newwin(view->height, 0, offset, 0);
1477                         if (!view->win)
1478                                 die("Failed to create %s view", view->name);
1480                         scrollok(view->win, TRUE);
1482                         view->title = newwin(1, 0, offset + view->height, 0);
1483                         if (!view->title)
1484                                 die("Failed to create title window");
1486                 } else {
1487                         wresize(view->win, view->height, view->width);
1488                         mvwin(view->win,   offset, 0);
1489                         mvwin(view->title, offset + view->height, 0);
1490                 }
1492                 offset += view->height + 1;
1493         }
1496 static void
1497 redraw_display(void)
1499         struct view *view;
1500         int i;
1502         foreach_displayed_view (view, i) {
1503                 redraw_view(view);
1504                 update_view_title(view);
1505         }
1508 static void
1509 update_display_cursor(struct view *view)
1511         /* Move the cursor to the right-most column of the cursor line.
1512          *
1513          * XXX: This could turn out to be a bit expensive, but it ensures that
1514          * the cursor does not jump around. */
1515         if (view->lines) {
1516                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1517                 wrefresh(view->win);
1518         }
1521 /*
1522  * Navigation
1523  */
1525 /* Scrolling backend */
1526 static void
1527 do_scroll_view(struct view *view, int lines)
1529         bool redraw_current_line = FALSE;
1531         /* The rendering expects the new offset. */
1532         view->offset += lines;
1534         assert(0 <= view->offset && view->offset < view->lines);
1535         assert(lines);
1537         /* Move current line into the view. */
1538         if (view->lineno < view->offset) {
1539                 view->lineno = view->offset;
1540                 redraw_current_line = TRUE;
1541         } else if (view->lineno >= view->offset + view->height) {
1542                 view->lineno = view->offset + view->height - 1;
1543                 redraw_current_line = TRUE;
1544         }
1546         assert(view->offset <= view->lineno && view->lineno < view->lines);
1548         /* Redraw the whole screen if scrolling is pointless. */
1549         if (view->height < ABS(lines)) {
1550                 redraw_view(view);
1552         } else {
1553                 int line = lines > 0 ? view->height - lines : 0;
1554                 int end = line + ABS(lines);
1556                 wscrl(view->win, lines);
1558                 for (; line < end; line++) {
1559                         if (!draw_view_line(view, line))
1560                                 break;
1561                 }
1563                 if (redraw_current_line)
1564                         draw_view_line(view, view->lineno - view->offset);
1565         }
1567         redrawwin(view->win);
1568         wrefresh(view->win);
1569         report("");
1572 /* Scroll frontend */
1573 static void
1574 scroll_view(struct view *view, enum request request)
1576         int lines = 1;
1578         assert(view_is_displayed(view));
1580         switch (request) {
1581         case REQ_SCROLL_PAGE_DOWN:
1582                 lines = view->height;
1583         case REQ_SCROLL_LINE_DOWN:
1584                 if (view->offset + lines > view->lines)
1585                         lines = view->lines - view->offset;
1587                 if (lines == 0 || view->offset + view->height >= view->lines) {
1588                         report("Cannot scroll beyond the last line");
1589                         return;
1590                 }
1591                 break;
1593         case REQ_SCROLL_PAGE_UP:
1594                 lines = view->height;
1595         case REQ_SCROLL_LINE_UP:
1596                 if (lines > view->offset)
1597                         lines = view->offset;
1599                 if (lines == 0) {
1600                         report("Cannot scroll beyond the first line");
1601                         return;
1602                 }
1604                 lines = -lines;
1605                 break;
1607         default:
1608                 die("request %d not handled in switch", request);
1609         }
1611         do_scroll_view(view, lines);
1614 /* Cursor moving */
1615 static void
1616 move_view(struct view *view, enum request request)
1618         int scroll_steps = 0;
1619         int steps;
1621         switch (request) {
1622         case REQ_MOVE_FIRST_LINE:
1623                 steps = -view->lineno;
1624                 break;
1626         case REQ_MOVE_LAST_LINE:
1627                 steps = view->lines - view->lineno - 1;
1628                 break;
1630         case REQ_MOVE_PAGE_UP:
1631                 steps = view->height > view->lineno
1632                       ? -view->lineno : -view->height;
1633                 break;
1635         case REQ_MOVE_PAGE_DOWN:
1636                 steps = view->lineno + view->height >= view->lines
1637                       ? view->lines - view->lineno - 1 : view->height;
1638                 break;
1640         case REQ_MOVE_UP:
1641                 steps = -1;
1642                 break;
1644         case REQ_MOVE_DOWN:
1645                 steps = 1;
1646                 break;
1648         default:
1649                 die("request %d not handled in switch", request);
1650         }
1652         if (steps <= 0 && view->lineno == 0) {
1653                 report("Cannot move beyond the first line");
1654                 return;
1656         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1657                 report("Cannot move beyond the last line");
1658                 return;
1659         }
1661         /* Move the current line */
1662         view->lineno += steps;
1663         assert(0 <= view->lineno && view->lineno < view->lines);
1665         /* Check whether the view needs to be scrolled */
1666         if (view->lineno < view->offset ||
1667             view->lineno >= view->offset + view->height) {
1668                 scroll_steps = steps;
1669                 if (steps < 0 && -steps > view->offset) {
1670                         scroll_steps = -view->offset;
1672                 } else if (steps > 0) {
1673                         if (view->lineno == view->lines - 1 &&
1674                             view->lines > view->height) {
1675                                 scroll_steps = view->lines - view->offset - 1;
1676                                 if (scroll_steps >= view->height)
1677                                         scroll_steps -= view->height - 1;
1678                         }
1679                 }
1680         }
1682         if (!view_is_displayed(view)) {
1683                 view->offset += scroll_steps;
1684                 assert(0 <= view->offset && view->offset < view->lines);
1685                 view->ops->select(view, &view->line[view->lineno]);
1686                 return;
1687         }
1689         /* Repaint the old "current" line if we be scrolling */
1690         if (ABS(steps) < view->height)
1691                 draw_view_line(view, view->lineno - steps - view->offset);
1693         if (scroll_steps) {
1694                 do_scroll_view(view, scroll_steps);
1695                 return;
1696         }
1698         /* Draw the current line */
1699         draw_view_line(view, view->lineno - view->offset);
1701         redrawwin(view->win);
1702         wrefresh(view->win);
1703         report("");
1707 /*
1708  * Searching
1709  */
1711 static void search_view(struct view *view, enum request request);
1713 static bool
1714 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1716         assert(view_is_displayed(view));
1718         if (!view->ops->grep(view, line))
1719                 return FALSE;
1721         if (lineno - view->offset >= view->height) {
1722                 view->offset = lineno;
1723                 view->lineno = lineno;
1724                 redraw_view(view);
1726         } else {
1727                 unsigned long old_lineno = view->lineno - view->offset;
1729                 view->lineno = lineno;
1730                 draw_view_line(view, old_lineno);
1732                 draw_view_line(view, view->lineno - view->offset);
1733                 redrawwin(view->win);
1734                 wrefresh(view->win);
1735         }
1737         report("Line %ld matches '%s'", lineno + 1, view->grep);
1738         return TRUE;
1741 static void
1742 find_next(struct view *view, enum request request)
1744         unsigned long lineno = view->lineno;
1745         int direction;
1747         if (!*view->grep) {
1748                 if (!*opt_search)
1749                         report("No previous search");
1750                 else
1751                         search_view(view, request);
1752                 return;
1753         }
1755         switch (request) {
1756         case REQ_SEARCH:
1757         case REQ_FIND_NEXT:
1758                 direction = 1;
1759                 break;
1761         case REQ_SEARCH_BACK:
1762         case REQ_FIND_PREV:
1763                 direction = -1;
1764                 break;
1766         default:
1767                 return;
1768         }
1770         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1771                 lineno += direction;
1773         /* Note, lineno is unsigned long so will wrap around in which case it
1774          * will become bigger than view->lines. */
1775         for (; lineno < view->lines; lineno += direction) {
1776                 struct line *line = &view->line[lineno];
1778                 if (find_next_line(view, lineno, line))
1779                         return;
1780         }
1782         report("No match found for '%s'", view->grep);
1785 static void
1786 search_view(struct view *view, enum request request)
1788         int regex_err;
1790         if (view->regex) {
1791                 regfree(view->regex);
1792                 *view->grep = 0;
1793         } else {
1794                 view->regex = calloc(1, sizeof(*view->regex));
1795                 if (!view->regex)
1796                         return;
1797         }
1799         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1800         if (regex_err != 0) {
1801                 char buf[SIZEOF_STR] = "unknown error";
1803                 regerror(regex_err, view->regex, buf, sizeof(buf));
1804                 report("Search failed: %s", buf);
1805                 return;
1806         }
1808         string_copy(view->grep, opt_search);
1810         find_next(view, request);
1813 /*
1814  * Incremental updating
1815  */
1817 static void
1818 end_update(struct view *view)
1820         if (!view->pipe)
1821                 return;
1822         set_nonblocking_input(FALSE);
1823         if (view->pipe == stdin)
1824                 fclose(view->pipe);
1825         else
1826                 pclose(view->pipe);
1827         view->pipe = NULL;
1830 static bool
1831 begin_update(struct view *view)
1833         if (view->pipe)
1834                 end_update(view);
1836         if (opt_cmd[0]) {
1837                 string_copy(view->cmd, opt_cmd);
1838                 opt_cmd[0] = 0;
1839                 /* When running random commands, initially show the
1840                  * command in the title. However, it maybe later be
1841                  * overwritten if a commit line is selected. */
1842                 if (view == VIEW(REQ_VIEW_PAGER))
1843                         string_copy(view->ref, view->cmd);
1844                 else
1845                         view->ref[0] = 0;
1847         } else if (view == VIEW(REQ_VIEW_TREE)) {
1848                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1849                 char path[SIZEOF_STR];
1851                 if (strcmp(view->vid, view->id))
1852                         opt_path[0] = path[0] = 0;
1853                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1854                         return FALSE;
1856                 if (!string_format(view->cmd, format, view->id, path))
1857                         return FALSE;
1859         } else {
1860                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1861                 const char *id = view->id;
1863                 if (!string_format(view->cmd, format, id, id, id, id, id))
1864                         return FALSE;
1866                 /* Put the current ref_* value to the view title ref
1867                  * member. This is needed by the blob view. Most other
1868                  * views sets it automatically after loading because the
1869                  * first line is a commit line. */
1870                 string_copy_rev(view->ref, view->id);
1871         }
1873         /* Special case for the pager view. */
1874         if (opt_pipe) {
1875                 view->pipe = opt_pipe;
1876                 opt_pipe = NULL;
1877         } else {
1878                 view->pipe = popen(view->cmd, "r");
1879         }
1881         if (!view->pipe)
1882                 return FALSE;
1884         set_nonblocking_input(TRUE);
1886         view->offset = 0;
1887         view->lines  = 0;
1888         view->lineno = 0;
1889         string_copy_rev(view->vid, view->id);
1891         if (view->line) {
1892                 int i;
1894                 for (i = 0; i < view->lines; i++)
1895                         if (view->line[i].data)
1896                                 free(view->line[i].data);
1898                 free(view->line);
1899                 view->line = NULL;
1900         }
1902         view->start_time = time(NULL);
1904         return TRUE;
1907 static struct line *
1908 realloc_lines(struct view *view, size_t line_size)
1910         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1912         if (!tmp)
1913                 return NULL;
1915         view->line = tmp;
1916         view->line_size = line_size;
1917         return view->line;
1920 static bool
1921 update_view(struct view *view)
1923         char in_buffer[BUFSIZ];
1924         char out_buffer[BUFSIZ * 2];
1925         char *line;
1926         /* The number of lines to read. If too low it will cause too much
1927          * redrawing (and possible flickering), if too high responsiveness
1928          * will suffer. */
1929         unsigned long lines = view->height;
1930         int redraw_from = -1;
1932         if (!view->pipe)
1933                 return TRUE;
1935         /* Only redraw if lines are visible. */
1936         if (view->offset + view->height >= view->lines)
1937                 redraw_from = view->lines - view->offset;
1939         /* FIXME: This is probably not perfect for backgrounded views. */
1940         if (!realloc_lines(view, view->lines + lines))
1941                 goto alloc_error;
1943         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1944                 size_t linelen = strlen(line);
1946                 if (linelen)
1947                         line[linelen - 1] = 0;
1949                 if (opt_iconv != ICONV_NONE) {
1950                         ICONV_CONST char *inbuf = line;
1951                         size_t inlen = linelen;
1953                         char *outbuf = out_buffer;
1954                         size_t outlen = sizeof(out_buffer);
1956                         size_t ret;
1958                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1959                         if (ret != (size_t) -1) {
1960                                 line = out_buffer;
1961                                 linelen = strlen(out_buffer);
1962                         }
1963                 }
1965                 if (!view->ops->read(view, line))
1966                         goto alloc_error;
1968                 if (lines-- == 1)
1969                         break;
1970         }
1972         {
1973                 int digits;
1975                 lines = view->lines;
1976                 for (digits = 0; lines; digits++)
1977                         lines /= 10;
1979                 /* Keep the displayed view in sync with line number scaling. */
1980                 if (digits != view->digits) {
1981                         view->digits = digits;
1982                         redraw_from = 0;
1983                 }
1984         }
1986         if (!view_is_displayed(view))
1987                 goto check_pipe;
1989         if (view == VIEW(REQ_VIEW_TREE)) {
1990                 /* Clear the view and redraw everything since the tree sorting
1991                  * might have rearranged things. */
1992                 redraw_view(view);
1994         } else if (redraw_from >= 0) {
1995                 /* If this is an incremental update, redraw the previous line
1996                  * since for commits some members could have changed when
1997                  * loading the main view. */
1998                 if (redraw_from > 0)
1999                         redraw_from--;
2001                 /* Since revision graph visualization requires knowledge
2002                  * about the parent commit, it causes a further one-off
2003                  * needed to be redrawn for incremental updates. */
2004                 if (redraw_from > 0 && opt_rev_graph)
2005                         redraw_from--;
2007                 /* Incrementally draw avoids flickering. */
2008                 redraw_view_from(view, redraw_from);
2009         }
2011         /* Update the title _after_ the redraw so that if the redraw picks up a
2012          * commit reference in view->ref it'll be available here. */
2013         update_view_title(view);
2015 check_pipe:
2016         if (ferror(view->pipe)) {
2017                 report("Failed to read: %s", strerror(errno));
2018                 goto end;
2020         } else if (feof(view->pipe)) {
2021                 report("");
2022                 goto end;
2023         }
2025         return TRUE;
2027 alloc_error:
2028         report("Allocation failure");
2030 end:
2031         view->ops->read(view, NULL);
2032         end_update(view);
2033         return FALSE;
2036 static struct line *
2037 add_line_data(struct view *view, void *data, enum line_type type)
2039         struct line *line = &view->line[view->lines++];
2041         memset(line, 0, sizeof(*line));
2042         line->type = type;
2043         line->data = data;
2045         return line;
2048 static struct line *
2049 add_line_text(struct view *view, char *data, enum line_type type)
2051         if (data)
2052                 data = strdup(data);
2054         return data ? add_line_data(view, data, type) : NULL;
2058 /*
2059  * View opening
2060  */
2062 enum open_flags {
2063         OPEN_DEFAULT = 0,       /* Use default view switching. */
2064         OPEN_SPLIT = 1,         /* Split current view. */
2065         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2066         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2067 };
2069 static void
2070 open_view(struct view *prev, enum request request, enum open_flags flags)
2072         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2073         bool split = !!(flags & OPEN_SPLIT);
2074         bool reload = !!(flags & OPEN_RELOAD);
2075         struct view *view = VIEW(request);
2076         int nviews = displayed_views();
2077         struct view *base_view = display[0];
2079         if (view == prev && nviews == 1 && !reload) {
2080                 report("Already in %s view", view->name);
2081                 return;
2082         }
2084         if (view->ops->open) {
2085                 if (!view->ops->open(view)) {
2086                         report("Failed to load %s view", view->name);
2087                         return;
2088                 }
2090         } else if ((reload || strcmp(view->vid, view->id)) &&
2091                    !begin_update(view)) {
2092                 report("Failed to load %s view", view->name);
2093                 return;
2094         }
2096         if (split) {
2097                 display[1] = view;
2098                 if (!backgrounded)
2099                         current_view = 1;
2100         } else {
2101                 /* Maximize the current view. */
2102                 memset(display, 0, sizeof(display));
2103                 current_view = 0;
2104                 display[current_view] = view;
2105         }
2107         /* Resize the view when switching between split- and full-screen,
2108          * or when switching between two different full-screen views. */
2109         if (nviews != displayed_views() ||
2110             (nviews == 1 && base_view != display[0]))
2111                 resize_display();
2113         if (split && prev->lineno - prev->offset >= prev->height) {
2114                 /* Take the title line into account. */
2115                 int lines = prev->lineno - prev->offset - prev->height + 1;
2117                 /* Scroll the view that was split if the current line is
2118                  * outside the new limited view. */
2119                 do_scroll_view(prev, lines);
2120         }
2122         if (prev && view != prev) {
2123                 if (split && !backgrounded) {
2124                         /* "Blur" the previous view. */
2125                         update_view_title(prev);
2126                 }
2128                 view->parent = prev;
2129         }
2131         if (view->pipe && view->lines == 0) {
2132                 /* Clear the old view and let the incremental updating refill
2133                  * the screen. */
2134                 wclear(view->win);
2135                 report("");
2136         } else {
2137                 redraw_view(view);
2138                 report("");
2139         }
2141         /* If the view is backgrounded the above calls to report()
2142          * won't redraw the view title. */
2143         if (backgrounded)
2144                 update_view_title(view);
2147 static void
2148 open_editor(bool from_root, char *file)
2150         char cmd[SIZEOF_STR];
2151         char file_sq[SIZEOF_STR];
2152         char *editor;
2153         char *prefix = from_root ? opt_cdup : "";
2155         editor = getenv("GIT_EDITOR");
2156         if (!editor && *opt_editor)
2157                 editor = opt_editor;
2158         if (!editor)
2159                 editor = getenv("VISUAL");
2160         if (!editor)
2161                 editor = getenv("EDITOR");
2162         if (!editor)
2163                 editor = "vi";
2165         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2166             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2167                 def_prog_mode();           /* save current tty modes */
2168                 endwin();                  /* restore original tty modes */
2169                 system(cmd);
2170                 reset_prog_mode();
2171                 redraw_display();
2172         }
2175 /*
2176  * User request switch noodle
2177  */
2179 static int
2180 view_driver(struct view *view, enum request request)
2182         int i;
2184         if (request == REQ_NONE) {
2185                 doupdate();
2186                 return TRUE;
2187         }
2189         if (view && view->lines) {
2190                 request = view->ops->request(view, request, &view->line[view->lineno]);
2191                 if (request == REQ_NONE)
2192                         return TRUE;
2193         }
2195         switch (request) {
2196         case REQ_MOVE_UP:
2197         case REQ_MOVE_DOWN:
2198         case REQ_MOVE_PAGE_UP:
2199         case REQ_MOVE_PAGE_DOWN:
2200         case REQ_MOVE_FIRST_LINE:
2201         case REQ_MOVE_LAST_LINE:
2202                 move_view(view, request);
2203                 break;
2205         case REQ_SCROLL_LINE_DOWN:
2206         case REQ_SCROLL_LINE_UP:
2207         case REQ_SCROLL_PAGE_DOWN:
2208         case REQ_SCROLL_PAGE_UP:
2209                 scroll_view(view, request);
2210                 break;
2212         case REQ_VIEW_BLOB:
2213                 if (!ref_blob[0]) {
2214                         report("No file chosen, press %s to open tree view",
2215                                get_key(REQ_VIEW_TREE));
2216                         break;
2217                 }
2218                 open_view(view, request, OPEN_DEFAULT);
2219                 break;
2221         case REQ_VIEW_PAGER:
2222                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2223                         report("No pager content, press %s to run command from prompt",
2224                                get_key(REQ_PROMPT));
2225                         break;
2226                 }
2227                 open_view(view, request, OPEN_DEFAULT);
2228                 break;
2230         case REQ_VIEW_STAGE:
2231                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2232                         report("No stage content, press %s to open the status view and choose file",
2233                                get_key(REQ_VIEW_STATUS));
2234                         break;
2235                 }
2236                 open_view(view, request, OPEN_DEFAULT);
2237                 break;
2239         case REQ_VIEW_MAIN:
2240         case REQ_VIEW_DIFF:
2241         case REQ_VIEW_LOG:
2242         case REQ_VIEW_TREE:
2243         case REQ_VIEW_HELP:
2244         case REQ_VIEW_STATUS:
2245                 open_view(view, request, OPEN_DEFAULT);
2246                 break;
2248         case REQ_NEXT:
2249         case REQ_PREVIOUS:
2250                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2252                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2253                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2254                    (view == VIEW(REQ_VIEW_STAGE) &&
2255                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2256                    (view == VIEW(REQ_VIEW_BLOB) &&
2257                      view->parent == VIEW(REQ_VIEW_TREE))) {
2258                         int line;
2260                         view = view->parent;
2261                         line = view->lineno;
2262                         move_view(view, request);
2263                         if (view_is_displayed(view))
2264                                 update_view_title(view);
2265                         if (line != view->lineno)
2266                                 view->ops->request(view, REQ_ENTER,
2267                                                    &view->line[view->lineno]);
2269                 } else {
2270                         move_view(view, request);
2271                 }
2272                 break;
2274         case REQ_VIEW_NEXT:
2275         {
2276                 int nviews = displayed_views();
2277                 int next_view = (current_view + 1) % nviews;
2279                 if (next_view == current_view) {
2280                         report("Only one view is displayed");
2281                         break;
2282                 }
2284                 current_view = next_view;
2285                 /* Blur out the title of the previous view. */
2286                 update_view_title(view);
2287                 report("");
2288                 break;
2289         }
2290         case REQ_REFRESH:
2291                 report("Refreshing is not yet supported for the %s view", view->name);
2292                 break;
2294         case REQ_TOGGLE_LINENO:
2295                 opt_line_number = !opt_line_number;
2296                 redraw_display();
2297                 break;
2299         case REQ_TOGGLE_REV_GRAPH:
2300                 opt_rev_graph = !opt_rev_graph;
2301                 redraw_display();
2302                 break;
2304         case REQ_PROMPT:
2305                 /* Always reload^Wrerun commands from the prompt. */
2306                 open_view(view, opt_request, OPEN_RELOAD);
2307                 break;
2309         case REQ_SEARCH:
2310         case REQ_SEARCH_BACK:
2311                 search_view(view, request);
2312                 break;
2314         case REQ_FIND_NEXT:
2315         case REQ_FIND_PREV:
2316                 find_next(view, request);
2317                 break;
2319         case REQ_STOP_LOADING:
2320                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2321                         view = &views[i];
2322                         if (view->pipe)
2323                                 report("Stopped loading the %s view", view->name),
2324                         end_update(view);
2325                 }
2326                 break;
2328         case REQ_SHOW_VERSION:
2329                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2330                 return TRUE;
2332         case REQ_SCREEN_RESIZE:
2333                 resize_display();
2334                 /* Fall-through */
2335         case REQ_SCREEN_REDRAW:
2336                 redraw_display();
2337                 break;
2339         case REQ_EDIT:
2340                 report("Nothing to edit");
2341                 break;
2343         case REQ_CHERRY_PICK:
2344                 report("Nothing to cherry-pick");
2345                 break;
2347         case REQ_ENTER:
2348                 report("Nothing to enter");
2349                 break;
2352         case REQ_VIEW_CLOSE:
2353                 /* XXX: Mark closed views by letting view->parent point to the
2354                  * view itself. Parents to closed view should never be
2355                  * followed. */
2356                 if (view->parent &&
2357                     view->parent->parent != view->parent) {
2358                         memset(display, 0, sizeof(display));
2359                         current_view = 0;
2360                         display[current_view] = view->parent;
2361                         view->parent = view;
2362                         resize_display();
2363                         redraw_display();
2364                         break;
2365                 }
2366                 /* Fall-through */
2367         case REQ_QUIT:
2368                 return FALSE;
2370         default:
2371                 /* An unknown key will show most commonly used commands. */
2372                 report("Unknown key, press 'h' for help");
2373                 return TRUE;
2374         }
2376         return TRUE;
2380 /*
2381  * Pager backend
2382  */
2384 static bool
2385 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2387         char *text = line->data;
2388         enum line_type type = line->type;
2389         int textlen = strlen(text);
2390         int attr;
2392         wmove(view->win, lineno, 0);
2394         if (selected) {
2395                 type = LINE_CURSOR;
2396                 wchgat(view->win, -1, 0, type, NULL);
2397         }
2399         attr = get_line_attr(type);
2400         wattrset(view->win, attr);
2402         if (opt_line_number || opt_tab_size < TABSIZE) {
2403                 static char spaces[] = "                    ";
2404                 int col_offset = 0, col = 0;
2406                 if (opt_line_number) {
2407                         unsigned long real_lineno = view->offset + lineno + 1;
2409                         if (real_lineno == 1 ||
2410                             (real_lineno % opt_num_interval) == 0) {
2411                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2413                         } else {
2414                                 waddnstr(view->win, spaces,
2415                                          MIN(view->digits, STRING_SIZE(spaces)));
2416                         }
2417                         waddstr(view->win, ": ");
2418                         col_offset = view->digits + 2;
2419                 }
2421                 while (text && col_offset + col < view->width) {
2422                         int cols_max = view->width - col_offset - col;
2423                         char *pos = text;
2424                         int cols;
2426                         if (*text == '\t') {
2427                                 text++;
2428                                 assert(sizeof(spaces) > TABSIZE);
2429                                 pos = spaces;
2430                                 cols = opt_tab_size - (col % opt_tab_size);
2432                         } else {
2433                                 text = strchr(text, '\t');
2434                                 cols = line ? text - pos : strlen(pos);
2435                         }
2437                         waddnstr(view->win, pos, MIN(cols, cols_max));
2438                         col += cols;
2439                 }
2441         } else {
2442                 int col = 0, pos = 0;
2444                 for (; pos < textlen && col < view->width; pos++, col++)
2445                         if (text[pos] == '\t')
2446                                 col += TABSIZE - (col % TABSIZE) - 1;
2448                 waddnstr(view->win, text, pos);
2449         }
2451         return TRUE;
2454 static bool
2455 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2457         char refbuf[SIZEOF_STR];
2458         char *ref = NULL;
2459         FILE *pipe;
2461         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2462                 return TRUE;
2464         pipe = popen(refbuf, "r");
2465         if (!pipe)
2466                 return TRUE;
2468         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2469                 ref = chomp_string(ref);
2470         pclose(pipe);
2472         if (!ref || !*ref)
2473                 return TRUE;
2475         /* This is the only fatal call, since it can "corrupt" the buffer. */
2476         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2477                 return FALSE;
2479         return TRUE;
2482 static void
2483 add_pager_refs(struct view *view, struct line *line)
2485         char buf[SIZEOF_STR];
2486         char *commit_id = line->data + STRING_SIZE("commit ");
2487         struct ref **refs;
2488         size_t bufpos = 0, refpos = 0;
2489         const char *sep = "Refs: ";
2490         bool is_tag = FALSE;
2492         assert(line->type == LINE_COMMIT);
2494         refs = get_refs(commit_id);
2495         if (!refs) {
2496                 if (view == VIEW(REQ_VIEW_DIFF))
2497                         goto try_add_describe_ref;
2498                 return;
2499         }
2501         do {
2502                 struct ref *ref = refs[refpos];
2503                 char *fmt = ref->tag    ? "%s[%s]" :
2504                             ref->remote ? "%s<%s>" : "%s%s";
2506                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2507                         return;
2508                 sep = ", ";
2509                 if (ref->tag)
2510                         is_tag = TRUE;
2511         } while (refs[refpos++]->next);
2513         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2514 try_add_describe_ref:
2515                 /* Add <tag>-g<commit_id> "fake" reference. */
2516                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2517                         return;
2518         }
2520         if (bufpos == 0)
2521                 return;
2523         if (!realloc_lines(view, view->line_size + 1))
2524                 return;
2526         add_line_text(view, buf, LINE_PP_REFS);
2529 static bool
2530 pager_read(struct view *view, char *data)
2532         struct line *line;
2534         if (!data)
2535                 return TRUE;
2537         line = add_line_text(view, data, get_line_type(data));
2538         if (!line)
2539                 return FALSE;
2541         if (line->type == LINE_COMMIT &&
2542             (view == VIEW(REQ_VIEW_DIFF) ||
2543              view == VIEW(REQ_VIEW_LOG)))
2544                 add_pager_refs(view, line);
2546         return TRUE;
2549 static enum request
2550 pager_request(struct view *view, enum request request, struct line *line)
2552         int split = 0;
2554         if (request != REQ_ENTER)
2555                 return request;
2557         if (line->type == LINE_COMMIT &&
2558            (view == VIEW(REQ_VIEW_LOG) ||
2559             view == VIEW(REQ_VIEW_PAGER))) {
2560                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2561                 split = 1;
2562         }
2564         /* Always scroll the view even if it was split. That way
2565          * you can use Enter to scroll through the log view and
2566          * split open each commit diff. */
2567         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2569         /* FIXME: A minor workaround. Scrolling the view will call report("")
2570          * but if we are scrolling a non-current view this won't properly
2571          * update the view title. */
2572         if (split)
2573                 update_view_title(view);
2575         return REQ_NONE;
2578 static bool
2579 pager_grep(struct view *view, struct line *line)
2581         regmatch_t pmatch;
2582         char *text = line->data;
2584         if (!*text)
2585                 return FALSE;
2587         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2588                 return FALSE;
2590         return TRUE;
2593 static void
2594 pager_select(struct view *view, struct line *line)
2596         if (line->type == LINE_COMMIT) {
2597                 char *text = line->data + STRING_SIZE("commit ");
2599                 if (view != VIEW(REQ_VIEW_PAGER))
2600                         string_copy_rev(view->ref, text);
2601                 string_copy_rev(ref_commit, text);
2602         }
2605 static struct view_ops pager_ops = {
2606         "line",
2607         NULL,
2608         pager_read,
2609         pager_draw,
2610         pager_request,
2611         pager_grep,
2612         pager_select,
2613 };
2616 /*
2617  * Help backend
2618  */
2620 static bool
2621 help_open(struct view *view)
2623         char buf[BUFSIZ];
2624         int lines = ARRAY_SIZE(req_info) + 2;
2625         int i;
2627         if (view->lines > 0)
2628                 return TRUE;
2630         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2631                 if (!req_info[i].request)
2632                         lines++;
2634         view->line = calloc(lines, sizeof(*view->line));
2635         if (!view->line)
2636                 return FALSE;
2638         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2640         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2641                 char *key;
2643                 if (!req_info[i].request) {
2644                         add_line_text(view, "", LINE_DEFAULT);
2645                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2646                         continue;
2647                 }
2649                 key = get_key(req_info[i].request);
2650                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2651                         continue;
2653                 add_line_text(view, buf, LINE_DEFAULT);
2654         }
2656         return TRUE;
2659 static struct view_ops help_ops = {
2660         "line",
2661         help_open,
2662         NULL,
2663         pager_draw,
2664         pager_request,
2665         pager_grep,
2666         pager_select,
2667 };
2670 /*
2671  * Tree backend
2672  */
2674 struct tree_stack_entry {
2675         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2676         unsigned long lineno;           /* Line number to restore */
2677         char *name;                     /* Position of name in opt_path */
2678 };
2680 /* The top of the path stack. */
2681 static struct tree_stack_entry *tree_stack = NULL;
2682 unsigned long tree_lineno = 0;
2684 static void
2685 pop_tree_stack_entry(void)
2687         struct tree_stack_entry *entry = tree_stack;
2689         tree_lineno = entry->lineno;
2690         entry->name[0] = 0;
2691         tree_stack = entry->prev;
2692         free(entry);
2695 static void
2696 push_tree_stack_entry(char *name, unsigned long lineno)
2698         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2699         size_t pathlen = strlen(opt_path);
2701         if (!entry)
2702                 return;
2704         entry->prev = tree_stack;
2705         entry->name = opt_path + pathlen;
2706         tree_stack = entry;
2708         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2709                 pop_tree_stack_entry();
2710                 return;
2711         }
2713         /* Move the current line to the first tree entry. */
2714         tree_lineno = 1;
2715         entry->lineno = lineno;
2718 /* Parse output from git-ls-tree(1):
2719  *
2720  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2721  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2722  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2723  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2724  */
2726 #define SIZEOF_TREE_ATTR \
2727         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2729 #define TREE_UP_FORMAT "040000 tree %s\t.."
2731 static int
2732 tree_compare_entry(enum line_type type1, char *name1,
2733                    enum line_type type2, char *name2)
2735         if (type1 != type2) {
2736                 if (type1 == LINE_TREE_DIR)
2737                         return -1;
2738                 return 1;
2739         }
2741         return strcmp(name1, name2);
2744 static bool
2745 tree_read(struct view *view, char *text)
2747         size_t textlen = text ? strlen(text) : 0;
2748         char buf[SIZEOF_STR];
2749         unsigned long pos;
2750         enum line_type type;
2751         bool first_read = view->lines == 0;
2753         if (textlen <= SIZEOF_TREE_ATTR)
2754                 return FALSE;
2756         type = text[STRING_SIZE("100644 ")] == 't'
2757              ? LINE_TREE_DIR : LINE_TREE_FILE;
2759         if (first_read) {
2760                 /* Add path info line */
2761                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2762                     !realloc_lines(view, view->line_size + 1) ||
2763                     !add_line_text(view, buf, LINE_DEFAULT))
2764                         return FALSE;
2766                 /* Insert "link" to parent directory. */
2767                 if (*opt_path) {
2768                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2769                             !realloc_lines(view, view->line_size + 1) ||
2770                             !add_line_text(view, buf, LINE_TREE_DIR))
2771                                 return FALSE;
2772                 }
2773         }
2775         /* Strip the path part ... */
2776         if (*opt_path) {
2777                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2778                 size_t striplen = strlen(opt_path);
2779                 char *path = text + SIZEOF_TREE_ATTR;
2781                 if (pathlen > striplen)
2782                         memmove(path, path + striplen,
2783                                 pathlen - striplen + 1);
2784         }
2786         /* Skip "Directory ..." and ".." line. */
2787         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2788                 struct line *line = &view->line[pos];
2789                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2790                 char *path2 = text + SIZEOF_TREE_ATTR;
2791                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2793                 if (cmp <= 0)
2794                         continue;
2796                 text = strdup(text);
2797                 if (!text)
2798                         return FALSE;
2800                 if (view->lines > pos)
2801                         memmove(&view->line[pos + 1], &view->line[pos],
2802                                 (view->lines - pos) * sizeof(*line));
2804                 line = &view->line[pos];
2805                 line->data = text;
2806                 line->type = type;
2807                 view->lines++;
2808                 return TRUE;
2809         }
2811         if (!add_line_text(view, text, type))
2812                 return FALSE;
2814         if (tree_lineno > view->lineno) {
2815                 view->lineno = tree_lineno;
2816                 tree_lineno = 0;
2817         }
2819         return TRUE;
2822 static enum request
2823 tree_request(struct view *view, enum request request, struct line *line)
2825         enum open_flags flags;
2827         if (request != REQ_ENTER)
2828                 return request;
2830         /* Cleanup the stack if the tree view is at a different tree. */
2831         while (!*opt_path && tree_stack)
2832                 pop_tree_stack_entry();
2834         switch (line->type) {
2835         case LINE_TREE_DIR:
2836                 /* Depending on whether it is a subdir or parent (updir?) link
2837                  * mangle the path buffer. */
2838                 if (line == &view->line[1] && *opt_path) {
2839                         pop_tree_stack_entry();
2841                 } else {
2842                         char *data = line->data;
2843                         char *basename = data + SIZEOF_TREE_ATTR;
2845                         push_tree_stack_entry(basename, view->lineno);
2846                 }
2848                 /* Trees and subtrees share the same ID, so they are not not
2849                  * unique like blobs. */
2850                 flags = OPEN_RELOAD;
2851                 request = REQ_VIEW_TREE;
2852                 break;
2854         case LINE_TREE_FILE:
2855                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2856                 request = REQ_VIEW_BLOB;
2857                 break;
2859         default:
2860                 return TRUE;
2861         }
2863         open_view(view, request, flags);
2864         if (request == REQ_VIEW_TREE) {
2865                 view->lineno = tree_lineno;
2866         }
2868         return REQ_NONE;
2871 static void
2872 tree_select(struct view *view, struct line *line)
2874         char *text = line->data + STRING_SIZE("100644 blob ");
2876         if (line->type == LINE_TREE_FILE) {
2877                 string_copy_rev(ref_blob, text);
2879         } else if (line->type != LINE_TREE_DIR) {
2880                 return;
2881         }
2883         string_copy_rev(view->ref, text);
2886 static struct view_ops tree_ops = {
2887         "file",
2888         NULL,
2889         tree_read,
2890         pager_draw,
2891         tree_request,
2892         pager_grep,
2893         tree_select,
2894 };
2896 static bool
2897 blob_read(struct view *view, char *line)
2899         return add_line_text(view, line, LINE_DEFAULT) != NULL;
2902 static struct view_ops blob_ops = {
2903         "line",
2904         NULL,
2905         blob_read,
2906         pager_draw,
2907         pager_request,
2908         pager_grep,
2909         pager_select,
2910 };
2913 /*
2914  * Status backend
2915  */
2917 struct status {
2918         char status;
2919         struct {
2920                 mode_t mode;
2921                 char rev[SIZEOF_REV];
2922         } old;
2923         struct {
2924                 mode_t mode;
2925                 char rev[SIZEOF_REV];
2926         } new;
2927         char name[SIZEOF_STR];
2928 };
2930 static struct status stage_status;
2931 static enum line_type stage_line_type;
2933 /* Get fields from the diff line:
2934  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2935  */
2936 static inline bool
2937 status_get_diff(struct status *file, char *buf, size_t bufsize)
2939         char *old_mode = buf +  1;
2940         char *new_mode = buf +  8;
2941         char *old_rev  = buf + 15;
2942         char *new_rev  = buf + 56;
2943         char *status   = buf + 97;
2945         if (bufsize != 99 ||
2946             old_mode[-1] != ':' ||
2947             new_mode[-1] != ' ' ||
2948             old_rev[-1]  != ' ' ||
2949             new_rev[-1]  != ' ' ||
2950             status[-1]   != ' ')
2951                 return FALSE;
2953         file->status = *status;
2955         string_copy_rev(file->old.rev, old_rev);
2956         string_copy_rev(file->new.rev, new_rev);
2958         file->old.mode = strtoul(old_mode, NULL, 8);
2959         file->new.mode = strtoul(new_mode, NULL, 8);
2961         file->name[0] = 0;
2963         return TRUE;
2966 static bool
2967 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2969         struct status *file = NULL;
2970         char buf[SIZEOF_STR * 4];
2971         size_t bufsize = 0;
2972         FILE *pipe;
2974         pipe = popen(cmd, "r");
2975         if (!pipe)
2976                 return FALSE;
2978         add_line_data(view, NULL, type);
2980         while (!feof(pipe) && !ferror(pipe)) {
2981                 char *sep;
2982                 size_t readsize;
2984                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2985                 if (!readsize)
2986                         break;
2987                 bufsize += readsize;
2989                 /* Process while we have NUL chars. */
2990                 while ((sep = memchr(buf, 0, bufsize))) {
2991                         size_t sepsize = sep - buf + 1;
2993                         if (!file) {
2994                                 if (!realloc_lines(view, view->line_size + 1))
2995                                         goto error_out;
2997                                 file = calloc(1, sizeof(*file));
2998                                 if (!file)
2999                                         goto error_out;
3001                                 add_line_data(view, file, type);
3002                         }
3004                         /* Parse diff info part. */
3005                         if (!diff) {
3006                                 file->status = '?';
3008                         } else if (!file->status) {
3009                                 if (!status_get_diff(file, buf, sepsize))
3010                                         goto error_out;
3012                                 bufsize -= sepsize;
3013                                 memmove(buf, sep + 1, bufsize);
3015                                 sep = memchr(buf, 0, bufsize);
3016                                 if (!sep)
3017                                         break;
3018                                 sepsize = sep - buf + 1;
3019                         }
3021                         /* git-ls-files just delivers a NUL separated
3022                          * list of file names similar to the second half
3023                          * of the git-diff-* output. */
3024                         string_ncopy(file->name, buf, sepsize);
3025                         bufsize -= sepsize;
3026                         memmove(buf, sep + 1, bufsize);
3027                         file = NULL;
3028                 }
3029         }
3031         if (ferror(pipe)) {
3032 error_out:
3033                 pclose(pipe);
3034                 return FALSE;
3035         }
3037         if (!view->line[view->lines - 1].data)
3038                 add_line_data(view, NULL, LINE_STAT_NONE);
3040         pclose(pipe);
3041         return TRUE;
3044 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3045 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3046 #define STATUS_LIST_OTHER_CMD \
3047         "git ls-files -z --others --exclude-per-directory=.gitignore"
3049 #define STATUS_DIFF_SHOW_CMD \
3050         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3052 /* First parse staged info using git-diff-index(1), then parse unstaged
3053  * info using git-diff-files(1), and finally untracked files using
3054  * git-ls-files(1). */
3055 static bool
3056 status_open(struct view *view)
3058         struct stat statbuf;
3059         char exclude[SIZEOF_STR];
3060         char cmd[SIZEOF_STR];
3061         unsigned long prev_lineno = view->lineno;
3062         size_t i;
3065         for (i = 0; i < view->lines; i++)
3066                 free(view->line[i].data);
3067         free(view->line);
3068         view->lines = view->line_size = view->lineno = 0;
3069         view->line = NULL;
3071         if (!realloc_lines(view, view->line_size + 6))
3072                 return FALSE;
3074         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3075                 return FALSE;
3077         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3079         if (stat(exclude, &statbuf) >= 0) {
3080                 size_t cmdsize = strlen(cmd);
3082                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3083                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3084                         return FALSE;
3085         }
3087         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3088             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3089             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3090                 return FALSE;
3092         /* If all went well restore the previous line number to stay in
3093          * the context. */
3094         if (prev_lineno < view->lines)
3095                 view->lineno = prev_lineno;
3096         else
3097                 view->lineno = view->lines - 1;
3099         return TRUE;
3102 static bool
3103 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3105         struct status *status = line->data;
3107         wmove(view->win, lineno, 0);
3109         if (selected) {
3110                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3111                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3113         } else if (!status && line->type != LINE_STAT_NONE) {
3114                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3115                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3117         } else {
3118                 wattrset(view->win, get_line_attr(line->type));
3119         }
3121         if (!status) {
3122                 char *text;
3124                 switch (line->type) {
3125                 case LINE_STAT_STAGED:
3126                         text = "Changes to be committed:";
3127                         break;
3129                 case LINE_STAT_UNSTAGED:
3130                         text = "Changed but not updated:";
3131                         break;
3133                 case LINE_STAT_UNTRACKED:
3134                         text = "Untracked files:";
3135                         break;
3137                 case LINE_STAT_NONE:
3138                         text = "    (no files)";
3139                         break;
3141                 default:
3142                         return FALSE;
3143                 }
3145                 waddstr(view->win, text);
3146                 return TRUE;
3147         }
3149         waddch(view->win, status->status);
3150         if (!selected)
3151                 wattrset(view->win, A_NORMAL);
3152         wmove(view->win, lineno, 4);
3153         waddstr(view->win, status->name);
3155         return TRUE;
3158 static enum request
3159 status_enter(struct view *view, struct line *line)
3161         struct status *status = line->data;
3162         char path[SIZEOF_STR] = "";
3163         char *info;
3164         size_t cmdsize = 0;
3166         if (line->type == LINE_STAT_NONE ||
3167             (!status && line[1].type == LINE_STAT_NONE)) {
3168                 report("No file to diff");
3169                 return REQ_NONE;
3170         }
3172         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3173                 return REQ_QUIT;
3175         if (opt_cdup[0] &&
3176             line->type != LINE_STAT_UNTRACKED &&
3177             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3178                 return REQ_QUIT;
3180         switch (line->type) {
3181         case LINE_STAT_STAGED:
3182                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3183                                         "--cached", path))
3184                         return REQ_QUIT;
3185                 if (status)
3186                         info = "Staged changes to %s";
3187                 else
3188                         info = "Staged changes";
3189                 break;
3191         case LINE_STAT_UNSTAGED:
3192                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3193                                         "", path))
3194                         return REQ_QUIT;
3195                 if (status)
3196                         info = "Unstaged changes to %s";
3197                 else
3198                         info = "Unstaged changes";
3199                 break;
3201         case LINE_STAT_UNTRACKED:
3202                 if (opt_pipe)
3203                         return REQ_QUIT;
3206                 if (!status) {
3207                         report("No file to show");
3208                         return REQ_NONE;
3209                 }
3211                 opt_pipe = fopen(status->name, "r");
3212                 info = "Untracked file %s";
3213                 break;
3215         default:
3216                 die("w00t");
3217         }
3219         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3220         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3221                 if (status) {
3222                         stage_status = *status;
3223                 } else {
3224                         memset(&stage_status, 0, sizeof(stage_status));
3225                 }
3227                 stage_line_type = line->type;
3228                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3229         }
3231         return REQ_NONE;
3235 static bool
3236 status_update_file(struct view *view, struct status *status, enum line_type type)
3238         char cmd[SIZEOF_STR];
3239         char buf[SIZEOF_STR];
3240         size_t cmdsize = 0;
3241         size_t bufsize = 0;
3242         size_t written = 0;
3243         FILE *pipe;
3245         if (opt_cdup[0] &&
3246             type != LINE_STAT_UNTRACKED &&
3247             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3248                 return FALSE;
3250         switch (type) {
3251         case LINE_STAT_STAGED:
3252                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3253                                         status->old.mode,
3254                                         status->old.rev,
3255                                         status->name, 0))
3256                         return FALSE;
3258                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3259                 break;
3261         case LINE_STAT_UNSTAGED:
3262         case LINE_STAT_UNTRACKED:
3263                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3264                         return FALSE;
3266                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3267                 break;
3269         default:
3270                 die("w00t");
3271         }
3273         pipe = popen(cmd, "w");
3274         if (!pipe)
3275                 return FALSE;
3277         while (!ferror(pipe) && written < bufsize) {
3278                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3279         }
3281         pclose(pipe);
3283         if (written != bufsize)
3284                 return FALSE;
3286         return TRUE;
3289 static void
3290 status_update(struct view *view)
3292         struct line *line = &view->line[view->lineno];
3294         assert(view->lines);
3296         if (!line->data) {
3297                 while (++line < view->line + view->lines && line->data) {
3298                         if (!status_update_file(view, line->data, line->type))
3299                                 report("Failed to update file status");
3300                 }
3302                 if (!line[-1].data) {
3303                         report("Nothing to update");
3304                         return;
3305                 }
3307         } else if (!status_update_file(view, line->data, line->type)) {
3308                 report("Failed to update file status");
3309         }
3311         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3314 static enum request
3315 status_request(struct view *view, enum request request, struct line *line)
3317         struct status *status = line->data;
3319         switch (request) {
3320         case REQ_STATUS_UPDATE:
3321                 status_update(view);
3322                 break;
3324         case REQ_EDIT:
3325                 if (!status)
3326                         return request;
3328                 open_editor(status->status != '?', status->name);
3329                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3330                 break;
3332         case REQ_ENTER:
3333                 status_enter(view, line);
3334                 break;
3336         case REQ_REFRESH:
3337                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3338                 break;
3340         default:
3341                 return request;
3342         }
3344         return REQ_NONE;
3347 static void
3348 status_select(struct view *view, struct line *line)
3350         struct status *status = line->data;
3351         char file[SIZEOF_STR] = "all files";
3352         char *text;
3354         if (status && !string_format(file, "'%s'", status->name))
3355                 return;
3357         if (!status && line[1].type == LINE_STAT_NONE)
3358                 line++;
3360         switch (line->type) {
3361         case LINE_STAT_STAGED:
3362                 text = "Press %s to unstage %s for commit";
3363                 break;
3365         case LINE_STAT_UNSTAGED:
3366                 text = "Press %s to stage %s for commit";
3367                 break;
3369         case LINE_STAT_UNTRACKED:
3370                 text = "Press %s to stage %s for addition";
3371                 break;
3373         case LINE_STAT_NONE:
3374                 text = "Nothing to update";
3375                 break;
3377         default:
3378                 die("w00t");
3379         }
3381         string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3384 static bool
3385 status_grep(struct view *view, struct line *line)
3387         struct status *status = line->data;
3388         enum { S_STATUS, S_NAME, S_END } state;
3389         char buf[2] = "?";
3390         regmatch_t pmatch;
3392         if (!status)
3393                 return FALSE;
3395         for (state = S_STATUS; state < S_END; state++) {
3396                 char *text;
3398                 switch (state) {
3399                 case S_NAME:    text = status->name;    break;
3400                 case S_STATUS:
3401                         buf[0] = status->status;
3402                         text = buf;
3403                         break;
3405                 default:
3406                         return FALSE;
3407                 }
3409                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3410                         return TRUE;
3411         }
3413         return FALSE;
3416 static struct view_ops status_ops = {
3417         "file",
3418         status_open,
3419         NULL,
3420         status_draw,
3421         status_request,
3422         status_grep,
3423         status_select,
3424 };
3427 static bool
3428 stage_diff_line(FILE *pipe, struct line *line)
3430         char *buf = line->data;
3431         size_t bufsize = strlen(buf);
3432         size_t written = 0;
3434         while (!ferror(pipe) && written < bufsize) {
3435                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3436         }
3438         fputc('\n', pipe);
3440         return written == bufsize;
3443 static struct line *
3444 stage_diff_hdr(struct view *view, struct line *line)
3446         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3447         struct line *diff_hdr;
3449         if (line->type == LINE_DIFF_CHUNK)
3450                 diff_hdr = line - 1;
3451         else
3452                 diff_hdr = view->line + 1;
3454         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3455                 if (diff_hdr->type == LINE_DIFF_HEADER)
3456                         return diff_hdr;
3458                 diff_hdr += diff_hdr_dir;
3459         }
3461         return NULL;
3464 static bool
3465 stage_update_chunk(struct view *view, struct line *line)
3467         char cmd[SIZEOF_STR];
3468         size_t cmdsize = 0;
3469         struct line *diff_hdr, *diff_chunk, *diff_end;
3470         FILE *pipe;
3472         diff_hdr = stage_diff_hdr(view, line);
3473         if (!diff_hdr)
3474                 return FALSE;
3476         if (opt_cdup[0] &&
3477             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3478                 return FALSE;
3480         if (!string_format_from(cmd, &cmdsize,
3481                                 "git apply --cached %s - && "
3482                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3483                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3484                 return FALSE;
3486         pipe = popen(cmd, "w");
3487         if (!pipe)
3488                 return FALSE;
3490         diff_end = view->line + view->lines;
3491         if (line->type != LINE_DIFF_CHUNK) {
3492                 diff_chunk = diff_hdr;
3494         } else {
3495                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3496                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3497                             diff_chunk->type == LINE_DIFF_HEADER)
3498                                 diff_end = diff_chunk;
3500                 diff_chunk = line;
3502                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3503                         switch (diff_hdr->type) {
3504                         case LINE_DIFF_HEADER:
3505                         case LINE_DIFF_INDEX:
3506                         case LINE_DIFF_ADD:
3507                         case LINE_DIFF_DEL:
3508                                 break;
3510                         default:
3511                                 diff_hdr++;
3512                                 continue;
3513                         }
3515                         if (!stage_diff_line(pipe, diff_hdr++)) {
3516                                 pclose(pipe);
3517                                 return FALSE;
3518                         }
3519                 }
3520         }
3522         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3523                 diff_chunk++;
3525         pclose(pipe);
3527         if (diff_chunk != diff_end)
3528                 return FALSE;
3530         return TRUE;
3533 static void
3534 stage_update(struct view *view, struct line *line)
3536         if (stage_line_type != LINE_STAT_UNTRACKED &&
3537             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3538                 if (!stage_update_chunk(view, line)) {
3539                         report("Failed to apply chunk");
3540                         return;
3541                 }
3543         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3544                 report("Failed to update file");
3545                 return;
3546         }
3548         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3550         view = VIEW(REQ_VIEW_STATUS);
3551         if (view_is_displayed(view))
3552                 status_enter(view, &view->line[view->lineno]);
3555 static enum request
3556 stage_request(struct view *view, enum request request, struct line *line)
3558         switch (request) {
3559         case REQ_STATUS_UPDATE:
3560                 stage_update(view, line);
3561                 break;
3563         case REQ_EDIT:
3564                 if (!stage_status.name[0])
3565                         return request;
3567                 open_editor(stage_status.status != '?', stage_status.name);
3568                 break;
3570         case REQ_ENTER:
3571                 pager_request(view, request, line);
3572                 break;
3574         default:
3575                 return request;
3576         }
3578         return REQ_NONE;
3581 static struct view_ops stage_ops = {
3582         "line",
3583         NULL,
3584         pager_read,
3585         pager_draw,
3586         stage_request,
3587         pager_grep,
3588         pager_select,
3589 };
3592 /*
3593  * Revision graph
3594  */
3596 struct commit {
3597         char id[SIZEOF_REV];            /* SHA1 ID. */
3598         char title[128];                /* First line of the commit message. */
3599         char author[75];                /* Author of the commit. */
3600         struct tm time;                 /* Date from the author ident. */
3601         struct ref **refs;              /* Repository references. */
3602         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3603         size_t graph_size;              /* The width of the graph array. */
3604 };
3606 /* Size of rev graph with no  "padding" columns */
3607 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3609 struct rev_graph {
3610         struct rev_graph *prev, *next, *parents;
3611         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3612         size_t size;
3613         struct commit *commit;
3614         size_t pos;
3615 };
3617 /* Parents of the commit being visualized. */
3618 static struct rev_graph graph_parents[4];
3620 /* The current stack of revisions on the graph. */
3621 static struct rev_graph graph_stacks[4] = {
3622         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3623         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3624         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3625         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3626 };
3628 static inline bool
3629 graph_parent_is_merge(struct rev_graph *graph)
3631         return graph->parents->size > 1;
3634 static inline void
3635 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3637         struct commit *commit = graph->commit;
3639         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3640                 commit->graph[commit->graph_size++] = symbol;
3643 static void
3644 done_rev_graph(struct rev_graph *graph)
3646         if (graph_parent_is_merge(graph) &&
3647             graph->pos < graph->size - 1 &&
3648             graph->next->size == graph->size + graph->parents->size - 1) {
3649                 size_t i = graph->pos + graph->parents->size - 1;
3651                 graph->commit->graph_size = i * 2;
3652                 while (i < graph->next->size - 1) {
3653                         append_to_rev_graph(graph, ' ');
3654                         append_to_rev_graph(graph, '\\');
3655                         i++;
3656                 }
3657         }
3659         graph->size = graph->pos = 0;
3660         graph->commit = NULL;
3661         memset(graph->parents, 0, sizeof(*graph->parents));
3664 static void
3665 push_rev_graph(struct rev_graph *graph, char *parent)
3667         int i;
3669         /* "Collapse" duplicate parents lines.
3670          *
3671          * FIXME: This needs to also update update the drawn graph but
3672          * for now it just serves as a method for pruning graph lines. */
3673         for (i = 0; i < graph->size; i++)
3674                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3675                         return;
3677         if (graph->size < SIZEOF_REVITEMS) {
3678                 string_copy_rev(graph->rev[graph->size++], parent);
3679         }
3682 static chtype
3683 get_rev_graph_symbol(struct rev_graph *graph)
3685         chtype symbol;
3687         if (graph->parents->size == 0)
3688                 symbol = REVGRAPH_INIT;
3689         else if (graph_parent_is_merge(graph))
3690                 symbol = REVGRAPH_MERGE;
3691         else if (graph->pos >= graph->size)
3692                 symbol = REVGRAPH_BRANCH;
3693         else
3694                 symbol = REVGRAPH_COMMIT;
3696         return symbol;
3699 static void
3700 draw_rev_graph(struct rev_graph *graph)
3702         struct rev_filler {
3703                 chtype separator, line;
3704         };
3705         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3706         static struct rev_filler fillers[] = {
3707                 { ' ',  REVGRAPH_LINE },
3708                 { '`',  '.' },
3709                 { '\'', ' ' },
3710                 { '/',  ' ' },
3711         };
3712         chtype symbol = get_rev_graph_symbol(graph);
3713         struct rev_filler *filler;
3714         size_t i;
3716         filler = &fillers[DEFAULT];
3718         for (i = 0; i < graph->pos; i++) {
3719                 append_to_rev_graph(graph, filler->line);
3720                 if (graph_parent_is_merge(graph->prev) &&
3721                     graph->prev->pos == i)
3722                         filler = &fillers[RSHARP];
3724                 append_to_rev_graph(graph, filler->separator);
3725         }
3727         /* Place the symbol for this revision. */
3728         append_to_rev_graph(graph, symbol);
3730         if (graph->prev->size > graph->size)
3731                 filler = &fillers[RDIAG];
3732         else
3733                 filler = &fillers[DEFAULT];
3735         i++;
3737         for (; i < graph->size; i++) {
3738                 append_to_rev_graph(graph, filler->separator);
3739                 append_to_rev_graph(graph, filler->line);
3740                 if (graph_parent_is_merge(graph->prev) &&
3741                     i < graph->prev->pos + graph->parents->size)
3742                         filler = &fillers[RSHARP];
3743                 if (graph->prev->size > graph->size)
3744                         filler = &fillers[LDIAG];
3745         }
3747         if (graph->prev->size > graph->size) {
3748                 append_to_rev_graph(graph, filler->separator);
3749                 if (filler->line != ' ')
3750                         append_to_rev_graph(graph, filler->line);
3751         }
3754 /* Prepare the next rev graph */
3755 static void
3756 prepare_rev_graph(struct rev_graph *graph)
3758         size_t i;
3760         /* First, traverse all lines of revisions up to the active one. */
3761         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3762                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3763                         break;
3765                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3766         }
3768         /* Interleave the new revision parent(s). */
3769         for (i = 0; i < graph->parents->size; i++)
3770                 push_rev_graph(graph->next, graph->parents->rev[i]);
3772         /* Lastly, put any remaining revisions. */
3773         for (i = graph->pos + 1; i < graph->size; i++)
3774                 push_rev_graph(graph->next, graph->rev[i]);
3777 static void
3778 update_rev_graph(struct rev_graph *graph)
3780         /* If this is the finalizing update ... */
3781         if (graph->commit)
3782                 prepare_rev_graph(graph);
3784         /* Graph visualization needs a one rev look-ahead,
3785          * so the first update doesn't visualize anything. */
3786         if (!graph->prev->commit)
3787                 return;
3789         draw_rev_graph(graph->prev);
3790         done_rev_graph(graph->prev->prev);
3794 /*
3795  * Main view backend
3796  */
3798 static bool
3799 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3801         char buf[DATE_COLS + 1];
3802         struct commit *commit = line->data;
3803         enum line_type type;
3804         int col = 0;
3805         size_t timelen;
3806         size_t authorlen;
3807         int trimmed = 1;
3809         if (!*commit->author)
3810                 return FALSE;
3812         wmove(view->win, lineno, col);
3814         if (selected) {
3815                 type = LINE_CURSOR;
3816                 wattrset(view->win, get_line_attr(type));
3817                 wchgat(view->win, -1, 0, type, NULL);
3819         } else {
3820                 type = LINE_MAIN_COMMIT;
3821                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3822         }
3824         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3825         waddnstr(view->win, buf, timelen);
3826         waddstr(view->win, " ");
3828         col += DATE_COLS;
3829         wmove(view->win, lineno, col);
3830         if (type != LINE_CURSOR)
3831                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3833         if (opt_utf8) {
3834                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3835         } else {
3836                 authorlen = strlen(commit->author);
3837                 if (authorlen > AUTHOR_COLS - 2) {
3838                         authorlen = AUTHOR_COLS - 2;
3839                         trimmed = 1;
3840                 }
3841         }
3843         if (trimmed) {
3844                 waddnstr(view->win, commit->author, authorlen);
3845                 if (type != LINE_CURSOR)
3846                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3847                 waddch(view->win, '~');
3848         } else {
3849                 waddstr(view->win, commit->author);
3850         }
3852         col += AUTHOR_COLS;
3853         if (type != LINE_CURSOR)
3854                 wattrset(view->win, A_NORMAL);
3856         if (opt_rev_graph && commit->graph_size) {
3857                 size_t i;
3859                 wmove(view->win, lineno, col);
3860                 /* Using waddch() instead of waddnstr() ensures that
3861                  * they'll be rendered correctly for the cursor line. */
3862                 for (i = 0; i < commit->graph_size; i++)
3863                         waddch(view->win, commit->graph[i]);
3865                 waddch(view->win, ' ');
3866                 col += commit->graph_size + 1;
3867         }
3869         wmove(view->win, lineno, col);
3871         if (commit->refs) {
3872                 size_t i = 0;
3874                 do {
3875                         if (type == LINE_CURSOR)
3876                                 ;
3877                         else if (commit->refs[i]->tag)
3878                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3879                         else if (commit->refs[i]->remote)
3880                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3881                         else
3882                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3883                         waddstr(view->win, "[");
3884                         waddstr(view->win, commit->refs[i]->name);
3885                         waddstr(view->win, "]");
3886                         if (type != LINE_CURSOR)
3887                                 wattrset(view->win, A_NORMAL);
3888                         waddstr(view->win, " ");
3889                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3890                 } while (commit->refs[i++]->next);
3891         }
3893         if (type != LINE_CURSOR)
3894                 wattrset(view->win, get_line_attr(type));
3896         {
3897                 int titlelen = strlen(commit->title);
3899                 if (col + titlelen > view->width)
3900                         titlelen = view->width - col;
3902                 waddnstr(view->win, commit->title, titlelen);
3903         }
3905         return TRUE;
3908 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3909 static bool
3910 main_read(struct view *view, char *line)
3912         static struct rev_graph *graph = graph_stacks;
3913         enum line_type type;
3914         struct commit *commit;
3916         if (!line) {
3917                 update_rev_graph(graph);
3918                 return TRUE;
3919         }
3921         type = get_line_type(line);
3922         if (type == LINE_COMMIT) {
3923                 commit = calloc(1, sizeof(struct commit));
3924                 if (!commit)
3925                         return FALSE;
3927                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3928                 commit->refs = get_refs(commit->id);
3929                 graph->commit = commit;
3930                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3931                 return TRUE;
3932         }
3934         if (!view->lines)
3935                 return TRUE;
3936         commit = view->line[view->lines - 1].data;
3938         switch (type) {
3939         case LINE_PARENT:
3940                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3941                 break;
3943         case LINE_AUTHOR:
3944         {
3945                 /* Parse author lines where the name may be empty:
3946                  *      author  <email@address.tld> 1138474660 +0100
3947                  */
3948                 char *ident = line + STRING_SIZE("author ");
3949                 char *nameend = strchr(ident, '<');
3950                 char *emailend = strchr(ident, '>');
3952                 if (!nameend || !emailend)
3953                         break;
3955                 update_rev_graph(graph);
3956                 graph = graph->next;
3958                 *nameend = *emailend = 0;
3959                 ident = chomp_string(ident);
3960                 if (!*ident) {
3961                         ident = chomp_string(nameend + 1);
3962                         if (!*ident)
3963                                 ident = "Unknown";
3964                 }
3966                 string_ncopy(commit->author, ident, strlen(ident));
3968                 /* Parse epoch and timezone */
3969                 if (emailend[1] == ' ') {
3970                         char *secs = emailend + 2;
3971                         char *zone = strchr(secs, ' ');
3972                         time_t time = (time_t) atol(secs);
3974                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3975                                 long tz;
3977                                 zone++;
3978                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3979                                 tz += ('0' - zone[2]) * 60 * 60;
3980                                 tz += ('0' - zone[3]) * 60;
3981                                 tz += ('0' - zone[4]) * 60;
3983                                 if (zone[0] == '-')
3984                                         tz = -tz;
3986                                 time -= tz;
3987                         }
3989                         gmtime_r(&time, &commit->time);
3990                 }
3991                 break;
3992         }
3993         default:
3994                 /* Fill in the commit title if it has not already been set. */
3995                 if (commit->title[0])
3996                         break;
3998                 /* Require titles to start with a non-space character at the
3999                  * offset used by git log. */
4000                 if (strncmp(line, "    ", 4))
4001                         break;
4002                 line += 4;
4003                 /* Well, if the title starts with a whitespace character,
4004                  * try to be forgiving.  Otherwise we end up with no title. */
4005                 while (isspace(*line))
4006                         line++;
4007                 if (*line == '\0')
4008                         break;
4009                 /* FIXME: More graceful handling of titles; append "..." to
4010                  * shortened titles, etc. */
4012                 string_ncopy(commit->title, line, strlen(line));
4013         }
4015         return TRUE;
4018 static void
4019 cherry_pick_commit(struct commit *commit)
4021         char cmd[SIZEOF_STR];
4022         char *cherry_pick = getenv("TIG_CHERRY_PICK");
4024         if (!cherry_pick)
4025                 cherry_pick = "git cherry-pick";
4027         if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4028                 def_prog_mode();           /* save current tty modes */
4029                 endwin();                  /* restore original tty modes */
4030                 system(cmd);
4031                 fprintf(stderr, "Press Enter to continue");
4032                 getc(stdin);
4033                 reset_prog_mode();
4034                 redraw_display();
4035         }
4038 static enum request
4039 main_request(struct view *view, enum request request, struct line *line)
4041         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4043         if (request == REQ_ENTER)
4044                 open_view(view, REQ_VIEW_DIFF, flags);
4045         else if (request == REQ_CHERRY_PICK)
4046                 cherry_pick_commit(line->data);
4047         else
4048                 return request;
4050         return REQ_NONE;
4053 static bool
4054 main_grep(struct view *view, struct line *line)
4056         struct commit *commit = line->data;
4057         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4058         char buf[DATE_COLS + 1];
4059         regmatch_t pmatch;
4061         for (state = S_TITLE; state < S_END; state++) {
4062                 char *text;
4064                 switch (state) {
4065                 case S_TITLE:   text = commit->title;   break;
4066                 case S_AUTHOR:  text = commit->author;  break;
4067                 case S_DATE:
4068                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4069                                 continue;
4070                         text = buf;
4071                         break;
4073                 default:
4074                         return FALSE;
4075                 }
4077                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4078                         return TRUE;
4079         }
4081         return FALSE;
4084 static void
4085 main_select(struct view *view, struct line *line)
4087         struct commit *commit = line->data;
4089         string_copy_rev(view->ref, commit->id);
4090         string_copy_rev(ref_commit, view->ref);
4093 static struct view_ops main_ops = {
4094         "commit",
4095         NULL,
4096         main_read,
4097         main_draw,
4098         main_request,
4099         main_grep,
4100         main_select,
4101 };
4104 /*
4105  * Unicode / UTF-8 handling
4106  *
4107  * NOTE: Much of the following code for dealing with unicode is derived from
4108  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4109  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4110  */
4112 /* I've (over)annotated a lot of code snippets because I am not entirely
4113  * confident that the approach taken by this small UTF-8 interface is correct.
4114  * --jonas */
4116 static inline int
4117 unicode_width(unsigned long c)
4119         if (c >= 0x1100 &&
4120            (c <= 0x115f                         /* Hangul Jamo */
4121             || c == 0x2329
4122             || c == 0x232a
4123             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4124                                                 /* CJK ... Yi */
4125             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4126             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4127             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4128             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4129             || (c >= 0xffe0  && c <= 0xffe6)
4130             || (c >= 0x20000 && c <= 0x2fffd)
4131             || (c >= 0x30000 && c <= 0x3fffd)))
4132                 return 2;
4134         return 1;
4137 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4138  * Illegal bytes are set one. */
4139 static const unsigned char utf8_bytes[256] = {
4140         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,
4141         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,
4142         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,
4143         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,
4144         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,
4145         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,
4146         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,
4147         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,
4148 };
4150 /* Decode UTF-8 multi-byte representation into a unicode character. */
4151 static inline unsigned long
4152 utf8_to_unicode(const char *string, size_t length)
4154         unsigned long unicode;
4156         switch (length) {
4157         case 1:
4158                 unicode  =   string[0];
4159                 break;
4160         case 2:
4161                 unicode  =  (string[0] & 0x1f) << 6;
4162                 unicode +=  (string[1] & 0x3f);
4163                 break;
4164         case 3:
4165                 unicode  =  (string[0] & 0x0f) << 12;
4166                 unicode += ((string[1] & 0x3f) << 6);
4167                 unicode +=  (string[2] & 0x3f);
4168                 break;
4169         case 4:
4170                 unicode  =  (string[0] & 0x0f) << 18;
4171                 unicode += ((string[1] & 0x3f) << 12);
4172                 unicode += ((string[2] & 0x3f) << 6);
4173                 unicode +=  (string[3] & 0x3f);
4174                 break;
4175         case 5:
4176                 unicode  =  (string[0] & 0x0f) << 24;
4177                 unicode += ((string[1] & 0x3f) << 18);
4178                 unicode += ((string[2] & 0x3f) << 12);
4179                 unicode += ((string[3] & 0x3f) << 6);
4180                 unicode +=  (string[4] & 0x3f);
4181                 break;
4182         case 6:
4183                 unicode  =  (string[0] & 0x01) << 30;
4184                 unicode += ((string[1] & 0x3f) << 24);
4185                 unicode += ((string[2] & 0x3f) << 18);
4186                 unicode += ((string[3] & 0x3f) << 12);
4187                 unicode += ((string[4] & 0x3f) << 6);
4188                 unicode +=  (string[5] & 0x3f);
4189                 break;
4190         default:
4191                 die("Invalid unicode length");
4192         }
4194         /* Invalid characters could return the special 0xfffd value but NUL
4195          * should be just as good. */
4196         return unicode > 0xffff ? 0 : unicode;
4199 /* Calculates how much of string can be shown within the given maximum width
4200  * and sets trimmed parameter to non-zero value if all of string could not be
4201  * shown.
4202  *
4203  * Additionally, adds to coloffset how many many columns to move to align with
4204  * the expected position. Takes into account how multi-byte and double-width
4205  * characters will effect the cursor position.
4206  *
4207  * Returns the number of bytes to output from string to satisfy max_width. */
4208 static size_t
4209 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4211         const char *start = string;
4212         const char *end = strchr(string, '\0');
4213         size_t mbwidth = 0;
4214         size_t width = 0;
4216         *trimmed = 0;
4218         while (string < end) {
4219                 int c = *(unsigned char *) string;
4220                 unsigned char bytes = utf8_bytes[c];
4221                 size_t ucwidth;
4222                 unsigned long unicode;
4224                 if (string + bytes > end)
4225                         break;
4227                 /* Change representation to figure out whether
4228                  * it is a single- or double-width character. */
4230                 unicode = utf8_to_unicode(string, bytes);
4231                 /* FIXME: Graceful handling of invalid unicode character. */
4232                 if (!unicode)
4233                         break;
4235                 ucwidth = unicode_width(unicode);
4236                 width  += ucwidth;
4237                 if (width > max_width) {
4238                         *trimmed = 1;
4239                         break;
4240                 }
4242                 /* The column offset collects the differences between the
4243                  * number of bytes encoding a character and the number of
4244                  * columns will be used for rendering said character.
4245                  *
4246                  * So if some character A is encoded in 2 bytes, but will be
4247                  * represented on the screen using only 1 byte this will and up
4248                  * adding 1 to the multi-byte column offset.
4249                  *
4250                  * Assumes that no double-width character can be encoding in
4251                  * less than two bytes. */
4252                 if (bytes > ucwidth)
4253                         mbwidth += bytes - ucwidth;
4255                 string  += bytes;
4256         }
4258         *coloffset += mbwidth;
4260         return string - start;
4264 /*
4265  * Status management
4266  */
4268 /* Whether or not the curses interface has been initialized. */
4269 static bool cursed = FALSE;
4271 /* The status window is used for polling keystrokes. */
4272 static WINDOW *status_win;
4274 static bool status_empty = TRUE;
4276 /* Update status and title window. */
4277 static void
4278 report(const char *msg, ...)
4280         struct view *view = display[current_view];
4282         if (input_mode)
4283                 return;
4285         if (!status_empty || *msg) {
4286                 va_list args;
4288                 va_start(args, msg);
4290                 wmove(status_win, 0, 0);
4291                 if (*msg) {
4292                         vwprintw(status_win, msg, args);
4293                         status_empty = FALSE;
4294                 } else {
4295                         status_empty = TRUE;
4296                 }
4297                 wclrtoeol(status_win);
4298                 wrefresh(status_win);
4300                 va_end(args);
4301         }
4303         update_view_title(view);
4304         update_display_cursor(view);
4307 /* Controls when nodelay should be in effect when polling user input. */
4308 static void
4309 set_nonblocking_input(bool loading)
4311         static unsigned int loading_views;
4313         if ((loading == FALSE && loading_views-- == 1) ||
4314             (loading == TRUE  && loading_views++ == 0))
4315                 nodelay(status_win, loading);
4318 static void
4319 init_display(void)
4321         int x, y;
4323         /* Initialize the curses library */
4324         if (isatty(STDIN_FILENO)) {
4325                 cursed = !!initscr();
4326         } else {
4327                 /* Leave stdin and stdout alone when acting as a pager. */
4328                 FILE *io = fopen("/dev/tty", "r+");
4330                 if (!io)
4331                         die("Failed to open /dev/tty");
4332                 cursed = !!newterm(NULL, io, io);
4333         }
4335         if (!cursed)
4336                 die("Failed to initialize curses");
4338         nonl();         /* Tell curses not to do NL->CR/NL on output */
4339         cbreak();       /* Take input chars one at a time, no wait for \n */
4340         noecho();       /* Don't echo input */
4341         leaveok(stdscr, TRUE);
4343         if (has_colors())
4344                 init_colors();
4346         getmaxyx(stdscr, y, x);
4347         status_win = newwin(1, 0, y - 1, 0);
4348         if (!status_win)
4349                 die("Failed to create status window");
4351         /* Enable keyboard mapping */
4352         keypad(status_win, TRUE);
4353         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4356 static char *
4357 read_prompt(const char *prompt)
4359         enum { READING, STOP, CANCEL } status = READING;
4360         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4361         int pos = 0;
4363         while (status == READING) {
4364                 struct view *view;
4365                 int i, key;
4367                 input_mode = TRUE;
4369                 foreach_view (view, i)
4370                         update_view(view);
4372                 input_mode = FALSE;
4374                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4375                 wclrtoeol(status_win);
4377                 /* Refresh, accept single keystroke of input */
4378                 key = wgetch(status_win);
4379                 switch (key) {
4380                 case KEY_RETURN:
4381                 case KEY_ENTER:
4382                 case '\n':
4383                         status = pos ? STOP : CANCEL;
4384                         break;
4386                 case KEY_BACKSPACE:
4387                         if (pos > 0)
4388                                 pos--;
4389                         else
4390                                 status = CANCEL;
4391                         break;
4393                 case KEY_ESC:
4394                         status = CANCEL;
4395                         break;
4397                 case ERR:
4398                         break;
4400                 default:
4401                         if (pos >= sizeof(buf)) {
4402                                 report("Input string too long");
4403                                 return NULL;
4404                         }
4406                         if (isprint(key))
4407                                 buf[pos++] = (char) key;
4408                 }
4409         }
4411         /* Clear the status window */
4412         status_empty = FALSE;
4413         report("");
4415         if (status == CANCEL)
4416                 return NULL;
4418         buf[pos++] = 0;
4420         return buf;
4423 /*
4424  * Repository references
4425  */
4427 static struct ref *refs;
4428 static size_t refs_size;
4430 /* Id <-> ref store */
4431 static struct ref ***id_refs;
4432 static size_t id_refs_size;
4434 static struct ref **
4435 get_refs(char *id)
4437         struct ref ***tmp_id_refs;
4438         struct ref **ref_list = NULL;
4439         size_t ref_list_size = 0;
4440         size_t i;
4442         for (i = 0; i < id_refs_size; i++)
4443                 if (!strcmp(id, id_refs[i][0]->id))
4444                         return id_refs[i];
4446         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4447         if (!tmp_id_refs)
4448                 return NULL;
4450         id_refs = tmp_id_refs;
4452         for (i = 0; i < refs_size; i++) {
4453                 struct ref **tmp;
4455                 if (strcmp(id, refs[i].id))
4456                         continue;
4458                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4459                 if (!tmp) {
4460                         if (ref_list)
4461                                 free(ref_list);
4462                         return NULL;
4463                 }
4465                 ref_list = tmp;
4466                 if (ref_list_size > 0)
4467                         ref_list[ref_list_size - 1]->next = 1;
4468                 ref_list[ref_list_size] = &refs[i];
4470                 /* XXX: The properties of the commit chains ensures that we can
4471                  * safely modify the shared ref. The repo references will
4472                  * always be similar for the same id. */
4473                 ref_list[ref_list_size]->next = 0;
4474                 ref_list_size++;
4475         }
4477         if (ref_list)
4478                 id_refs[id_refs_size++] = ref_list;
4480         return ref_list;
4483 static int
4484 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4486         struct ref *ref;
4487         bool tag = FALSE;
4488         bool remote = FALSE;
4490         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4491                 /* Commits referenced by tags has "^{}" appended. */
4492                 if (name[namelen - 1] != '}')
4493                         return OK;
4495                 while (namelen > 0 && name[namelen] != '^')
4496                         namelen--;
4498                 tag = TRUE;
4499                 namelen -= STRING_SIZE("refs/tags/");
4500                 name    += STRING_SIZE("refs/tags/");
4502         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4503                 remote = TRUE;
4504                 namelen -= STRING_SIZE("refs/remotes/");
4505                 name    += STRING_SIZE("refs/remotes/");
4507         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4508                 namelen -= STRING_SIZE("refs/heads/");
4509                 name    += STRING_SIZE("refs/heads/");
4511         } else if (!strcmp(name, "HEAD")) {
4512                 return OK;
4513         }
4515         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4516         if (!refs)
4517                 return ERR;
4519         ref = &refs[refs_size++];
4520         ref->name = malloc(namelen + 1);
4521         if (!ref->name)
4522                 return ERR;
4524         strncpy(ref->name, name, namelen);
4525         ref->name[namelen] = 0;
4526         ref->tag = tag;
4527         ref->remote = remote;
4528         string_copy_rev(ref->id, id);
4530         return OK;
4533 static int
4534 load_refs(void)
4536         const char *cmd_env = getenv("TIG_LS_REMOTE");
4537         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4539         return read_properties(popen(cmd, "r"), "\t", read_ref);
4542 static int
4543 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4545         if (!strcmp(name, "i18n.commitencoding"))
4546                 string_ncopy(opt_encoding, value, valuelen);
4548         if (!strcmp(name, "core.editor"))
4549                 string_ncopy(opt_editor, value, valuelen);
4551         return OK;
4554 static int
4555 load_repo_config(void)
4557         return read_properties(popen(GIT_CONFIG " --list", "r"),
4558                                "=", read_repo_config_option);
4561 static int
4562 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4564         if (!opt_git_dir[0])
4565                 string_ncopy(opt_git_dir, name, namelen);
4566         else
4567                 string_ncopy(opt_cdup, name, namelen);
4568         return OK;
4571 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4572  * must be the last one! */
4573 static int
4574 load_repo_info(void)
4576         return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4577                                "=", read_repo_info);
4580 static int
4581 read_properties(FILE *pipe, const char *separators,
4582                 int (*read_property)(char *, size_t, char *, size_t))
4584         char buffer[BUFSIZ];
4585         char *name;
4586         int state = OK;
4588         if (!pipe)
4589                 return ERR;
4591         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4592                 char *value;
4593                 size_t namelen;
4594                 size_t valuelen;
4596                 name = chomp_string(name);
4597                 namelen = strcspn(name, separators);
4599                 if (name[namelen]) {
4600                         name[namelen] = 0;
4601                         value = chomp_string(name + namelen + 1);
4602                         valuelen = strlen(value);
4604                 } else {
4605                         value = "";
4606                         valuelen = 0;
4607                 }
4609                 state = read_property(name, namelen, value, valuelen);
4610         }
4612         if (state != ERR && ferror(pipe))
4613                 state = ERR;
4615         pclose(pipe);
4617         return state;
4621 /*
4622  * Main
4623  */
4625 static void __NORETURN
4626 quit(int sig)
4628         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4629         if (cursed)
4630                 endwin();
4631         exit(0);
4634 static void __NORETURN
4635 die(const char *err, ...)
4637         va_list args;
4639         endwin();
4641         va_start(args, err);
4642         fputs("tig: ", stderr);
4643         vfprintf(stderr, err, args);
4644         fputs("\n", stderr);
4645         va_end(args);
4647         exit(1);
4650 int
4651 main(int argc, char *argv[])
4653         struct view *view;
4654         enum request request;
4655         size_t i;
4657         signal(SIGINT, quit);
4659         if (setlocale(LC_ALL, "")) {
4660                 char *codeset = nl_langinfo(CODESET);
4662                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4663         }
4665         if (load_repo_info() == ERR)
4666                 die("Failed to load repo info.");
4668         if (load_options() == ERR)
4669                 die("Failed to load user config.");
4671         /* Load the repo config file so options can be overwritten from
4672          * the command line. */
4673         if (load_repo_config() == ERR)
4674                 die("Failed to load repo config.");
4676         if (!parse_options(argc, argv))
4677                 return 0;
4679         /* Require a git repository unless when running in pager mode. */
4680         if (!opt_git_dir[0])
4681                 die("Not a git repository");
4683         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4684                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4685                 if (opt_iconv == ICONV_NONE)
4686                         die("Failed to initialize character set conversion");
4687         }
4689         if (load_refs() == ERR)
4690                 die("Failed to load refs.");
4692         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4693                 view->cmd_env = getenv(view->cmd_env);
4695         request = opt_request;
4697         init_display();
4699         while (view_driver(display[current_view], request)) {
4700                 int key;
4701                 int i;
4703                 foreach_view (view, i)
4704                         update_view(view);
4706                 /* Refresh, accept single keystroke of input */
4707                 key = wgetch(status_win);
4709                 /* wgetch() with nodelay() enabled returns ERR when there's no
4710                  * input. */
4711                 if (key == ERR) {
4712                         request = REQ_NONE;
4713                         continue;
4714                 }
4716                 request = get_keybinding(display[current_view]->keymap, key);
4718                 /* Some low-level request handling. This keeps access to
4719                  * status_win restricted. */
4720                 switch (request) {
4721                 case REQ_PROMPT:
4722                 {
4723                         char *cmd = read_prompt(":");
4725                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4726                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4727                                         opt_request = REQ_VIEW_DIFF;
4728                                 } else {
4729                                         opt_request = REQ_VIEW_PAGER;
4730                                 }
4731                                 break;
4732                         }
4734                         request = REQ_NONE;
4735                         break;
4736                 }
4737                 case REQ_SEARCH:
4738                 case REQ_SEARCH_BACK:
4739                 {
4740                         const char *prompt = request == REQ_SEARCH
4741                                            ? "/" : "?";
4742                         char *search = read_prompt(prompt);
4744                         if (search)
4745                                 string_ncopy(opt_search, search, strlen(search));
4746                         else
4747                                 request = REQ_NONE;
4748                         break;
4749                 }
4750                 case REQ_SCREEN_RESIZE:
4751                 {
4752                         int height, width;
4754                         getmaxyx(stdscr, height, width);
4756                         /* Resize the status view and let the view driver take
4757                          * care of resizing the displayed views. */
4758                         wresize(status_win, 1, width);
4759                         mvwin(status_win, height - 1, 0);
4760                         wrefresh(status_win);
4761                         break;
4762                 }
4763                 default:
4764                         break;
4765                 }
4766         }
4768         quit(0);
4770         return 0;