Code

Ignore REQ_NONE in the help view and improve unbound request handling
[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 == REQ_NONE)
2644                         continue;
2646                 if (!req_info[i].request) {
2647                         add_line_text(view, "", LINE_DEFAULT);
2648                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2649                         continue;
2650                 }
2652                 key = get_key(req_info[i].request);
2653                 if (!*key)
2654                         key = "(no key defined)";
2656                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2657                         continue;
2659                 add_line_text(view, buf, LINE_DEFAULT);
2660         }
2662         return TRUE;
2665 static struct view_ops help_ops = {
2666         "line",
2667         help_open,
2668         NULL,
2669         pager_draw,
2670         pager_request,
2671         pager_grep,
2672         pager_select,
2673 };
2676 /*
2677  * Tree backend
2678  */
2680 struct tree_stack_entry {
2681         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2682         unsigned long lineno;           /* Line number to restore */
2683         char *name;                     /* Position of name in opt_path */
2684 };
2686 /* The top of the path stack. */
2687 static struct tree_stack_entry *tree_stack = NULL;
2688 unsigned long tree_lineno = 0;
2690 static void
2691 pop_tree_stack_entry(void)
2693         struct tree_stack_entry *entry = tree_stack;
2695         tree_lineno = entry->lineno;
2696         entry->name[0] = 0;
2697         tree_stack = entry->prev;
2698         free(entry);
2701 static void
2702 push_tree_stack_entry(char *name, unsigned long lineno)
2704         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2705         size_t pathlen = strlen(opt_path);
2707         if (!entry)
2708                 return;
2710         entry->prev = tree_stack;
2711         entry->name = opt_path + pathlen;
2712         tree_stack = entry;
2714         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2715                 pop_tree_stack_entry();
2716                 return;
2717         }
2719         /* Move the current line to the first tree entry. */
2720         tree_lineno = 1;
2721         entry->lineno = lineno;
2724 /* Parse output from git-ls-tree(1):
2725  *
2726  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2727  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2728  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2729  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2730  */
2732 #define SIZEOF_TREE_ATTR \
2733         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2735 #define TREE_UP_FORMAT "040000 tree %s\t.."
2737 static int
2738 tree_compare_entry(enum line_type type1, char *name1,
2739                    enum line_type type2, char *name2)
2741         if (type1 != type2) {
2742                 if (type1 == LINE_TREE_DIR)
2743                         return -1;
2744                 return 1;
2745         }
2747         return strcmp(name1, name2);
2750 static bool
2751 tree_read(struct view *view, char *text)
2753         size_t textlen = text ? strlen(text) : 0;
2754         char buf[SIZEOF_STR];
2755         unsigned long pos;
2756         enum line_type type;
2757         bool first_read = view->lines == 0;
2759         if (textlen <= SIZEOF_TREE_ATTR)
2760                 return FALSE;
2762         type = text[STRING_SIZE("100644 ")] == 't'
2763              ? LINE_TREE_DIR : LINE_TREE_FILE;
2765         if (first_read) {
2766                 /* Add path info line */
2767                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2768                     !realloc_lines(view, view->line_size + 1) ||
2769                     !add_line_text(view, buf, LINE_DEFAULT))
2770                         return FALSE;
2772                 /* Insert "link" to parent directory. */
2773                 if (*opt_path) {
2774                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2775                             !realloc_lines(view, view->line_size + 1) ||
2776                             !add_line_text(view, buf, LINE_TREE_DIR))
2777                                 return FALSE;
2778                 }
2779         }
2781         /* Strip the path part ... */
2782         if (*opt_path) {
2783                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2784                 size_t striplen = strlen(opt_path);
2785                 char *path = text + SIZEOF_TREE_ATTR;
2787                 if (pathlen > striplen)
2788                         memmove(path, path + striplen,
2789                                 pathlen - striplen + 1);
2790         }
2792         /* Skip "Directory ..." and ".." line. */
2793         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2794                 struct line *line = &view->line[pos];
2795                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2796                 char *path2 = text + SIZEOF_TREE_ATTR;
2797                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2799                 if (cmp <= 0)
2800                         continue;
2802                 text = strdup(text);
2803                 if (!text)
2804                         return FALSE;
2806                 if (view->lines > pos)
2807                         memmove(&view->line[pos + 1], &view->line[pos],
2808                                 (view->lines - pos) * sizeof(*line));
2810                 line = &view->line[pos];
2811                 line->data = text;
2812                 line->type = type;
2813                 view->lines++;
2814                 return TRUE;
2815         }
2817         if (!add_line_text(view, text, type))
2818                 return FALSE;
2820         if (tree_lineno > view->lineno) {
2821                 view->lineno = tree_lineno;
2822                 tree_lineno = 0;
2823         }
2825         return TRUE;
2828 static enum request
2829 tree_request(struct view *view, enum request request, struct line *line)
2831         enum open_flags flags;
2833         if (request != REQ_ENTER)
2834                 return request;
2836         /* Cleanup the stack if the tree view is at a different tree. */
2837         while (!*opt_path && tree_stack)
2838                 pop_tree_stack_entry();
2840         switch (line->type) {
2841         case LINE_TREE_DIR:
2842                 /* Depending on whether it is a subdir or parent (updir?) link
2843                  * mangle the path buffer. */
2844                 if (line == &view->line[1] && *opt_path) {
2845                         pop_tree_stack_entry();
2847                 } else {
2848                         char *data = line->data;
2849                         char *basename = data + SIZEOF_TREE_ATTR;
2851                         push_tree_stack_entry(basename, view->lineno);
2852                 }
2854                 /* Trees and subtrees share the same ID, so they are not not
2855                  * unique like blobs. */
2856                 flags = OPEN_RELOAD;
2857                 request = REQ_VIEW_TREE;
2858                 break;
2860         case LINE_TREE_FILE:
2861                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2862                 request = REQ_VIEW_BLOB;
2863                 break;
2865         default:
2866                 return TRUE;
2867         }
2869         open_view(view, request, flags);
2870         if (request == REQ_VIEW_TREE) {
2871                 view->lineno = tree_lineno;
2872         }
2874         return REQ_NONE;
2877 static void
2878 tree_select(struct view *view, struct line *line)
2880         char *text = line->data + STRING_SIZE("100644 blob ");
2882         if (line->type == LINE_TREE_FILE) {
2883                 string_copy_rev(ref_blob, text);
2885         } else if (line->type != LINE_TREE_DIR) {
2886                 return;
2887         }
2889         string_copy_rev(view->ref, text);
2892 static struct view_ops tree_ops = {
2893         "file",
2894         NULL,
2895         tree_read,
2896         pager_draw,
2897         tree_request,
2898         pager_grep,
2899         tree_select,
2900 };
2902 static bool
2903 blob_read(struct view *view, char *line)
2905         return add_line_text(view, line, LINE_DEFAULT) != NULL;
2908 static struct view_ops blob_ops = {
2909         "line",
2910         NULL,
2911         blob_read,
2912         pager_draw,
2913         pager_request,
2914         pager_grep,
2915         pager_select,
2916 };
2919 /*
2920  * Status backend
2921  */
2923 struct status {
2924         char status;
2925         struct {
2926                 mode_t mode;
2927                 char rev[SIZEOF_REV];
2928         } old;
2929         struct {
2930                 mode_t mode;
2931                 char rev[SIZEOF_REV];
2932         } new;
2933         char name[SIZEOF_STR];
2934 };
2936 static struct status stage_status;
2937 static enum line_type stage_line_type;
2939 /* Get fields from the diff line:
2940  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2941  */
2942 static inline bool
2943 status_get_diff(struct status *file, char *buf, size_t bufsize)
2945         char *old_mode = buf +  1;
2946         char *new_mode = buf +  8;
2947         char *old_rev  = buf + 15;
2948         char *new_rev  = buf + 56;
2949         char *status   = buf + 97;
2951         if (bufsize != 99 ||
2952             old_mode[-1] != ':' ||
2953             new_mode[-1] != ' ' ||
2954             old_rev[-1]  != ' ' ||
2955             new_rev[-1]  != ' ' ||
2956             status[-1]   != ' ')
2957                 return FALSE;
2959         file->status = *status;
2961         string_copy_rev(file->old.rev, old_rev);
2962         string_copy_rev(file->new.rev, new_rev);
2964         file->old.mode = strtoul(old_mode, NULL, 8);
2965         file->new.mode = strtoul(new_mode, NULL, 8);
2967         file->name[0] = 0;
2969         return TRUE;
2972 static bool
2973 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2975         struct status *file = NULL;
2976         char buf[SIZEOF_STR * 4];
2977         size_t bufsize = 0;
2978         FILE *pipe;
2980         pipe = popen(cmd, "r");
2981         if (!pipe)
2982                 return FALSE;
2984         add_line_data(view, NULL, type);
2986         while (!feof(pipe) && !ferror(pipe)) {
2987                 char *sep;
2988                 size_t readsize;
2990                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2991                 if (!readsize)
2992                         break;
2993                 bufsize += readsize;
2995                 /* Process while we have NUL chars. */
2996                 while ((sep = memchr(buf, 0, bufsize))) {
2997                         size_t sepsize = sep - buf + 1;
2999                         if (!file) {
3000                                 if (!realloc_lines(view, view->line_size + 1))
3001                                         goto error_out;
3003                                 file = calloc(1, sizeof(*file));
3004                                 if (!file)
3005                                         goto error_out;
3007                                 add_line_data(view, file, type);
3008                         }
3010                         /* Parse diff info part. */
3011                         if (!diff) {
3012                                 file->status = '?';
3014                         } else if (!file->status) {
3015                                 if (!status_get_diff(file, buf, sepsize))
3016                                         goto error_out;
3018                                 bufsize -= sepsize;
3019                                 memmove(buf, sep + 1, bufsize);
3021                                 sep = memchr(buf, 0, bufsize);
3022                                 if (!sep)
3023                                         break;
3024                                 sepsize = sep - buf + 1;
3025                         }
3027                         /* git-ls-files just delivers a NUL separated
3028                          * list of file names similar to the second half
3029                          * of the git-diff-* output. */
3030                         string_ncopy(file->name, buf, sepsize);
3031                         bufsize -= sepsize;
3032                         memmove(buf, sep + 1, bufsize);
3033                         file = NULL;
3034                 }
3035         }
3037         if (ferror(pipe)) {
3038 error_out:
3039                 pclose(pipe);
3040                 return FALSE;
3041         }
3043         if (!view->line[view->lines - 1].data)
3044                 add_line_data(view, NULL, LINE_STAT_NONE);
3046         pclose(pipe);
3047         return TRUE;
3050 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3051 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3052 #define STATUS_LIST_OTHER_CMD \
3053         "git ls-files -z --others --exclude-per-directory=.gitignore"
3055 #define STATUS_DIFF_SHOW_CMD \
3056         "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3058 /* First parse staged info using git-diff-index(1), then parse unstaged
3059  * info using git-diff-files(1), and finally untracked files using
3060  * git-ls-files(1). */
3061 static bool
3062 status_open(struct view *view)
3064         struct stat statbuf;
3065         char exclude[SIZEOF_STR];
3066         char cmd[SIZEOF_STR];
3067         unsigned long prev_lineno = view->lineno;
3068         size_t i;
3071         for (i = 0; i < view->lines; i++)
3072                 free(view->line[i].data);
3073         free(view->line);
3074         view->lines = view->line_size = view->lineno = 0;
3075         view->line = NULL;
3077         if (!realloc_lines(view, view->line_size + 6))
3078                 return FALSE;
3080         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3081                 return FALSE;
3083         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3085         if (stat(exclude, &statbuf) >= 0) {
3086                 size_t cmdsize = strlen(cmd);
3088                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3089                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3090                         return FALSE;
3091         }
3093         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3094             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3095             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3096                 return FALSE;
3098         /* If all went well restore the previous line number to stay in
3099          * the context. */
3100         if (prev_lineno < view->lines)
3101                 view->lineno = prev_lineno;
3102         else
3103                 view->lineno = view->lines - 1;
3105         return TRUE;
3108 static bool
3109 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3111         struct status *status = line->data;
3113         wmove(view->win, lineno, 0);
3115         if (selected) {
3116                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3117                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3119         } else if (!status && line->type != LINE_STAT_NONE) {
3120                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3121                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3123         } else {
3124                 wattrset(view->win, get_line_attr(line->type));
3125         }
3127         if (!status) {
3128                 char *text;
3130                 switch (line->type) {
3131                 case LINE_STAT_STAGED:
3132                         text = "Changes to be committed:";
3133                         break;
3135                 case LINE_STAT_UNSTAGED:
3136                         text = "Changed but not updated:";
3137                         break;
3139                 case LINE_STAT_UNTRACKED:
3140                         text = "Untracked files:";
3141                         break;
3143                 case LINE_STAT_NONE:
3144                         text = "    (no files)";
3145                         break;
3147                 default:
3148                         return FALSE;
3149                 }
3151                 waddstr(view->win, text);
3152                 return TRUE;
3153         }
3155         waddch(view->win, status->status);
3156         if (!selected)
3157                 wattrset(view->win, A_NORMAL);
3158         wmove(view->win, lineno, 4);
3159         waddstr(view->win, status->name);
3161         return TRUE;
3164 static enum request
3165 status_enter(struct view *view, struct line *line)
3167         struct status *status = line->data;
3168         char path[SIZEOF_STR] = "";
3169         char *info;
3170         size_t cmdsize = 0;
3172         if (line->type == LINE_STAT_NONE ||
3173             (!status && line[1].type == LINE_STAT_NONE)) {
3174                 report("No file to diff");
3175                 return REQ_NONE;
3176         }
3178         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3179                 return REQ_QUIT;
3181         if (opt_cdup[0] &&
3182             line->type != LINE_STAT_UNTRACKED &&
3183             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3184                 return REQ_QUIT;
3186         switch (line->type) {
3187         case LINE_STAT_STAGED:
3188                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3189                                         "--cached", path))
3190                         return REQ_QUIT;
3191                 if (status)
3192                         info = "Staged changes to %s";
3193                 else
3194                         info = "Staged changes";
3195                 break;
3197         case LINE_STAT_UNSTAGED:
3198                 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3199                                         "", path))
3200                         return REQ_QUIT;
3201                 if (status)
3202                         info = "Unstaged changes to %s";
3203                 else
3204                         info = "Unstaged changes";
3205                 break;
3207         case LINE_STAT_UNTRACKED:
3208                 if (opt_pipe)
3209                         return REQ_QUIT;
3212                 if (!status) {
3213                         report("No file to show");
3214                         return REQ_NONE;
3215                 }
3217                 opt_pipe = fopen(status->name, "r");
3218                 info = "Untracked file %s";
3219                 break;
3221         default:
3222                 die("w00t");
3223         }
3225         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3226         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3227                 if (status) {
3228                         stage_status = *status;
3229                 } else {
3230                         memset(&stage_status, 0, sizeof(stage_status));
3231                 }
3233                 stage_line_type = line->type;
3234                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3235         }
3237         return REQ_NONE;
3241 static bool
3242 status_update_file(struct view *view, struct status *status, enum line_type type)
3244         char cmd[SIZEOF_STR];
3245         char buf[SIZEOF_STR];
3246         size_t cmdsize = 0;
3247         size_t bufsize = 0;
3248         size_t written = 0;
3249         FILE *pipe;
3251         if (opt_cdup[0] &&
3252             type != LINE_STAT_UNTRACKED &&
3253             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3254                 return FALSE;
3256         switch (type) {
3257         case LINE_STAT_STAGED:
3258                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3259                                         status->old.mode,
3260                                         status->old.rev,
3261                                         status->name, 0))
3262                         return FALSE;
3264                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3265                 break;
3267         case LINE_STAT_UNSTAGED:
3268         case LINE_STAT_UNTRACKED:
3269                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3270                         return FALSE;
3272                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3273                 break;
3275         default:
3276                 die("w00t");
3277         }
3279         pipe = popen(cmd, "w");
3280         if (!pipe)
3281                 return FALSE;
3283         while (!ferror(pipe) && written < bufsize) {
3284                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3285         }
3287         pclose(pipe);
3289         if (written != bufsize)
3290                 return FALSE;
3292         return TRUE;
3295 static void
3296 status_update(struct view *view)
3298         struct line *line = &view->line[view->lineno];
3300         assert(view->lines);
3302         if (!line->data) {
3303                 while (++line < view->line + view->lines && line->data) {
3304                         if (!status_update_file(view, line->data, line->type))
3305                                 report("Failed to update file status");
3306                 }
3308                 if (!line[-1].data) {
3309                         report("Nothing to update");
3310                         return;
3311                 }
3313         } else if (!status_update_file(view, line->data, line->type)) {
3314                 report("Failed to update file status");
3315         }
3317         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3320 static enum request
3321 status_request(struct view *view, enum request request, struct line *line)
3323         struct status *status = line->data;
3325         switch (request) {
3326         case REQ_STATUS_UPDATE:
3327                 status_update(view);
3328                 break;
3330         case REQ_EDIT:
3331                 if (!status)
3332                         return request;
3334                 open_editor(status->status != '?', status->name);
3335                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3336                 break;
3338         case REQ_ENTER:
3339                 status_enter(view, line);
3340                 break;
3342         case REQ_REFRESH:
3343                 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3344                 break;
3346         default:
3347                 return request;
3348         }
3350         return REQ_NONE;
3353 static void
3354 status_select(struct view *view, struct line *line)
3356         struct status *status = line->data;
3357         char file[SIZEOF_STR] = "all files";
3358         char *text;
3360         if (status && !string_format(file, "'%s'", status->name))
3361                 return;
3363         if (!status && line[1].type == LINE_STAT_NONE)
3364                 line++;
3366         switch (line->type) {
3367         case LINE_STAT_STAGED:
3368                 text = "Press %s to unstage %s for commit";
3369                 break;
3371         case LINE_STAT_UNSTAGED:
3372                 text = "Press %s to stage %s for commit";
3373                 break;
3375         case LINE_STAT_UNTRACKED:
3376                 text = "Press %s to stage %s for addition";
3377                 break;
3379         case LINE_STAT_NONE:
3380                 text = "Nothing to update";
3381                 break;
3383         default:
3384                 die("w00t");
3385         }
3387         string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3390 static bool
3391 status_grep(struct view *view, struct line *line)
3393         struct status *status = line->data;
3394         enum { S_STATUS, S_NAME, S_END } state;
3395         char buf[2] = "?";
3396         regmatch_t pmatch;
3398         if (!status)
3399                 return FALSE;
3401         for (state = S_STATUS; state < S_END; state++) {
3402                 char *text;
3404                 switch (state) {
3405                 case S_NAME:    text = status->name;    break;
3406                 case S_STATUS:
3407                         buf[0] = status->status;
3408                         text = buf;
3409                         break;
3411                 default:
3412                         return FALSE;
3413                 }
3415                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3416                         return TRUE;
3417         }
3419         return FALSE;
3422 static struct view_ops status_ops = {
3423         "file",
3424         status_open,
3425         NULL,
3426         status_draw,
3427         status_request,
3428         status_grep,
3429         status_select,
3430 };
3433 static bool
3434 stage_diff_line(FILE *pipe, struct line *line)
3436         char *buf = line->data;
3437         size_t bufsize = strlen(buf);
3438         size_t written = 0;
3440         while (!ferror(pipe) && written < bufsize) {
3441                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3442         }
3444         fputc('\n', pipe);
3446         return written == bufsize;
3449 static struct line *
3450 stage_diff_hdr(struct view *view, struct line *line)
3452         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3453         struct line *diff_hdr;
3455         if (line->type == LINE_DIFF_CHUNK)
3456                 diff_hdr = line - 1;
3457         else
3458                 diff_hdr = view->line + 1;
3460         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3461                 if (diff_hdr->type == LINE_DIFF_HEADER)
3462                         return diff_hdr;
3464                 diff_hdr += diff_hdr_dir;
3465         }
3467         return NULL;
3470 static bool
3471 stage_update_chunk(struct view *view, struct line *line)
3473         char cmd[SIZEOF_STR];
3474         size_t cmdsize = 0;
3475         struct line *diff_hdr, *diff_chunk, *diff_end;
3476         FILE *pipe;
3478         diff_hdr = stage_diff_hdr(view, line);
3479         if (!diff_hdr)
3480                 return FALSE;
3482         if (opt_cdup[0] &&
3483             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3484                 return FALSE;
3486         if (!string_format_from(cmd, &cmdsize,
3487                                 "git apply --cached %s - && "
3488                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3489                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3490                 return FALSE;
3492         pipe = popen(cmd, "w");
3493         if (!pipe)
3494                 return FALSE;
3496         diff_end = view->line + view->lines;
3497         if (line->type != LINE_DIFF_CHUNK) {
3498                 diff_chunk = diff_hdr;
3500         } else {
3501                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3502                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3503                             diff_chunk->type == LINE_DIFF_HEADER)
3504                                 diff_end = diff_chunk;
3506                 diff_chunk = line;
3508                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3509                         switch (diff_hdr->type) {
3510                         case LINE_DIFF_HEADER:
3511                         case LINE_DIFF_INDEX:
3512                         case LINE_DIFF_ADD:
3513                         case LINE_DIFF_DEL:
3514                                 break;
3516                         default:
3517                                 diff_hdr++;
3518                                 continue;
3519                         }
3521                         if (!stage_diff_line(pipe, diff_hdr++)) {
3522                                 pclose(pipe);
3523                                 return FALSE;
3524                         }
3525                 }
3526         }
3528         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3529                 diff_chunk++;
3531         pclose(pipe);
3533         if (diff_chunk != diff_end)
3534                 return FALSE;
3536         return TRUE;
3539 static void
3540 stage_update(struct view *view, struct line *line)
3542         if (stage_line_type != LINE_STAT_UNTRACKED &&
3543             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3544                 if (!stage_update_chunk(view, line)) {
3545                         report("Failed to apply chunk");
3546                         return;
3547                 }
3549         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3550                 report("Failed to update file");
3551                 return;
3552         }
3554         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3556         view = VIEW(REQ_VIEW_STATUS);
3557         if (view_is_displayed(view))
3558                 status_enter(view, &view->line[view->lineno]);
3561 static enum request
3562 stage_request(struct view *view, enum request request, struct line *line)
3564         switch (request) {
3565         case REQ_STATUS_UPDATE:
3566                 stage_update(view, line);
3567                 break;
3569         case REQ_EDIT:
3570                 if (!stage_status.name[0])
3571                         return request;
3573                 open_editor(stage_status.status != '?', stage_status.name);
3574                 break;
3576         case REQ_ENTER:
3577                 pager_request(view, request, line);
3578                 break;
3580         default:
3581                 return request;
3582         }
3584         return REQ_NONE;
3587 static struct view_ops stage_ops = {
3588         "line",
3589         NULL,
3590         pager_read,
3591         pager_draw,
3592         stage_request,
3593         pager_grep,
3594         pager_select,
3595 };
3598 /*
3599  * Revision graph
3600  */
3602 struct commit {
3603         char id[SIZEOF_REV];            /* SHA1 ID. */
3604         char title[128];                /* First line of the commit message. */
3605         char author[75];                /* Author of the commit. */
3606         struct tm time;                 /* Date from the author ident. */
3607         struct ref **refs;              /* Repository references. */
3608         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3609         size_t graph_size;              /* The width of the graph array. */
3610 };
3612 /* Size of rev graph with no  "padding" columns */
3613 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3615 struct rev_graph {
3616         struct rev_graph *prev, *next, *parents;
3617         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3618         size_t size;
3619         struct commit *commit;
3620         size_t pos;
3621 };
3623 /* Parents of the commit being visualized. */
3624 static struct rev_graph graph_parents[4];
3626 /* The current stack of revisions on the graph. */
3627 static struct rev_graph graph_stacks[4] = {
3628         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3629         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3630         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3631         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3632 };
3634 static inline bool
3635 graph_parent_is_merge(struct rev_graph *graph)
3637         return graph->parents->size > 1;
3640 static inline void
3641 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3643         struct commit *commit = graph->commit;
3645         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3646                 commit->graph[commit->graph_size++] = symbol;
3649 static void
3650 done_rev_graph(struct rev_graph *graph)
3652         if (graph_parent_is_merge(graph) &&
3653             graph->pos < graph->size - 1 &&
3654             graph->next->size == graph->size + graph->parents->size - 1) {
3655                 size_t i = graph->pos + graph->parents->size - 1;
3657                 graph->commit->graph_size = i * 2;
3658                 while (i < graph->next->size - 1) {
3659                         append_to_rev_graph(graph, ' ');
3660                         append_to_rev_graph(graph, '\\');
3661                         i++;
3662                 }
3663         }
3665         graph->size = graph->pos = 0;
3666         graph->commit = NULL;
3667         memset(graph->parents, 0, sizeof(*graph->parents));
3670 static void
3671 push_rev_graph(struct rev_graph *graph, char *parent)
3673         int i;
3675         /* "Collapse" duplicate parents lines.
3676          *
3677          * FIXME: This needs to also update update the drawn graph but
3678          * for now it just serves as a method for pruning graph lines. */
3679         for (i = 0; i < graph->size; i++)
3680                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3681                         return;
3683         if (graph->size < SIZEOF_REVITEMS) {
3684                 string_copy_rev(graph->rev[graph->size++], parent);
3685         }
3688 static chtype
3689 get_rev_graph_symbol(struct rev_graph *graph)
3691         chtype symbol;
3693         if (graph->parents->size == 0)
3694                 symbol = REVGRAPH_INIT;
3695         else if (graph_parent_is_merge(graph))
3696                 symbol = REVGRAPH_MERGE;
3697         else if (graph->pos >= graph->size)
3698                 symbol = REVGRAPH_BRANCH;
3699         else
3700                 symbol = REVGRAPH_COMMIT;
3702         return symbol;
3705 static void
3706 draw_rev_graph(struct rev_graph *graph)
3708         struct rev_filler {
3709                 chtype separator, line;
3710         };
3711         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3712         static struct rev_filler fillers[] = {
3713                 { ' ',  REVGRAPH_LINE },
3714                 { '`',  '.' },
3715                 { '\'', ' ' },
3716                 { '/',  ' ' },
3717         };
3718         chtype symbol = get_rev_graph_symbol(graph);
3719         struct rev_filler *filler;
3720         size_t i;
3722         filler = &fillers[DEFAULT];
3724         for (i = 0; i < graph->pos; i++) {
3725                 append_to_rev_graph(graph, filler->line);
3726                 if (graph_parent_is_merge(graph->prev) &&
3727                     graph->prev->pos == i)
3728                         filler = &fillers[RSHARP];
3730                 append_to_rev_graph(graph, filler->separator);
3731         }
3733         /* Place the symbol for this revision. */
3734         append_to_rev_graph(graph, symbol);
3736         if (graph->prev->size > graph->size)
3737                 filler = &fillers[RDIAG];
3738         else
3739                 filler = &fillers[DEFAULT];
3741         i++;
3743         for (; i < graph->size; i++) {
3744                 append_to_rev_graph(graph, filler->separator);
3745                 append_to_rev_graph(graph, filler->line);
3746                 if (graph_parent_is_merge(graph->prev) &&
3747                     i < graph->prev->pos + graph->parents->size)
3748                         filler = &fillers[RSHARP];
3749                 if (graph->prev->size > graph->size)
3750                         filler = &fillers[LDIAG];
3751         }
3753         if (graph->prev->size > graph->size) {
3754                 append_to_rev_graph(graph, filler->separator);
3755                 if (filler->line != ' ')
3756                         append_to_rev_graph(graph, filler->line);
3757         }
3760 /* Prepare the next rev graph */
3761 static void
3762 prepare_rev_graph(struct rev_graph *graph)
3764         size_t i;
3766         /* First, traverse all lines of revisions up to the active one. */
3767         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3768                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3769                         break;
3771                 push_rev_graph(graph->next, graph->rev[graph->pos]);
3772         }
3774         /* Interleave the new revision parent(s). */
3775         for (i = 0; i < graph->parents->size; i++)
3776                 push_rev_graph(graph->next, graph->parents->rev[i]);
3778         /* Lastly, put any remaining revisions. */
3779         for (i = graph->pos + 1; i < graph->size; i++)
3780                 push_rev_graph(graph->next, graph->rev[i]);
3783 static void
3784 update_rev_graph(struct rev_graph *graph)
3786         /* If this is the finalizing update ... */
3787         if (graph->commit)
3788                 prepare_rev_graph(graph);
3790         /* Graph visualization needs a one rev look-ahead,
3791          * so the first update doesn't visualize anything. */
3792         if (!graph->prev->commit)
3793                 return;
3795         draw_rev_graph(graph->prev);
3796         done_rev_graph(graph->prev->prev);
3800 /*
3801  * Main view backend
3802  */
3804 static bool
3805 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3807         char buf[DATE_COLS + 1];
3808         struct commit *commit = line->data;
3809         enum line_type type;
3810         int col = 0;
3811         size_t timelen;
3812         size_t authorlen;
3813         int trimmed = 1;
3815         if (!*commit->author)
3816                 return FALSE;
3818         wmove(view->win, lineno, col);
3820         if (selected) {
3821                 type = LINE_CURSOR;
3822                 wattrset(view->win, get_line_attr(type));
3823                 wchgat(view->win, -1, 0, type, NULL);
3825         } else {
3826                 type = LINE_MAIN_COMMIT;
3827                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3828         }
3830         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3831         waddnstr(view->win, buf, timelen);
3832         waddstr(view->win, " ");
3834         col += DATE_COLS;
3835         wmove(view->win, lineno, col);
3836         if (type != LINE_CURSOR)
3837                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3839         if (opt_utf8) {
3840                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3841         } else {
3842                 authorlen = strlen(commit->author);
3843                 if (authorlen > AUTHOR_COLS - 2) {
3844                         authorlen = AUTHOR_COLS - 2;
3845                         trimmed = 1;
3846                 }
3847         }
3849         if (trimmed) {
3850                 waddnstr(view->win, commit->author, authorlen);
3851                 if (type != LINE_CURSOR)
3852                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3853                 waddch(view->win, '~');
3854         } else {
3855                 waddstr(view->win, commit->author);
3856         }
3858         col += AUTHOR_COLS;
3859         if (type != LINE_CURSOR)
3860                 wattrset(view->win, A_NORMAL);
3862         if (opt_rev_graph && commit->graph_size) {
3863                 size_t i;
3865                 wmove(view->win, lineno, col);
3866                 /* Using waddch() instead of waddnstr() ensures that
3867                  * they'll be rendered correctly for the cursor line. */
3868                 for (i = 0; i < commit->graph_size; i++)
3869                         waddch(view->win, commit->graph[i]);
3871                 waddch(view->win, ' ');
3872                 col += commit->graph_size + 1;
3873         }
3875         wmove(view->win, lineno, col);
3877         if (commit->refs) {
3878                 size_t i = 0;
3880                 do {
3881                         if (type == LINE_CURSOR)
3882                                 ;
3883                         else if (commit->refs[i]->tag)
3884                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3885                         else if (commit->refs[i]->remote)
3886                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3887                         else
3888                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3889                         waddstr(view->win, "[");
3890                         waddstr(view->win, commit->refs[i]->name);
3891                         waddstr(view->win, "]");
3892                         if (type != LINE_CURSOR)
3893                                 wattrset(view->win, A_NORMAL);
3894                         waddstr(view->win, " ");
3895                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3896                 } while (commit->refs[i++]->next);
3897         }
3899         if (type != LINE_CURSOR)
3900                 wattrset(view->win, get_line_attr(type));
3902         {
3903                 int titlelen = strlen(commit->title);
3905                 if (col + titlelen > view->width)
3906                         titlelen = view->width - col;
3908                 waddnstr(view->win, commit->title, titlelen);
3909         }
3911         return TRUE;
3914 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3915 static bool
3916 main_read(struct view *view, char *line)
3918         static struct rev_graph *graph = graph_stacks;
3919         enum line_type type;
3920         struct commit *commit;
3922         if (!line) {
3923                 update_rev_graph(graph);
3924                 return TRUE;
3925         }
3927         type = get_line_type(line);
3928         if (type == LINE_COMMIT) {
3929                 commit = calloc(1, sizeof(struct commit));
3930                 if (!commit)
3931                         return FALSE;
3933                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3934                 commit->refs = get_refs(commit->id);
3935                 graph->commit = commit;
3936                 add_line_data(view, commit, LINE_MAIN_COMMIT);
3937                 return TRUE;
3938         }
3940         if (!view->lines)
3941                 return TRUE;
3942         commit = view->line[view->lines - 1].data;
3944         switch (type) {
3945         case LINE_PARENT:
3946                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3947                 break;
3949         case LINE_AUTHOR:
3950         {
3951                 /* Parse author lines where the name may be empty:
3952                  *      author  <email@address.tld> 1138474660 +0100
3953                  */
3954                 char *ident = line + STRING_SIZE("author ");
3955                 char *nameend = strchr(ident, '<');
3956                 char *emailend = strchr(ident, '>');
3958                 if (!nameend || !emailend)
3959                         break;
3961                 update_rev_graph(graph);
3962                 graph = graph->next;
3964                 *nameend = *emailend = 0;
3965                 ident = chomp_string(ident);
3966                 if (!*ident) {
3967                         ident = chomp_string(nameend + 1);
3968                         if (!*ident)
3969                                 ident = "Unknown";
3970                 }
3972                 string_ncopy(commit->author, ident, strlen(ident));
3974                 /* Parse epoch and timezone */
3975                 if (emailend[1] == ' ') {
3976                         char *secs = emailend + 2;
3977                         char *zone = strchr(secs, ' ');
3978                         time_t time = (time_t) atol(secs);
3980                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3981                                 long tz;
3983                                 zone++;
3984                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
3985                                 tz += ('0' - zone[2]) * 60 * 60;
3986                                 tz += ('0' - zone[3]) * 60;
3987                                 tz += ('0' - zone[4]) * 60;
3989                                 if (zone[0] == '-')
3990                                         tz = -tz;
3992                                 time -= tz;
3993                         }
3995                         gmtime_r(&time, &commit->time);
3996                 }
3997                 break;
3998         }
3999         default:
4000                 /* Fill in the commit title if it has not already been set. */
4001                 if (commit->title[0])
4002                         break;
4004                 /* Require titles to start with a non-space character at the
4005                  * offset used by git log. */
4006                 if (strncmp(line, "    ", 4))
4007                         break;
4008                 line += 4;
4009                 /* Well, if the title starts with a whitespace character,
4010                  * try to be forgiving.  Otherwise we end up with no title. */
4011                 while (isspace(*line))
4012                         line++;
4013                 if (*line == '\0')
4014                         break;
4015                 /* FIXME: More graceful handling of titles; append "..." to
4016                  * shortened titles, etc. */
4018                 string_ncopy(commit->title, line, strlen(line));
4019         }
4021         return TRUE;
4024 static void
4025 cherry_pick_commit(struct commit *commit)
4027         char cmd[SIZEOF_STR];
4028         char *cherry_pick = getenv("TIG_CHERRY_PICK");
4030         if (!cherry_pick)
4031                 cherry_pick = "git cherry-pick";
4033         if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4034                 def_prog_mode();           /* save current tty modes */
4035                 endwin();                  /* restore original tty modes */
4036                 system(cmd);
4037                 fprintf(stderr, "Press Enter to continue");
4038                 getc(stdin);
4039                 reset_prog_mode();
4040                 redraw_display();
4041         }
4044 static enum request
4045 main_request(struct view *view, enum request request, struct line *line)
4047         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4049         if (request == REQ_ENTER)
4050                 open_view(view, REQ_VIEW_DIFF, flags);
4051         else if (request == REQ_CHERRY_PICK)
4052                 cherry_pick_commit(line->data);
4053         else
4054                 return request;
4056         return REQ_NONE;
4059 static bool
4060 main_grep(struct view *view, struct line *line)
4062         struct commit *commit = line->data;
4063         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4064         char buf[DATE_COLS + 1];
4065         regmatch_t pmatch;
4067         for (state = S_TITLE; state < S_END; state++) {
4068                 char *text;
4070                 switch (state) {
4071                 case S_TITLE:   text = commit->title;   break;
4072                 case S_AUTHOR:  text = commit->author;  break;
4073                 case S_DATE:
4074                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4075                                 continue;
4076                         text = buf;
4077                         break;
4079                 default:
4080                         return FALSE;
4081                 }
4083                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4084                         return TRUE;
4085         }
4087         return FALSE;
4090 static void
4091 main_select(struct view *view, struct line *line)
4093         struct commit *commit = line->data;
4095         string_copy_rev(view->ref, commit->id);
4096         string_copy_rev(ref_commit, view->ref);
4099 static struct view_ops main_ops = {
4100         "commit",
4101         NULL,
4102         main_read,
4103         main_draw,
4104         main_request,
4105         main_grep,
4106         main_select,
4107 };
4110 /*
4111  * Unicode / UTF-8 handling
4112  *
4113  * NOTE: Much of the following code for dealing with unicode is derived from
4114  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4115  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4116  */
4118 /* I've (over)annotated a lot of code snippets because I am not entirely
4119  * confident that the approach taken by this small UTF-8 interface is correct.
4120  * --jonas */
4122 static inline int
4123 unicode_width(unsigned long c)
4125         if (c >= 0x1100 &&
4126            (c <= 0x115f                         /* Hangul Jamo */
4127             || c == 0x2329
4128             || c == 0x232a
4129             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4130                                                 /* CJK ... Yi */
4131             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4132             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4133             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4134             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4135             || (c >= 0xffe0  && c <= 0xffe6)
4136             || (c >= 0x20000 && c <= 0x2fffd)
4137             || (c >= 0x30000 && c <= 0x3fffd)))
4138                 return 2;
4140         return 1;
4143 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4144  * Illegal bytes are set one. */
4145 static const unsigned char utf8_bytes[256] = {
4146         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,
4147         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,
4148         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,
4149         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,
4150         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,
4151         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,
4152         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,
4153         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,
4154 };
4156 /* Decode UTF-8 multi-byte representation into a unicode character. */
4157 static inline unsigned long
4158 utf8_to_unicode(const char *string, size_t length)
4160         unsigned long unicode;
4162         switch (length) {
4163         case 1:
4164                 unicode  =   string[0];
4165                 break;
4166         case 2:
4167                 unicode  =  (string[0] & 0x1f) << 6;
4168                 unicode +=  (string[1] & 0x3f);
4169                 break;
4170         case 3:
4171                 unicode  =  (string[0] & 0x0f) << 12;
4172                 unicode += ((string[1] & 0x3f) << 6);
4173                 unicode +=  (string[2] & 0x3f);
4174                 break;
4175         case 4:
4176                 unicode  =  (string[0] & 0x0f) << 18;
4177                 unicode += ((string[1] & 0x3f) << 12);
4178                 unicode += ((string[2] & 0x3f) << 6);
4179                 unicode +=  (string[3] & 0x3f);
4180                 break;
4181         case 5:
4182                 unicode  =  (string[0] & 0x0f) << 24;
4183                 unicode += ((string[1] & 0x3f) << 18);
4184                 unicode += ((string[2] & 0x3f) << 12);
4185                 unicode += ((string[3] & 0x3f) << 6);
4186                 unicode +=  (string[4] & 0x3f);
4187                 break;
4188         case 6:
4189                 unicode  =  (string[0] & 0x01) << 30;
4190                 unicode += ((string[1] & 0x3f) << 24);
4191                 unicode += ((string[2] & 0x3f) << 18);
4192                 unicode += ((string[3] & 0x3f) << 12);
4193                 unicode += ((string[4] & 0x3f) << 6);
4194                 unicode +=  (string[5] & 0x3f);
4195                 break;
4196         default:
4197                 die("Invalid unicode length");
4198         }
4200         /* Invalid characters could return the special 0xfffd value but NUL
4201          * should be just as good. */
4202         return unicode > 0xffff ? 0 : unicode;
4205 /* Calculates how much of string can be shown within the given maximum width
4206  * and sets trimmed parameter to non-zero value if all of string could not be
4207  * shown.
4208  *
4209  * Additionally, adds to coloffset how many many columns to move to align with
4210  * the expected position. Takes into account how multi-byte and double-width
4211  * characters will effect the cursor position.
4212  *
4213  * Returns the number of bytes to output from string to satisfy max_width. */
4214 static size_t
4215 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4217         const char *start = string;
4218         const char *end = strchr(string, '\0');
4219         size_t mbwidth = 0;
4220         size_t width = 0;
4222         *trimmed = 0;
4224         while (string < end) {
4225                 int c = *(unsigned char *) string;
4226                 unsigned char bytes = utf8_bytes[c];
4227                 size_t ucwidth;
4228                 unsigned long unicode;
4230                 if (string + bytes > end)
4231                         break;
4233                 /* Change representation to figure out whether
4234                  * it is a single- or double-width character. */
4236                 unicode = utf8_to_unicode(string, bytes);
4237                 /* FIXME: Graceful handling of invalid unicode character. */
4238                 if (!unicode)
4239                         break;
4241                 ucwidth = unicode_width(unicode);
4242                 width  += ucwidth;
4243                 if (width > max_width) {
4244                         *trimmed = 1;
4245                         break;
4246                 }
4248                 /* The column offset collects the differences between the
4249                  * number of bytes encoding a character and the number of
4250                  * columns will be used for rendering said character.
4251                  *
4252                  * So if some character A is encoded in 2 bytes, but will be
4253                  * represented on the screen using only 1 byte this will and up
4254                  * adding 1 to the multi-byte column offset.
4255                  *
4256                  * Assumes that no double-width character can be encoding in
4257                  * less than two bytes. */
4258                 if (bytes > ucwidth)
4259                         mbwidth += bytes - ucwidth;
4261                 string  += bytes;
4262         }
4264         *coloffset += mbwidth;
4266         return string - start;
4270 /*
4271  * Status management
4272  */
4274 /* Whether or not the curses interface has been initialized. */
4275 static bool cursed = FALSE;
4277 /* The status window is used for polling keystrokes. */
4278 static WINDOW *status_win;
4280 static bool status_empty = TRUE;
4282 /* Update status and title window. */
4283 static void
4284 report(const char *msg, ...)
4286         struct view *view = display[current_view];
4288         if (input_mode)
4289                 return;
4291         if (!status_empty || *msg) {
4292                 va_list args;
4294                 va_start(args, msg);
4296                 wmove(status_win, 0, 0);
4297                 if (*msg) {
4298                         vwprintw(status_win, msg, args);
4299                         status_empty = FALSE;
4300                 } else {
4301                         status_empty = TRUE;
4302                 }
4303                 wclrtoeol(status_win);
4304                 wrefresh(status_win);
4306                 va_end(args);
4307         }
4309         update_view_title(view);
4310         update_display_cursor(view);
4313 /* Controls when nodelay should be in effect when polling user input. */
4314 static void
4315 set_nonblocking_input(bool loading)
4317         static unsigned int loading_views;
4319         if ((loading == FALSE && loading_views-- == 1) ||
4320             (loading == TRUE  && loading_views++ == 0))
4321                 nodelay(status_win, loading);
4324 static void
4325 init_display(void)
4327         int x, y;
4329         /* Initialize the curses library */
4330         if (isatty(STDIN_FILENO)) {
4331                 cursed = !!initscr();
4332         } else {
4333                 /* Leave stdin and stdout alone when acting as a pager. */
4334                 FILE *io = fopen("/dev/tty", "r+");
4336                 if (!io)
4337                         die("Failed to open /dev/tty");
4338                 cursed = !!newterm(NULL, io, io);
4339         }
4341         if (!cursed)
4342                 die("Failed to initialize curses");
4344         nonl();         /* Tell curses not to do NL->CR/NL on output */
4345         cbreak();       /* Take input chars one at a time, no wait for \n */
4346         noecho();       /* Don't echo input */
4347         leaveok(stdscr, TRUE);
4349         if (has_colors())
4350                 init_colors();
4352         getmaxyx(stdscr, y, x);
4353         status_win = newwin(1, 0, y - 1, 0);
4354         if (!status_win)
4355                 die("Failed to create status window");
4357         /* Enable keyboard mapping */
4358         keypad(status_win, TRUE);
4359         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4362 static char *
4363 read_prompt(const char *prompt)
4365         enum { READING, STOP, CANCEL } status = READING;
4366         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4367         int pos = 0;
4369         while (status == READING) {
4370                 struct view *view;
4371                 int i, key;
4373                 input_mode = TRUE;
4375                 foreach_view (view, i)
4376                         update_view(view);
4378                 input_mode = FALSE;
4380                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4381                 wclrtoeol(status_win);
4383                 /* Refresh, accept single keystroke of input */
4384                 key = wgetch(status_win);
4385                 switch (key) {
4386                 case KEY_RETURN:
4387                 case KEY_ENTER:
4388                 case '\n':
4389                         status = pos ? STOP : CANCEL;
4390                         break;
4392                 case KEY_BACKSPACE:
4393                         if (pos > 0)
4394                                 pos--;
4395                         else
4396                                 status = CANCEL;
4397                         break;
4399                 case KEY_ESC:
4400                         status = CANCEL;
4401                         break;
4403                 case ERR:
4404                         break;
4406                 default:
4407                         if (pos >= sizeof(buf)) {
4408                                 report("Input string too long");
4409                                 return NULL;
4410                         }
4412                         if (isprint(key))
4413                                 buf[pos++] = (char) key;
4414                 }
4415         }
4417         /* Clear the status window */
4418         status_empty = FALSE;
4419         report("");
4421         if (status == CANCEL)
4422                 return NULL;
4424         buf[pos++] = 0;
4426         return buf;
4429 /*
4430  * Repository references
4431  */
4433 static struct ref *refs;
4434 static size_t refs_size;
4436 /* Id <-> ref store */
4437 static struct ref ***id_refs;
4438 static size_t id_refs_size;
4440 static struct ref **
4441 get_refs(char *id)
4443         struct ref ***tmp_id_refs;
4444         struct ref **ref_list = NULL;
4445         size_t ref_list_size = 0;
4446         size_t i;
4448         for (i = 0; i < id_refs_size; i++)
4449                 if (!strcmp(id, id_refs[i][0]->id))
4450                         return id_refs[i];
4452         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4453         if (!tmp_id_refs)
4454                 return NULL;
4456         id_refs = tmp_id_refs;
4458         for (i = 0; i < refs_size; i++) {
4459                 struct ref **tmp;
4461                 if (strcmp(id, refs[i].id))
4462                         continue;
4464                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4465                 if (!tmp) {
4466                         if (ref_list)
4467                                 free(ref_list);
4468                         return NULL;
4469                 }
4471                 ref_list = tmp;
4472                 if (ref_list_size > 0)
4473                         ref_list[ref_list_size - 1]->next = 1;
4474                 ref_list[ref_list_size] = &refs[i];
4476                 /* XXX: The properties of the commit chains ensures that we can
4477                  * safely modify the shared ref. The repo references will
4478                  * always be similar for the same id. */
4479                 ref_list[ref_list_size]->next = 0;
4480                 ref_list_size++;
4481         }
4483         if (ref_list)
4484                 id_refs[id_refs_size++] = ref_list;
4486         return ref_list;
4489 static int
4490 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4492         struct ref *ref;
4493         bool tag = FALSE;
4494         bool remote = FALSE;
4496         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4497                 /* Commits referenced by tags has "^{}" appended. */
4498                 if (name[namelen - 1] != '}')
4499                         return OK;
4501                 while (namelen > 0 && name[namelen] != '^')
4502                         namelen--;
4504                 tag = TRUE;
4505                 namelen -= STRING_SIZE("refs/tags/");
4506                 name    += STRING_SIZE("refs/tags/");
4508         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4509                 remote = TRUE;
4510                 namelen -= STRING_SIZE("refs/remotes/");
4511                 name    += STRING_SIZE("refs/remotes/");
4513         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4514                 namelen -= STRING_SIZE("refs/heads/");
4515                 name    += STRING_SIZE("refs/heads/");
4517         } else if (!strcmp(name, "HEAD")) {
4518                 return OK;
4519         }
4521         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4522         if (!refs)
4523                 return ERR;
4525         ref = &refs[refs_size++];
4526         ref->name = malloc(namelen + 1);
4527         if (!ref->name)
4528                 return ERR;
4530         strncpy(ref->name, name, namelen);
4531         ref->name[namelen] = 0;
4532         ref->tag = tag;
4533         ref->remote = remote;
4534         string_copy_rev(ref->id, id);
4536         return OK;
4539 static int
4540 load_refs(void)
4542         const char *cmd_env = getenv("TIG_LS_REMOTE");
4543         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4545         return read_properties(popen(cmd, "r"), "\t", read_ref);
4548 static int
4549 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4551         if (!strcmp(name, "i18n.commitencoding"))
4552                 string_ncopy(opt_encoding, value, valuelen);
4554         if (!strcmp(name, "core.editor"))
4555                 string_ncopy(opt_editor, value, valuelen);
4557         return OK;
4560 static int
4561 load_repo_config(void)
4563         return read_properties(popen(GIT_CONFIG " --list", "r"),
4564                                "=", read_repo_config_option);
4567 static int
4568 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4570         if (!opt_git_dir[0])
4571                 string_ncopy(opt_git_dir, name, namelen);
4572         else
4573                 string_ncopy(opt_cdup, name, namelen);
4574         return OK;
4577 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4578  * must be the last one! */
4579 static int
4580 load_repo_info(void)
4582         return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
4583                                "=", read_repo_info);
4586 static int
4587 read_properties(FILE *pipe, const char *separators,
4588                 int (*read_property)(char *, size_t, char *, size_t))
4590         char buffer[BUFSIZ];
4591         char *name;
4592         int state = OK;
4594         if (!pipe)
4595                 return ERR;
4597         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4598                 char *value;
4599                 size_t namelen;
4600                 size_t valuelen;
4602                 name = chomp_string(name);
4603                 namelen = strcspn(name, separators);
4605                 if (name[namelen]) {
4606                         name[namelen] = 0;
4607                         value = chomp_string(name + namelen + 1);
4608                         valuelen = strlen(value);
4610                 } else {
4611                         value = "";
4612                         valuelen = 0;
4613                 }
4615                 state = read_property(name, namelen, value, valuelen);
4616         }
4618         if (state != ERR && ferror(pipe))
4619                 state = ERR;
4621         pclose(pipe);
4623         return state;
4627 /*
4628  * Main
4629  */
4631 static void __NORETURN
4632 quit(int sig)
4634         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4635         if (cursed)
4636                 endwin();
4637         exit(0);
4640 static void __NORETURN
4641 die(const char *err, ...)
4643         va_list args;
4645         endwin();
4647         va_start(args, err);
4648         fputs("tig: ", stderr);
4649         vfprintf(stderr, err, args);
4650         fputs("\n", stderr);
4651         va_end(args);
4653         exit(1);
4656 int
4657 main(int argc, char *argv[])
4659         struct view *view;
4660         enum request request;
4661         size_t i;
4663         signal(SIGINT, quit);
4665         if (setlocale(LC_ALL, "")) {
4666                 char *codeset = nl_langinfo(CODESET);
4668                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4669         }
4671         if (load_repo_info() == ERR)
4672                 die("Failed to load repo info.");
4674         if (load_options() == ERR)
4675                 die("Failed to load user config.");
4677         /* Load the repo config file so options can be overwritten from
4678          * the command line. */
4679         if (load_repo_config() == ERR)
4680                 die("Failed to load repo config.");
4682         if (!parse_options(argc, argv))
4683                 return 0;
4685         /* Require a git repository unless when running in pager mode. */
4686         if (!opt_git_dir[0])
4687                 die("Not a git repository");
4689         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4690                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4691                 if (opt_iconv == ICONV_NONE)
4692                         die("Failed to initialize character set conversion");
4693         }
4695         if (load_refs() == ERR)
4696                 die("Failed to load refs.");
4698         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4699                 view->cmd_env = getenv(view->cmd_env);
4701         request = opt_request;
4703         init_display();
4705         while (view_driver(display[current_view], request)) {
4706                 int key;
4707                 int i;
4709                 foreach_view (view, i)
4710                         update_view(view);
4712                 /* Refresh, accept single keystroke of input */
4713                 key = wgetch(status_win);
4715                 /* wgetch() with nodelay() enabled returns ERR when there's no
4716                  * input. */
4717                 if (key == ERR) {
4718                         request = REQ_NONE;
4719                         continue;
4720                 }
4722                 request = get_keybinding(display[current_view]->keymap, key);
4724                 /* Some low-level request handling. This keeps access to
4725                  * status_win restricted. */
4726                 switch (request) {
4727                 case REQ_PROMPT:
4728                 {
4729                         char *cmd = read_prompt(":");
4731                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4732                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4733                                         opt_request = REQ_VIEW_DIFF;
4734                                 } else {
4735                                         opt_request = REQ_VIEW_PAGER;
4736                                 }
4737                                 break;
4738                         }
4740                         request = REQ_NONE;
4741                         break;
4742                 }
4743                 case REQ_SEARCH:
4744                 case REQ_SEARCH_BACK:
4745                 {
4746                         const char *prompt = request == REQ_SEARCH
4747                                            ? "/" : "?";
4748                         char *search = read_prompt(prompt);
4750                         if (search)
4751                                 string_ncopy(opt_search, search, strlen(search));
4752                         else
4753                                 request = REQ_NONE;
4754                         break;
4755                 }
4756                 case REQ_SCREEN_RESIZE:
4757                 {
4758                         int height, width;
4760                         getmaxyx(stdscr, height, width);
4762                         /* Resize the status view and let the view driver take
4763                          * care of resizing the displayed views. */
4764                         wresize(status_win, 1, width);
4765                         mvwin(status_win, height - 1, 0);
4766                         wrefresh(status_win);
4767                         break;
4768                 }
4769                 default:
4770                         break;
4771                 }
4772         }
4774         quit(0);
4776         return 0;