IO API: use in the blame view
[tig.git] / tig.c
blobcb2c09fd10ab416bccecff5cd50a188540867d48
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
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.
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.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
107 #define ID_COLS 8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE 8
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 #define TIG_MAIN_BASE \
126 "git log --no-color --pretty=raw --parents --topo-order"
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 head:1; /* Is it the current HEAD? */
138 unsigned int tag:1; /* Is it a tag? */
139 unsigned int ltag:1; /* If so, is the tag local? */
140 unsigned int remote:1; /* Is it a remote ref? */
141 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142 unsigned int next:1; /* For ref lists: are there more refs? */
145 static struct ref **get_refs(const char *id);
147 enum format_flags {
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 struct int_map {
157 const char *name;
158 int namelen;
159 int value;
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164 int *value, const char *name, int namelen)
167 int i;
169 for (i = 0; i < map_size; i++)
170 if (namelen == map[i].namelen &&
171 !strncasecmp(name, map[i].name, namelen)) {
172 *value = map[i].value;
173 return OK;
176 return ERR;
181 * String helpers
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187 if (srclen > dstlen - 1)
188 srclen = dstlen - 1;
190 strncpy(dst, src, srclen);
191 dst[srclen] = 0;
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200 string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
211 int namelen;
213 while (isspace(*name))
214 name++;
216 namelen = strlen(name) - 1;
217 while (namelen > 0 && isspace(name[namelen]))
218 name[namelen--] = 0;
220 return name;
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
226 va_list args;
227 size_t pos = bufpos ? *bufpos : 0;
229 va_start(args, fmt);
230 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231 va_end(args);
233 if (bufpos)
234 *bufpos = pos;
236 return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240 string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243 string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
248 size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252 /* Diff-Header == DIFF_HEADER */
253 for (i = 0; i < len; i++) {
254 if (toupper(str1[i]) == toupper(str2[i]))
255 continue;
257 if (string_enum_sep(str1[i]) &&
258 string_enum_sep(str2[i]))
259 continue;
261 return str1[i] - str2[i];
264 return 0;
267 #define prefixcmp(str1, str2) \
268 strncmp(str1, str2, STRING_SIZE(str2))
270 static inline int
271 suffixcmp(const char *str, int slen, const char *suffix)
273 size_t len = slen >= 0 ? slen : strlen(str);
274 size_t suffixlen = strlen(suffix);
276 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
279 /* Shell quoting
281 * NOTE: The following is a slightly modified copy of the git project's shell
282 * quoting routines found in the quote.c file.
284 * Help to copy the thing properly quoted for the shell safety. any single
285 * quote is replaced with '\'', any exclamation point is replaced with '\!',
286 * and the whole thing is enclosed in a
288 * E.g.
289 * original sq_quote result
290 * name ==> name ==> 'name'
291 * a b ==> a b ==> 'a b'
292 * a'b ==> a'\''b ==> 'a'\''b'
293 * a!b ==> a'\!'b ==> 'a'\!'b'
296 static size_t
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
299 char c;
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
303 BUFPUT('\'');
304 while ((c = *src++)) {
305 if (c == '\'' || c == '!') {
306 BUFPUT('\'');
307 BUFPUT('\\');
308 BUFPUT(c);
309 BUFPUT('\'');
310 } else {
311 BUFPUT(c);
314 BUFPUT('\'');
316 if (bufsize < SIZEOF_STR)
317 buf[bufsize] = 0;
319 return bufsize;
322 static bool
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
325 int valuelen;
327 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328 bool advance = cmd[valuelen] != 0;
330 cmd[valuelen] = 0;
331 argv[(*argc)++] = chomp_string(cmd);
332 cmd += valuelen + advance;
335 if (*argc < SIZEOF_ARG)
336 argv[*argc] = NULL;
337 return *argc < SIZEOF_ARG;
340 static void
341 argv_from_env(const char **argv, const char *name)
343 char *env = argv ? getenv(name) : NULL;
344 int argc = 0;
346 if (env && *env)
347 env = strdup(env);
348 if (env && !argv_from_string(argv, &argc, env))
349 die("Too many arguments in the `%s` environment variable", name);
354 * Executing external commands.
357 enum io_type {
358 IO_FD, /* File descriptor based IO. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
364 struct io {
365 enum io_type type; /* The requested type of pipe. */
366 const char *dir; /* Directory from which to execute. */
367 FILE *pipe; /* Pipe for reading or writing. */
368 int error; /* Error status. */
369 char sh[SIZEOF_STR]; /* Shell command buffer. */
370 char *buf; /* Read/write buffer. */
371 size_t bufalloc; /* Allocated buffer size. */
374 static void
375 reset_io(struct io *io)
377 io->pipe = NULL;
378 io->buf = NULL;
379 io->bufalloc = 0;
380 io->error = 0;
383 static void
384 init_io(struct io *io, const char *dir, enum io_type type)
386 reset_io(io);
387 io->type = type;
388 io->dir = dir;
391 static bool
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393 enum format_flags flags)
395 init_io(io, dir, IO_RD);
396 return format_command(io->sh, argv, flags);
399 static bool
400 init_io_fd(struct io *io, FILE *pipe)
402 init_io(io, NULL, IO_FD);
403 io->pipe = pipe;
404 return io->pipe != NULL;
407 static bool
408 done_io(struct io *io)
410 free(io->buf);
411 if (io->type == IO_FD)
412 fclose(io->pipe);
413 else if (io->type == IO_RD || io->type == IO_WR)
414 pclose(io->pipe);
415 reset_io(io);
416 return TRUE;
419 static bool
420 start_io(struct io *io)
422 char buf[SIZEOF_STR * 2];
423 size_t bufpos = 0;
425 if (io->dir && *io->dir &&
426 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
427 return FALSE;
429 if (!string_format_from(buf, &bufpos, "%s", io->sh))
430 return FALSE;
432 if (io->type == IO_FG)
433 return system(buf) == 0;
435 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436 return io->pipe != NULL;
439 static bool
440 run_io(struct io *io, enum io_type type, const char *cmd)
442 init_io(io, NULL, type);
443 string_ncopy(io->sh, cmd, strlen(cmd));
444 return start_io(io);
447 static int
448 run_io_do(struct io *io)
450 return start_io(io) && done_io(io);
453 static bool
454 run_io_fg(const char **argv, const char *dir)
456 struct io io = {};
458 init_io(&io, dir, IO_FG);
459 if (!format_command(io.sh, argv, FORMAT_NONE))
460 return FALSE;
461 return run_io_do(&io);
464 static bool
465 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
467 return init_io_rd(io, argv, NULL, flags) && start_io(io);
470 static bool
471 io_eof(struct io *io)
473 return feof(io->pipe);
476 static int
477 io_error(struct io *io)
479 return io->error;
482 static bool
483 io_strerror(struct io *io)
485 return strerror(io->error);
488 static char *
489 io_gets(struct io *io)
491 if (!io->buf) {
492 io->buf = malloc(BUFSIZ);
493 if (!io->buf)
494 return NULL;
495 io->bufalloc = BUFSIZ;
498 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
499 if (ferror(io->pipe))
500 io->error = errno;
501 return NULL;
504 return io->buf;
509 * User requests
512 #define REQ_INFO \
513 /* XXX: Keep the view request first and in sync with views[]. */ \
514 REQ_GROUP("View switching") \
515 REQ_(VIEW_MAIN, "Show main view"), \
516 REQ_(VIEW_DIFF, "Show diff view"), \
517 REQ_(VIEW_LOG, "Show log view"), \
518 REQ_(VIEW_TREE, "Show tree view"), \
519 REQ_(VIEW_BLOB, "Show blob view"), \
520 REQ_(VIEW_BLAME, "Show blame view"), \
521 REQ_(VIEW_HELP, "Show help page"), \
522 REQ_(VIEW_PAGER, "Show pager view"), \
523 REQ_(VIEW_STATUS, "Show status view"), \
524 REQ_(VIEW_STAGE, "Show stage view"), \
526 REQ_GROUP("View manipulation") \
527 REQ_(ENTER, "Enter current line and scroll"), \
528 REQ_(NEXT, "Move to next"), \
529 REQ_(PREVIOUS, "Move to previous"), \
530 REQ_(VIEW_NEXT, "Move focus to next view"), \
531 REQ_(REFRESH, "Reload and refresh"), \
532 REQ_(MAXIMIZE, "Maximize the current view"), \
533 REQ_(VIEW_CLOSE, "Close the current view"), \
534 REQ_(QUIT, "Close all views and quit"), \
536 REQ_GROUP("View specific requests") \
537 REQ_(STATUS_UPDATE, "Update file status"), \
538 REQ_(STATUS_REVERT, "Revert file changes"), \
539 REQ_(STATUS_MERGE, "Merge file using external tool"), \
540 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
541 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
543 REQ_GROUP("Cursor navigation") \
544 REQ_(MOVE_UP, "Move cursor one line up"), \
545 REQ_(MOVE_DOWN, "Move cursor one line down"), \
546 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
547 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
548 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
549 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
551 REQ_GROUP("Scrolling") \
552 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
553 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
554 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
555 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
557 REQ_GROUP("Searching") \
558 REQ_(SEARCH, "Search the view"), \
559 REQ_(SEARCH_BACK, "Search backwards in the view"), \
560 REQ_(FIND_NEXT, "Find next search match"), \
561 REQ_(FIND_PREV, "Find previous search match"), \
563 REQ_GROUP("Option manipulation") \
564 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
565 REQ_(TOGGLE_DATE, "Toggle date display"), \
566 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
567 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
568 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
570 REQ_GROUP("Misc") \
571 REQ_(PROMPT, "Bring up the prompt"), \
572 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
573 REQ_(SCREEN_RESIZE, "Resize the screen"), \
574 REQ_(SHOW_VERSION, "Show version information"), \
575 REQ_(STOP_LOADING, "Stop all loading views"), \
576 REQ_(EDIT, "Open in editor"), \
577 REQ_(NONE, "Do nothing")
580 /* User action requests. */
581 enum request {
582 #define REQ_GROUP(help)
583 #define REQ_(req, help) REQ_##req
585 /* Offset all requests to avoid conflicts with ncurses getch values. */
586 REQ_OFFSET = KEY_MAX + 1,
587 REQ_INFO
589 #undef REQ_GROUP
590 #undef REQ_
593 struct request_info {
594 enum request request;
595 const char *name;
596 int namelen;
597 const char *help;
600 static struct request_info req_info[] = {
601 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
602 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
603 REQ_INFO
604 #undef REQ_GROUP
605 #undef REQ_
608 static enum request
609 get_request(const char *name)
611 int namelen = strlen(name);
612 int i;
614 for (i = 0; i < ARRAY_SIZE(req_info); i++)
615 if (req_info[i].namelen == namelen &&
616 !string_enum_compare(req_info[i].name, name, namelen))
617 return req_info[i].request;
619 return REQ_NONE;
624 * Options
627 static const char usage[] =
628 "tig " TIG_VERSION " (" __DATE__ ")\n"
629 "\n"
630 "Usage: tig [options] [revs] [--] [paths]\n"
631 " or: tig show [options] [revs] [--] [paths]\n"
632 " or: tig blame [rev] path\n"
633 " or: tig status\n"
634 " or: tig < [git command output]\n"
635 "\n"
636 "Options:\n"
637 " -v, --version Show version and exit\n"
638 " -h, --help Show help message and exit";
640 /* Option and state variables. */
641 static bool opt_date = TRUE;
642 static bool opt_author = TRUE;
643 static bool opt_line_number = FALSE;
644 static bool opt_line_graphics = TRUE;
645 static bool opt_rev_graph = FALSE;
646 static bool opt_show_refs = TRUE;
647 static int opt_num_interval = NUMBER_INTERVAL;
648 static int opt_tab_size = TAB_SIZE;
649 static int opt_author_cols = AUTHOR_COLS-1;
650 static char opt_cmd[SIZEOF_STR] = "";
651 static char opt_path[SIZEOF_STR] = "";
652 static char opt_file[SIZEOF_STR] = "";
653 static char opt_ref[SIZEOF_REF] = "";
654 static char opt_head[SIZEOF_REF] = "";
655 static char opt_head_rev[SIZEOF_REV] = "";
656 static char opt_remote[SIZEOF_REF] = "";
657 static FILE *opt_pipe = NULL;
658 static char opt_encoding[20] = "UTF-8";
659 static bool opt_utf8 = TRUE;
660 static char opt_codeset[20] = "UTF-8";
661 static iconv_t opt_iconv = ICONV_NONE;
662 static char opt_search[SIZEOF_STR] = "";
663 static char opt_cdup[SIZEOF_STR] = "";
664 static char opt_git_dir[SIZEOF_STR] = "";
665 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
666 static char opt_editor[SIZEOF_STR] = "";
667 static FILE *opt_tty = NULL;
669 #define is_initial_commit() (!*opt_head_rev)
670 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
672 static enum request
673 parse_options(int argc, const char *argv[])
675 enum request request = REQ_VIEW_MAIN;
676 size_t buf_size;
677 const char *subcommand;
678 bool seen_dashdash = FALSE;
679 int i;
681 if (!isatty(STDIN_FILENO)) {
682 opt_pipe = stdin;
683 return REQ_VIEW_PAGER;
686 if (argc <= 1)
687 return REQ_VIEW_MAIN;
689 subcommand = argv[1];
690 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
691 if (!strcmp(subcommand, "-S"))
692 warn("`-S' has been deprecated; use `tig status' instead");
693 if (argc > 2)
694 warn("ignoring arguments after `%s'", subcommand);
695 return REQ_VIEW_STATUS;
697 } else if (!strcmp(subcommand, "blame")) {
698 if (argc <= 2 || argc > 4)
699 die("invalid number of options to blame\n\n%s", usage);
701 i = 2;
702 if (argc == 4) {
703 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
704 i++;
707 string_ncopy(opt_file, argv[i], strlen(argv[i]));
708 return REQ_VIEW_BLAME;
710 } else if (!strcmp(subcommand, "show")) {
711 request = REQ_VIEW_DIFF;
713 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
714 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
715 warn("`tig %s' has been deprecated", subcommand);
717 } else {
718 subcommand = NULL;
721 if (!subcommand)
722 /* XXX: This is vulnerable to the user overriding
723 * options required for the main view parser. */
724 string_copy(opt_cmd, TIG_MAIN_BASE);
725 else
726 string_format(opt_cmd, "git %s", subcommand);
728 buf_size = strlen(opt_cmd);
730 for (i = 1 + !!subcommand; i < argc; i++) {
731 const char *opt = argv[i];
733 if (seen_dashdash || !strcmp(opt, "--")) {
734 seen_dashdash = TRUE;
736 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
737 printf("tig version %s\n", TIG_VERSION);
738 return REQ_NONE;
740 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
741 printf("%s\n", usage);
742 return REQ_NONE;
745 opt_cmd[buf_size++] = ' ';
746 buf_size = sq_quote(opt_cmd, buf_size, opt);
747 if (buf_size >= sizeof(opt_cmd))
748 die("command too long");
751 opt_cmd[buf_size] = 0;
753 return request;
758 * Line-oriented content detection.
761 #define LINE_INFO \
762 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
763 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
764 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
765 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
766 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
767 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
768 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
769 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
770 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
771 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
772 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
773 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
775 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
776 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
777 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
778 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
779 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
783 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
784 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
785 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
786 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
787 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
788 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
791 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
792 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
793 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
794 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
795 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
796 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
797 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
798 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
800 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
801 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
802 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
803 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
804 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
805 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
806 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
807 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
808 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
809 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
810 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
812 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
814 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
815 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
817 enum line_type {
818 #define LINE(type, line, fg, bg, attr) \
819 LINE_##type
820 LINE_INFO,
821 LINE_NONE
822 #undef LINE
825 struct line_info {
826 const char *name; /* Option name. */
827 int namelen; /* Size of option name. */
828 const char *line; /* The start of line to match. */
829 int linelen; /* Size of string to match. */
830 int fg, bg, attr; /* Color and text attributes for the lines. */
833 static struct line_info line_info[] = {
834 #define LINE(type, line, fg, bg, attr) \
835 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
836 LINE_INFO
837 #undef LINE
840 static enum line_type
841 get_line_type(const char *line)
843 int linelen = strlen(line);
844 enum line_type type;
846 for (type = 0; type < ARRAY_SIZE(line_info); type++)
847 /* Case insensitive search matches Signed-off-by lines better. */
848 if (linelen >= line_info[type].linelen &&
849 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
850 return type;
852 return LINE_DEFAULT;
855 static inline int
856 get_line_attr(enum line_type type)
858 assert(type < ARRAY_SIZE(line_info));
859 return COLOR_PAIR(type) | line_info[type].attr;
862 static struct line_info *
863 get_line_info(const char *name)
865 size_t namelen = strlen(name);
866 enum line_type type;
868 for (type = 0; type < ARRAY_SIZE(line_info); type++)
869 if (namelen == line_info[type].namelen &&
870 !string_enum_compare(line_info[type].name, name, namelen))
871 return &line_info[type];
873 return NULL;
876 static void
877 init_colors(void)
879 int default_bg = line_info[LINE_DEFAULT].bg;
880 int default_fg = line_info[LINE_DEFAULT].fg;
881 enum line_type type;
883 start_color();
885 if (assume_default_colors(default_fg, default_bg) == ERR) {
886 default_bg = COLOR_BLACK;
887 default_fg = COLOR_WHITE;
890 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
891 struct line_info *info = &line_info[type];
892 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
893 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
895 init_pair(type, fg, bg);
899 struct line {
900 enum line_type type;
902 /* State flags */
903 unsigned int selected:1;
904 unsigned int dirty:1;
906 void *data; /* User data */
911 * Keys
914 struct keybinding {
915 int alias;
916 enum request request;
919 static struct keybinding default_keybindings[] = {
920 /* View switching */
921 { 'm', REQ_VIEW_MAIN },
922 { 'd', REQ_VIEW_DIFF },
923 { 'l', REQ_VIEW_LOG },
924 { 't', REQ_VIEW_TREE },
925 { 'f', REQ_VIEW_BLOB },
926 { 'B', REQ_VIEW_BLAME },
927 { 'p', REQ_VIEW_PAGER },
928 { 'h', REQ_VIEW_HELP },
929 { 'S', REQ_VIEW_STATUS },
930 { 'c', REQ_VIEW_STAGE },
932 /* View manipulation */
933 { 'q', REQ_VIEW_CLOSE },
934 { KEY_TAB, REQ_VIEW_NEXT },
935 { KEY_RETURN, REQ_ENTER },
936 { KEY_UP, REQ_PREVIOUS },
937 { KEY_DOWN, REQ_NEXT },
938 { 'R', REQ_REFRESH },
939 { KEY_F(5), REQ_REFRESH },
940 { 'O', REQ_MAXIMIZE },
942 /* Cursor navigation */
943 { 'k', REQ_MOVE_UP },
944 { 'j', REQ_MOVE_DOWN },
945 { KEY_HOME, REQ_MOVE_FIRST_LINE },
946 { KEY_END, REQ_MOVE_LAST_LINE },
947 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
948 { ' ', REQ_MOVE_PAGE_DOWN },
949 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
950 { 'b', REQ_MOVE_PAGE_UP },
951 { '-', REQ_MOVE_PAGE_UP },
953 /* Scrolling */
954 { KEY_IC, REQ_SCROLL_LINE_UP },
955 { KEY_DC, REQ_SCROLL_LINE_DOWN },
956 { 'w', REQ_SCROLL_PAGE_UP },
957 { 's', REQ_SCROLL_PAGE_DOWN },
959 /* Searching */
960 { '/', REQ_SEARCH },
961 { '?', REQ_SEARCH_BACK },
962 { 'n', REQ_FIND_NEXT },
963 { 'N', REQ_FIND_PREV },
965 /* Misc */
966 { 'Q', REQ_QUIT },
967 { 'z', REQ_STOP_LOADING },
968 { 'v', REQ_SHOW_VERSION },
969 { 'r', REQ_SCREEN_REDRAW },
970 { '.', REQ_TOGGLE_LINENO },
971 { 'D', REQ_TOGGLE_DATE },
972 { 'A', REQ_TOGGLE_AUTHOR },
973 { 'g', REQ_TOGGLE_REV_GRAPH },
974 { 'F', REQ_TOGGLE_REFS },
975 { ':', REQ_PROMPT },
976 { 'u', REQ_STATUS_UPDATE },
977 { '!', REQ_STATUS_REVERT },
978 { 'M', REQ_STATUS_MERGE },
979 { '@', REQ_STAGE_NEXT },
980 { ',', REQ_TREE_PARENT },
981 { 'e', REQ_EDIT },
983 /* Using the ncurses SIGWINCH handler. */
984 { KEY_RESIZE, REQ_SCREEN_RESIZE },
987 #define KEYMAP_INFO \
988 KEYMAP_(GENERIC), \
989 KEYMAP_(MAIN), \
990 KEYMAP_(DIFF), \
991 KEYMAP_(LOG), \
992 KEYMAP_(TREE), \
993 KEYMAP_(BLOB), \
994 KEYMAP_(BLAME), \
995 KEYMAP_(PAGER), \
996 KEYMAP_(HELP), \
997 KEYMAP_(STATUS), \
998 KEYMAP_(STAGE)
1000 enum keymap {
1001 #define KEYMAP_(name) KEYMAP_##name
1002 KEYMAP_INFO
1003 #undef KEYMAP_
1006 static struct int_map keymap_table[] = {
1007 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1008 KEYMAP_INFO
1009 #undef KEYMAP_
1012 #define set_keymap(map, name) \
1013 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1015 struct keybinding_table {
1016 struct keybinding *data;
1017 size_t size;
1020 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1022 static void
1023 add_keybinding(enum keymap keymap, enum request request, int key)
1025 struct keybinding_table *table = &keybindings[keymap];
1027 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1028 if (!table->data)
1029 die("Failed to allocate keybinding");
1030 table->data[table->size].alias = key;
1031 table->data[table->size++].request = request;
1034 /* Looks for a key binding first in the given map, then in the generic map, and
1035 * lastly in the default keybindings. */
1036 static enum request
1037 get_keybinding(enum keymap keymap, int key)
1039 size_t i;
1041 for (i = 0; i < keybindings[keymap].size; i++)
1042 if (keybindings[keymap].data[i].alias == key)
1043 return keybindings[keymap].data[i].request;
1045 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1046 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1047 return keybindings[KEYMAP_GENERIC].data[i].request;
1049 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1050 if (default_keybindings[i].alias == key)
1051 return default_keybindings[i].request;
1053 return (enum request) key;
1057 struct key {
1058 const char *name;
1059 int value;
1062 static struct key key_table[] = {
1063 { "Enter", KEY_RETURN },
1064 { "Space", ' ' },
1065 { "Backspace", KEY_BACKSPACE },
1066 { "Tab", KEY_TAB },
1067 { "Escape", KEY_ESC },
1068 { "Left", KEY_LEFT },
1069 { "Right", KEY_RIGHT },
1070 { "Up", KEY_UP },
1071 { "Down", KEY_DOWN },
1072 { "Insert", KEY_IC },
1073 { "Delete", KEY_DC },
1074 { "Hash", '#' },
1075 { "Home", KEY_HOME },
1076 { "End", KEY_END },
1077 { "PageUp", KEY_PPAGE },
1078 { "PageDown", KEY_NPAGE },
1079 { "F1", KEY_F(1) },
1080 { "F2", KEY_F(2) },
1081 { "F3", KEY_F(3) },
1082 { "F4", KEY_F(4) },
1083 { "F5", KEY_F(5) },
1084 { "F6", KEY_F(6) },
1085 { "F7", KEY_F(7) },
1086 { "F8", KEY_F(8) },
1087 { "F9", KEY_F(9) },
1088 { "F10", KEY_F(10) },
1089 { "F11", KEY_F(11) },
1090 { "F12", KEY_F(12) },
1093 static int
1094 get_key_value(const char *name)
1096 int i;
1098 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1099 if (!strcasecmp(key_table[i].name, name))
1100 return key_table[i].value;
1102 if (strlen(name) == 1 && isprint(*name))
1103 return (int) *name;
1105 return ERR;
1108 static const char *
1109 get_key_name(int key_value)
1111 static char key_char[] = "'X'";
1112 const char *seq = NULL;
1113 int key;
1115 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1116 if (key_table[key].value == key_value)
1117 seq = key_table[key].name;
1119 if (seq == NULL &&
1120 key_value < 127 &&
1121 isprint(key_value)) {
1122 key_char[1] = (char) key_value;
1123 seq = key_char;
1126 return seq ? seq : "(no key)";
1129 static const char *
1130 get_key(enum request request)
1132 static char buf[BUFSIZ];
1133 size_t pos = 0;
1134 char *sep = "";
1135 int i;
1137 buf[pos] = 0;
1139 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1140 struct keybinding *keybinding = &default_keybindings[i];
1142 if (keybinding->request != request)
1143 continue;
1145 if (!string_format_from(buf, &pos, "%s%s", sep,
1146 get_key_name(keybinding->alias)))
1147 return "Too many keybindings!";
1148 sep = ", ";
1151 return buf;
1154 struct run_request {
1155 enum keymap keymap;
1156 int key;
1157 const char *argv[SIZEOF_ARG];
1160 static struct run_request *run_request;
1161 static size_t run_requests;
1163 static enum request
1164 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1166 struct run_request *req;
1168 if (argc >= ARRAY_SIZE(req->argv) - 1)
1169 return REQ_NONE;
1171 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1172 if (!req)
1173 return REQ_NONE;
1175 run_request = req;
1176 req = &run_request[run_requests];
1177 req->keymap = keymap;
1178 req->key = key;
1179 req->argv[0] = NULL;
1181 if (!format_argv(req->argv, argv, FORMAT_NONE))
1182 return REQ_NONE;
1184 return REQ_NONE + ++run_requests;
1187 static struct run_request *
1188 get_run_request(enum request request)
1190 if (request <= REQ_NONE)
1191 return NULL;
1192 return &run_request[request - REQ_NONE - 1];
1195 static void
1196 add_builtin_run_requests(void)
1198 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1199 const char *gc[] = { "git", "gc", NULL };
1200 struct {
1201 enum keymap keymap;
1202 int key;
1203 int argc;
1204 const char **argv;
1205 } reqs[] = {
1206 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1207 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1209 int i;
1211 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1212 enum request req;
1214 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1215 if (req != REQ_NONE)
1216 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1221 * User config file handling.
1224 static struct int_map color_map[] = {
1225 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1226 COLOR_MAP(DEFAULT),
1227 COLOR_MAP(BLACK),
1228 COLOR_MAP(BLUE),
1229 COLOR_MAP(CYAN),
1230 COLOR_MAP(GREEN),
1231 COLOR_MAP(MAGENTA),
1232 COLOR_MAP(RED),
1233 COLOR_MAP(WHITE),
1234 COLOR_MAP(YELLOW),
1237 #define set_color(color, name) \
1238 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1240 static struct int_map attr_map[] = {
1241 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1242 ATTR_MAP(NORMAL),
1243 ATTR_MAP(BLINK),
1244 ATTR_MAP(BOLD),
1245 ATTR_MAP(DIM),
1246 ATTR_MAP(REVERSE),
1247 ATTR_MAP(STANDOUT),
1248 ATTR_MAP(UNDERLINE),
1251 #define set_attribute(attr, name) \
1252 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1254 static int config_lineno;
1255 static bool config_errors;
1256 static const char *config_msg;
1258 /* Wants: object fgcolor bgcolor [attr] */
1259 static int
1260 option_color_command(int argc, const char *argv[])
1262 struct line_info *info;
1264 if (argc != 3 && argc != 4) {
1265 config_msg = "Wrong number of arguments given to color command";
1266 return ERR;
1269 info = get_line_info(argv[0]);
1270 if (!info) {
1271 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1272 info = get_line_info("delimiter");
1274 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1275 info = get_line_info("date");
1277 } else {
1278 config_msg = "Unknown color name";
1279 return ERR;
1283 if (set_color(&info->fg, argv[1]) == ERR ||
1284 set_color(&info->bg, argv[2]) == ERR) {
1285 config_msg = "Unknown color";
1286 return ERR;
1289 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1290 config_msg = "Unknown attribute";
1291 return ERR;
1294 return OK;
1297 static bool parse_bool(const char *s)
1299 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1300 !strcmp(s, "yes")) ? TRUE : FALSE;
1303 static int
1304 parse_int(const char *s, int default_value, int min, int max)
1306 int value = atoi(s);
1308 return (value < min || value > max) ? default_value : value;
1311 /* Wants: name = value */
1312 static int
1313 option_set_command(int argc, const char *argv[])
1315 if (argc != 3) {
1316 config_msg = "Wrong number of arguments given to set command";
1317 return ERR;
1320 if (strcmp(argv[1], "=")) {
1321 config_msg = "No value assigned";
1322 return ERR;
1325 if (!strcmp(argv[0], "show-author")) {
1326 opt_author = parse_bool(argv[2]);
1327 return OK;
1330 if (!strcmp(argv[0], "show-date")) {
1331 opt_date = parse_bool(argv[2]);
1332 return OK;
1335 if (!strcmp(argv[0], "show-rev-graph")) {
1336 opt_rev_graph = parse_bool(argv[2]);
1337 return OK;
1340 if (!strcmp(argv[0], "show-refs")) {
1341 opt_show_refs = parse_bool(argv[2]);
1342 return OK;
1345 if (!strcmp(argv[0], "show-line-numbers")) {
1346 opt_line_number = parse_bool(argv[2]);
1347 return OK;
1350 if (!strcmp(argv[0], "line-graphics")) {
1351 opt_line_graphics = parse_bool(argv[2]);
1352 return OK;
1355 if (!strcmp(argv[0], "line-number-interval")) {
1356 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1357 return OK;
1360 if (!strcmp(argv[0], "author-width")) {
1361 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1362 return OK;
1365 if (!strcmp(argv[0], "tab-size")) {
1366 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1367 return OK;
1370 if (!strcmp(argv[0], "commit-encoding")) {
1371 const char *arg = argv[2];
1372 int arglen = strlen(arg);
1374 switch (arg[0]) {
1375 case '"':
1376 case '\'':
1377 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1378 config_msg = "Unmatched quotation";
1379 return ERR;
1381 arg += 1; arglen -= 2;
1382 default:
1383 string_ncopy(opt_encoding, arg, strlen(arg));
1384 return OK;
1388 config_msg = "Unknown variable name";
1389 return ERR;
1392 /* Wants: mode request key */
1393 static int
1394 option_bind_command(int argc, const char *argv[])
1396 enum request request;
1397 int keymap;
1398 int key;
1400 if (argc < 3) {
1401 config_msg = "Wrong number of arguments given to bind command";
1402 return ERR;
1405 if (set_keymap(&keymap, argv[0]) == ERR) {
1406 config_msg = "Unknown key map";
1407 return ERR;
1410 key = get_key_value(argv[1]);
1411 if (key == ERR) {
1412 config_msg = "Unknown key";
1413 return ERR;
1416 request = get_request(argv[2]);
1417 if (request == REQ_NONE) {
1418 const char *obsolete[] = { "cherry-pick" };
1419 size_t namelen = strlen(argv[2]);
1420 int i;
1422 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1423 if (namelen == strlen(obsolete[i]) &&
1424 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1425 config_msg = "Obsolete request name";
1426 return ERR;
1430 if (request == REQ_NONE && *argv[2]++ == '!')
1431 request = add_run_request(keymap, key, argc - 2, argv + 2);
1432 if (request == REQ_NONE) {
1433 config_msg = "Unknown request name";
1434 return ERR;
1437 add_keybinding(keymap, request, key);
1439 return OK;
1442 static int
1443 set_option(const char *opt, char *value)
1445 const char *argv[SIZEOF_ARG];
1446 int argc = 0;
1448 if (!argv_from_string(argv, &argc, value)) {
1449 config_msg = "Too many option arguments";
1450 return ERR;
1453 if (!strcmp(opt, "color"))
1454 return option_color_command(argc, argv);
1456 if (!strcmp(opt, "set"))
1457 return option_set_command(argc, argv);
1459 if (!strcmp(opt, "bind"))
1460 return option_bind_command(argc, argv);
1462 config_msg = "Unknown option command";
1463 return ERR;
1466 static int
1467 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1469 int status = OK;
1471 config_lineno++;
1472 config_msg = "Internal error";
1474 /* Check for comment markers, since read_properties() will
1475 * only ensure opt and value are split at first " \t". */
1476 optlen = strcspn(opt, "#");
1477 if (optlen == 0)
1478 return OK;
1480 if (opt[optlen] != 0) {
1481 config_msg = "No option value";
1482 status = ERR;
1484 } else {
1485 /* Look for comment endings in the value. */
1486 size_t len = strcspn(value, "#");
1488 if (len < valuelen) {
1489 valuelen = len;
1490 value[valuelen] = 0;
1493 status = set_option(opt, value);
1496 if (status == ERR) {
1497 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1498 config_lineno, (int) optlen, opt, config_msg);
1499 config_errors = TRUE;
1502 /* Always keep going if errors are encountered. */
1503 return OK;
1506 static void
1507 load_option_file(const char *path)
1509 FILE *file;
1511 /* It's ok that the file doesn't exist. */
1512 file = fopen(path, "r");
1513 if (!file)
1514 return;
1516 config_lineno = 0;
1517 config_errors = FALSE;
1519 if (read_properties(file, " \t", read_option) == ERR ||
1520 config_errors == TRUE)
1521 fprintf(stderr, "Errors while loading %s.\n", path);
1524 static int
1525 load_options(void)
1527 const char *home = getenv("HOME");
1528 const char *tigrc_user = getenv("TIGRC_USER");
1529 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1530 char buf[SIZEOF_STR];
1532 add_builtin_run_requests();
1534 if (!tigrc_system) {
1535 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1536 return ERR;
1537 tigrc_system = buf;
1539 load_option_file(tigrc_system);
1541 if (!tigrc_user) {
1542 if (!home || !string_format(buf, "%s/.tigrc", home))
1543 return ERR;
1544 tigrc_user = buf;
1546 load_option_file(tigrc_user);
1548 return OK;
1553 * The viewer
1556 struct view;
1557 struct view_ops;
1559 /* The display array of active views and the index of the current view. */
1560 static struct view *display[2];
1561 static unsigned int current_view;
1563 /* Reading from the prompt? */
1564 static bool input_mode = FALSE;
1566 #define foreach_displayed_view(view, i) \
1567 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1569 #define displayed_views() (display[1] != NULL ? 2 : 1)
1571 /* Current head and commit ID */
1572 static char ref_blob[SIZEOF_REF] = "";
1573 static char ref_commit[SIZEOF_REF] = "HEAD";
1574 static char ref_head[SIZEOF_REF] = "HEAD";
1576 struct view {
1577 const char *name; /* View name */
1578 const char *cmd_env; /* Command line set via environment */
1579 const char *id; /* Points to either of ref_{head,commit,blob} */
1581 struct view_ops *ops; /* View operations */
1583 enum keymap keymap; /* What keymap does this view have */
1584 bool git_dir; /* Whether the view requires a git directory. */
1586 char ref[SIZEOF_REF]; /* Hovered commit reference */
1587 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1589 int height, width; /* The width and height of the main window */
1590 WINDOW *win; /* The main window */
1591 WINDOW *title; /* The title window living below the main window */
1593 /* Navigation */
1594 unsigned long offset; /* Offset of the window top */
1595 unsigned long lineno; /* Current line number */
1597 /* Searching */
1598 char grep[SIZEOF_STR]; /* Search string */
1599 regex_t *regex; /* Pre-compiled regex */
1601 /* If non-NULL, points to the view that opened this view. If this view
1602 * is closed tig will switch back to the parent view. */
1603 struct view *parent;
1605 /* Buffering */
1606 size_t lines; /* Total number of lines */
1607 struct line *line; /* Line index */
1608 size_t line_alloc; /* Total number of allocated lines */
1609 size_t line_size; /* Total number of used lines */
1610 unsigned int digits; /* Number of digits in the lines member. */
1612 /* Drawing */
1613 struct line *curline; /* Line currently being drawn. */
1614 enum line_type curtype; /* Attribute currently used for drawing. */
1615 unsigned long col; /* Column when drawing. */
1617 /* Loading */
1618 struct io io;
1619 struct io *pipe;
1620 time_t start_time;
1623 struct view_ops {
1624 /* What type of content being displayed. Used in the title bar. */
1625 const char *type;
1626 /* Default command arguments. */
1627 const char **argv;
1628 /* Open and reads in all view content. */
1629 bool (*open)(struct view *view);
1630 /* Read one line; updates view->line. */
1631 bool (*read)(struct view *view, char *data);
1632 /* Draw one line; @lineno must be < view->height. */
1633 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1634 /* Depending on view handle a special requests. */
1635 enum request (*request)(struct view *view, enum request request, struct line *line);
1636 /* Search for regex in a line. */
1637 bool (*grep)(struct view *view, struct line *line);
1638 /* Select line */
1639 void (*select)(struct view *view, struct line *line);
1642 static struct view_ops blame_ops;
1643 static struct view_ops blob_ops;
1644 static struct view_ops diff_ops;
1645 static struct view_ops help_ops;
1646 static struct view_ops log_ops;
1647 static struct view_ops main_ops;
1648 static struct view_ops pager_ops;
1649 static struct view_ops stage_ops;
1650 static struct view_ops status_ops;
1651 static struct view_ops tree_ops;
1653 #define VIEW_STR(name, env, ref, ops, map, git) \
1654 { name, #env, ref, ops, map, git }
1656 #define VIEW_(id, name, ops, git, ref) \
1657 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1660 static struct view views[] = {
1661 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1662 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1663 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1664 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1665 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1666 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1667 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1668 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1669 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1670 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1673 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1674 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1676 #define foreach_view(view, i) \
1677 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1679 #define view_is_displayed(view) \
1680 (view == display[0] || view == display[1])
1683 enum line_graphic {
1684 LINE_GRAPHIC_VLINE
1687 static int line_graphics[] = {
1688 /* LINE_GRAPHIC_VLINE: */ '|'
1691 static inline void
1692 set_view_attr(struct view *view, enum line_type type)
1694 if (!view->curline->selected && view->curtype != type) {
1695 wattrset(view->win, get_line_attr(type));
1696 wchgat(view->win, -1, 0, type, NULL);
1697 view->curtype = type;
1701 static int
1702 draw_chars(struct view *view, enum line_type type, const char *string,
1703 int max_len, bool use_tilde)
1705 int len = 0;
1706 int col = 0;
1707 int trimmed = FALSE;
1709 if (max_len <= 0)
1710 return 0;
1712 if (opt_utf8) {
1713 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1714 } else {
1715 col = len = strlen(string);
1716 if (len > max_len) {
1717 if (use_tilde) {
1718 max_len -= 1;
1720 col = len = max_len;
1721 trimmed = TRUE;
1725 set_view_attr(view, type);
1726 waddnstr(view->win, string, len);
1727 if (trimmed && use_tilde) {
1728 set_view_attr(view, LINE_DELIMITER);
1729 waddch(view->win, '~');
1730 col++;
1733 return col;
1736 static int
1737 draw_space(struct view *view, enum line_type type, int max, int spaces)
1739 static char space[] = " ";
1740 int col = 0;
1742 spaces = MIN(max, spaces);
1744 while (spaces > 0) {
1745 int len = MIN(spaces, sizeof(space) - 1);
1747 col += draw_chars(view, type, space, spaces, FALSE);
1748 spaces -= len;
1751 return col;
1754 static bool
1755 draw_lineno(struct view *view, unsigned int lineno)
1757 char number[10];
1758 int digits3 = view->digits < 3 ? 3 : view->digits;
1759 int max_number = MIN(digits3, STRING_SIZE(number));
1760 int max = view->width - view->col;
1761 int col;
1763 if (max < max_number)
1764 max_number = max;
1766 lineno += view->offset + 1;
1767 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1768 static char fmt[] = "%1ld";
1770 if (view->digits <= 9)
1771 fmt[1] = '0' + digits3;
1773 if (!string_format(number, fmt, lineno))
1774 number[0] = 0;
1775 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1776 } else {
1777 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1780 if (col < max) {
1781 set_view_attr(view, LINE_DEFAULT);
1782 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1783 col++;
1786 if (col < max)
1787 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1788 view->col += col;
1790 return view->width - view->col <= 0;
1793 static bool
1794 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1796 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1797 return view->width - view->col <= 0;
1800 static bool
1801 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1803 int max = view->width - view->col;
1804 int i;
1806 if (max < size)
1807 size = max;
1809 set_view_attr(view, type);
1810 /* Using waddch() instead of waddnstr() ensures that
1811 * they'll be rendered correctly for the cursor line. */
1812 for (i = 0; i < size; i++)
1813 waddch(view->win, graphic[i]);
1815 view->col += size;
1816 if (size < max) {
1817 waddch(view->win, ' ');
1818 view->col++;
1821 return view->width - view->col <= 0;
1824 static bool
1825 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1827 int max = MIN(view->width - view->col, len);
1828 int col;
1830 if (text)
1831 col = draw_chars(view, type, text, max - 1, trim);
1832 else
1833 col = draw_space(view, type, max - 1, max - 1);
1835 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1836 return view->width - view->col <= 0;
1839 static bool
1840 draw_date(struct view *view, struct tm *time)
1842 char buf[DATE_COLS];
1843 char *date;
1844 int timelen = 0;
1846 if (time)
1847 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1848 date = timelen ? buf : NULL;
1850 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1853 static bool
1854 draw_view_line(struct view *view, unsigned int lineno)
1856 struct line *line;
1857 bool selected = (view->offset + lineno == view->lineno);
1858 bool draw_ok;
1860 assert(view_is_displayed(view));
1862 if (view->offset + lineno >= view->lines)
1863 return FALSE;
1865 line = &view->line[view->offset + lineno];
1867 wmove(view->win, lineno, 0);
1868 view->col = 0;
1869 view->curline = line;
1870 view->curtype = LINE_NONE;
1871 line->selected = FALSE;
1873 if (selected) {
1874 set_view_attr(view, LINE_CURSOR);
1875 line->selected = TRUE;
1876 view->ops->select(view, line);
1877 } else if (line->selected) {
1878 wclrtoeol(view->win);
1881 scrollok(view->win, FALSE);
1882 draw_ok = view->ops->draw(view, line, lineno);
1883 scrollok(view->win, TRUE);
1885 return draw_ok;
1888 static void
1889 redraw_view_dirty(struct view *view)
1891 bool dirty = FALSE;
1892 int lineno;
1894 for (lineno = 0; lineno < view->height; lineno++) {
1895 struct line *line = &view->line[view->offset + lineno];
1897 if (!line->dirty)
1898 continue;
1899 line->dirty = 0;
1900 dirty = TRUE;
1901 if (!draw_view_line(view, lineno))
1902 break;
1905 if (!dirty)
1906 return;
1907 redrawwin(view->win);
1908 if (input_mode)
1909 wnoutrefresh(view->win);
1910 else
1911 wrefresh(view->win);
1914 static void
1915 redraw_view_from(struct view *view, int lineno)
1917 assert(0 <= lineno && lineno < view->height);
1919 for (; lineno < view->height; lineno++) {
1920 if (!draw_view_line(view, lineno))
1921 break;
1924 redrawwin(view->win);
1925 if (input_mode)
1926 wnoutrefresh(view->win);
1927 else
1928 wrefresh(view->win);
1931 static void
1932 redraw_view(struct view *view)
1934 wclear(view->win);
1935 redraw_view_from(view, 0);
1939 static void
1940 update_view_title(struct view *view)
1942 char buf[SIZEOF_STR];
1943 char state[SIZEOF_STR];
1944 size_t bufpos = 0, statelen = 0;
1946 assert(view_is_displayed(view));
1948 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1949 unsigned int view_lines = view->offset + view->height;
1950 unsigned int lines = view->lines
1951 ? MIN(view_lines, view->lines) * 100 / view->lines
1952 : 0;
1954 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1955 view->ops->type,
1956 view->lineno + 1,
1957 view->lines,
1958 lines);
1960 if (view->pipe) {
1961 time_t secs = time(NULL) - view->start_time;
1963 /* Three git seconds are a long time ... */
1964 if (secs > 2)
1965 string_format_from(state, &statelen, " %lds", secs);
1969 string_format_from(buf, &bufpos, "[%s]", view->name);
1970 if (*view->ref && bufpos < view->width) {
1971 size_t refsize = strlen(view->ref);
1972 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1974 if (minsize < view->width)
1975 refsize = view->width - minsize + 7;
1976 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1979 if (statelen && bufpos < view->width) {
1980 string_format_from(buf, &bufpos, " %s", state);
1983 if (view == display[current_view])
1984 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1985 else
1986 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1988 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1989 wclrtoeol(view->title);
1990 wmove(view->title, 0, view->width - 1);
1992 if (input_mode)
1993 wnoutrefresh(view->title);
1994 else
1995 wrefresh(view->title);
1998 static void
1999 resize_display(void)
2001 int offset, i;
2002 struct view *base = display[0];
2003 struct view *view = display[1] ? display[1] : display[0];
2005 /* Setup window dimensions */
2007 getmaxyx(stdscr, base->height, base->width);
2009 /* Make room for the status window. */
2010 base->height -= 1;
2012 if (view != base) {
2013 /* Horizontal split. */
2014 view->width = base->width;
2015 view->height = SCALE_SPLIT_VIEW(base->height);
2016 base->height -= view->height;
2018 /* Make room for the title bar. */
2019 view->height -= 1;
2022 /* Make room for the title bar. */
2023 base->height -= 1;
2025 offset = 0;
2027 foreach_displayed_view (view, i) {
2028 if (!view->win) {
2029 view->win = newwin(view->height, 0, offset, 0);
2030 if (!view->win)
2031 die("Failed to create %s view", view->name);
2033 scrollok(view->win, TRUE);
2035 view->title = newwin(1, 0, offset + view->height, 0);
2036 if (!view->title)
2037 die("Failed to create title window");
2039 } else {
2040 wresize(view->win, view->height, view->width);
2041 mvwin(view->win, offset, 0);
2042 mvwin(view->title, offset + view->height, 0);
2045 offset += view->height + 1;
2049 static void
2050 redraw_display(void)
2052 struct view *view;
2053 int i;
2055 foreach_displayed_view (view, i) {
2056 redraw_view(view);
2057 update_view_title(view);
2061 static void
2062 update_display_cursor(struct view *view)
2064 /* Move the cursor to the right-most column of the cursor line.
2066 * XXX: This could turn out to be a bit expensive, but it ensures that
2067 * the cursor does not jump around. */
2068 if (view->lines) {
2069 wmove(view->win, view->lineno - view->offset, view->width - 1);
2070 wrefresh(view->win);
2075 * Navigation
2078 /* Scrolling backend */
2079 static void
2080 do_scroll_view(struct view *view, int lines)
2082 bool redraw_current_line = FALSE;
2084 /* The rendering expects the new offset. */
2085 view->offset += lines;
2087 assert(0 <= view->offset && view->offset < view->lines);
2088 assert(lines);
2090 /* Move current line into the view. */
2091 if (view->lineno < view->offset) {
2092 view->lineno = view->offset;
2093 redraw_current_line = TRUE;
2094 } else if (view->lineno >= view->offset + view->height) {
2095 view->lineno = view->offset + view->height - 1;
2096 redraw_current_line = TRUE;
2099 assert(view->offset <= view->lineno && view->lineno < view->lines);
2101 /* Redraw the whole screen if scrolling is pointless. */
2102 if (view->height < ABS(lines)) {
2103 redraw_view(view);
2105 } else {
2106 int line = lines > 0 ? view->height - lines : 0;
2107 int end = line + ABS(lines);
2109 wscrl(view->win, lines);
2111 for (; line < end; line++) {
2112 if (!draw_view_line(view, line))
2113 break;
2116 if (redraw_current_line)
2117 draw_view_line(view, view->lineno - view->offset);
2120 redrawwin(view->win);
2121 wrefresh(view->win);
2122 report("");
2125 /* Scroll frontend */
2126 static void
2127 scroll_view(struct view *view, enum request request)
2129 int lines = 1;
2131 assert(view_is_displayed(view));
2133 switch (request) {
2134 case REQ_SCROLL_PAGE_DOWN:
2135 lines = view->height;
2136 case REQ_SCROLL_LINE_DOWN:
2137 if (view->offset + lines > view->lines)
2138 lines = view->lines - view->offset;
2140 if (lines == 0 || view->offset + view->height >= view->lines) {
2141 report("Cannot scroll beyond the last line");
2142 return;
2144 break;
2146 case REQ_SCROLL_PAGE_UP:
2147 lines = view->height;
2148 case REQ_SCROLL_LINE_UP:
2149 if (lines > view->offset)
2150 lines = view->offset;
2152 if (lines == 0) {
2153 report("Cannot scroll beyond the first line");
2154 return;
2157 lines = -lines;
2158 break;
2160 default:
2161 die("request %d not handled in switch", request);
2164 do_scroll_view(view, lines);
2167 /* Cursor moving */
2168 static void
2169 move_view(struct view *view, enum request request)
2171 int scroll_steps = 0;
2172 int steps;
2174 switch (request) {
2175 case REQ_MOVE_FIRST_LINE:
2176 steps = -view->lineno;
2177 break;
2179 case REQ_MOVE_LAST_LINE:
2180 steps = view->lines - view->lineno - 1;
2181 break;
2183 case REQ_MOVE_PAGE_UP:
2184 steps = view->height > view->lineno
2185 ? -view->lineno : -view->height;
2186 break;
2188 case REQ_MOVE_PAGE_DOWN:
2189 steps = view->lineno + view->height >= view->lines
2190 ? view->lines - view->lineno - 1 : view->height;
2191 break;
2193 case REQ_MOVE_UP:
2194 steps = -1;
2195 break;
2197 case REQ_MOVE_DOWN:
2198 steps = 1;
2199 break;
2201 default:
2202 die("request %d not handled in switch", request);
2205 if (steps <= 0 && view->lineno == 0) {
2206 report("Cannot move beyond the first line");
2207 return;
2209 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2210 report("Cannot move beyond the last line");
2211 return;
2214 /* Move the current line */
2215 view->lineno += steps;
2216 assert(0 <= view->lineno && view->lineno < view->lines);
2218 /* Check whether the view needs to be scrolled */
2219 if (view->lineno < view->offset ||
2220 view->lineno >= view->offset + view->height) {
2221 scroll_steps = steps;
2222 if (steps < 0 && -steps > view->offset) {
2223 scroll_steps = -view->offset;
2225 } else if (steps > 0) {
2226 if (view->lineno == view->lines - 1 &&
2227 view->lines > view->height) {
2228 scroll_steps = view->lines - view->offset - 1;
2229 if (scroll_steps >= view->height)
2230 scroll_steps -= view->height - 1;
2235 if (!view_is_displayed(view)) {
2236 view->offset += scroll_steps;
2237 assert(0 <= view->offset && view->offset < view->lines);
2238 view->ops->select(view, &view->line[view->lineno]);
2239 return;
2242 /* Repaint the old "current" line if we be scrolling */
2243 if (ABS(steps) < view->height)
2244 draw_view_line(view, view->lineno - steps - view->offset);
2246 if (scroll_steps) {
2247 do_scroll_view(view, scroll_steps);
2248 return;
2251 /* Draw the current line */
2252 draw_view_line(view, view->lineno - view->offset);
2254 redrawwin(view->win);
2255 wrefresh(view->win);
2256 report("");
2261 * Searching
2264 static void search_view(struct view *view, enum request request);
2266 static bool
2267 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2269 assert(view_is_displayed(view));
2271 if (!view->ops->grep(view, line))
2272 return FALSE;
2274 if (lineno - view->offset >= view->height) {
2275 view->offset = lineno;
2276 view->lineno = lineno;
2277 redraw_view(view);
2279 } else {
2280 unsigned long old_lineno = view->lineno - view->offset;
2282 view->lineno = lineno;
2283 draw_view_line(view, old_lineno);
2285 draw_view_line(view, view->lineno - view->offset);
2286 redrawwin(view->win);
2287 wrefresh(view->win);
2290 report("Line %ld matches '%s'", lineno + 1, view->grep);
2291 return TRUE;
2294 static void
2295 find_next(struct view *view, enum request request)
2297 unsigned long lineno = view->lineno;
2298 int direction;
2300 if (!*view->grep) {
2301 if (!*opt_search)
2302 report("No previous search");
2303 else
2304 search_view(view, request);
2305 return;
2308 switch (request) {
2309 case REQ_SEARCH:
2310 case REQ_FIND_NEXT:
2311 direction = 1;
2312 break;
2314 case REQ_SEARCH_BACK:
2315 case REQ_FIND_PREV:
2316 direction = -1;
2317 break;
2319 default:
2320 return;
2323 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2324 lineno += direction;
2326 /* Note, lineno is unsigned long so will wrap around in which case it
2327 * will become bigger than view->lines. */
2328 for (; lineno < view->lines; lineno += direction) {
2329 struct line *line = &view->line[lineno];
2331 if (find_next_line(view, lineno, line))
2332 return;
2335 report("No match found for '%s'", view->grep);
2338 static void
2339 search_view(struct view *view, enum request request)
2341 int regex_err;
2343 if (view->regex) {
2344 regfree(view->regex);
2345 *view->grep = 0;
2346 } else {
2347 view->regex = calloc(1, sizeof(*view->regex));
2348 if (!view->regex)
2349 return;
2352 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2353 if (regex_err != 0) {
2354 char buf[SIZEOF_STR] = "unknown error";
2356 regerror(regex_err, view->regex, buf, sizeof(buf));
2357 report("Search failed: %s", buf);
2358 return;
2361 string_copy(view->grep, opt_search);
2363 find_next(view, request);
2367 * Incremental updating
2370 static void
2371 reset_view(struct view *view)
2373 int i;
2375 for (i = 0; i < view->lines; i++)
2376 free(view->line[i].data);
2377 free(view->line);
2379 view->line = NULL;
2380 view->offset = 0;
2381 view->lines = 0;
2382 view->lineno = 0;
2383 view->line_size = 0;
2384 view->line_alloc = 0;
2385 view->vid[0] = 0;
2388 static void
2389 free_argv(const char *argv[])
2391 int argc;
2393 for (argc = 0; argv[argc]; argc++)
2394 free((void *) argv[argc]);
2397 static bool
2398 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2400 char buf[SIZEOF_STR];
2401 int argc;
2402 bool noreplace = flags == FORMAT_NONE;
2404 free_argv(dst_argv);
2406 for (argc = 0; src_argv[argc]; argc++) {
2407 const char *arg = src_argv[argc];
2408 size_t bufpos = 0;
2410 while (arg) {
2411 char *next = strstr(arg, "%(");
2412 int len = next - arg;
2413 const char *value;
2415 if (!next || noreplace) {
2416 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2417 noreplace = TRUE;
2418 len = strlen(arg);
2419 value = "";
2421 } else if (!prefixcmp(next, "%(directory)")) {
2422 value = opt_path;
2424 } else if (!prefixcmp(next, "%(file)")) {
2425 value = opt_file;
2427 } else if (!prefixcmp(next, "%(ref)")) {
2428 value = *opt_ref ? opt_ref : "HEAD";
2430 } else if (!prefixcmp(next, "%(head)")) {
2431 value = ref_head;
2433 } else if (!prefixcmp(next, "%(commit)")) {
2434 value = ref_commit;
2436 } else if (!prefixcmp(next, "%(blob)")) {
2437 value = ref_blob;
2439 } else {
2440 report("Unknown replacement: `%s`", next);
2441 return FALSE;
2444 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2445 return FALSE;
2447 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2450 dst_argv[argc] = strdup(buf);
2451 if (!dst_argv[argc])
2452 break;
2455 dst_argv[argc] = NULL;
2457 return src_argv[argc] == NULL;
2460 static bool
2461 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2463 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2464 int bufsize = 0;
2465 int argc;
2467 if (!format_argv(dst_argv, src_argv, flags)) {
2468 free_argv(dst_argv);
2469 return FALSE;
2472 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2473 if (bufsize > 0)
2474 dst[bufsize++] = ' ';
2475 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2478 if (bufsize < SIZEOF_STR)
2479 dst[bufsize] = 0;
2480 free_argv(dst_argv);
2482 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2485 static void
2486 end_update(struct view *view, bool force)
2488 if (!view->pipe)
2489 return;
2490 while (!view->ops->read(view, NULL))
2491 if (!force)
2492 return;
2493 set_nonblocking_input(FALSE);
2494 done_io(view->pipe);
2495 view->pipe = NULL;
2498 static void
2499 setup_update(struct view *view, const char *vid)
2501 set_nonblocking_input(TRUE);
2502 reset_view(view);
2503 string_copy_rev(view->vid, vid);
2504 view->pipe = &view->io;
2505 view->start_time = time(NULL);
2508 static bool
2509 prepare_update(struct view *view, const char *argv[], const char *dir,
2510 enum format_flags flags)
2512 if (view->pipe)
2513 end_update(view, TRUE);
2514 return init_io_rd(&view->io, argv, dir, flags);
2517 static bool
2518 begin_update(struct view *view, bool refresh)
2520 if (init_io_fd(&view->io, opt_pipe)) {
2521 opt_pipe = NULL;
2523 } else if (opt_cmd[0]) {
2524 if (!run_io(&view->io, IO_RD, opt_cmd))
2525 return FALSE;
2526 view->ref[0] = 0;
2527 opt_cmd[0] = 0;
2529 } else if (refresh) {
2530 if (!start_io(&view->io))
2531 return FALSE;
2533 } else {
2534 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2535 opt_path[0] = 0;
2537 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2538 return FALSE;
2540 /* Put the current ref_* value to the view title ref
2541 * member. This is needed by the blob view. Most other
2542 * views sets it automatically after loading because the
2543 * first line is a commit line. */
2544 string_copy_rev(view->ref, view->id);
2547 setup_update(view, view->id);
2549 return TRUE;
2552 #define ITEM_CHUNK_SIZE 256
2553 static void *
2554 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2556 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2557 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2559 if (mem == NULL || num_chunks != num_chunks_new) {
2560 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2561 mem = realloc(mem, *size * item_size);
2564 return mem;
2567 static struct line *
2568 realloc_lines(struct view *view, size_t line_size)
2570 size_t alloc = view->line_alloc;
2571 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2572 sizeof(*view->line));
2574 if (!tmp)
2575 return NULL;
2577 view->line = tmp;
2578 view->line_alloc = alloc;
2579 view->line_size = line_size;
2580 return view->line;
2583 static bool
2584 update_view(struct view *view)
2586 char out_buffer[BUFSIZ * 2];
2587 char *line;
2588 /* The number of lines to read. If too low it will cause too much
2589 * redrawing (and possible flickering), if too high responsiveness
2590 * will suffer. */
2591 unsigned long lines = view->height;
2592 int redraw_from = -1;
2594 if (!view->pipe)
2595 return TRUE;
2597 /* Only redraw if lines are visible. */
2598 if (view->offset + view->height >= view->lines)
2599 redraw_from = view->lines - view->offset;
2601 /* FIXME: This is probably not perfect for backgrounded views. */
2602 if (!realloc_lines(view, view->lines + lines))
2603 goto alloc_error;
2605 while ((line = io_gets(view->pipe))) {
2606 size_t linelen = strlen(line);
2608 if (linelen)
2609 line[linelen - 1] = 0;
2611 if (opt_iconv != ICONV_NONE) {
2612 ICONV_CONST char *inbuf = line;
2613 size_t inlen = linelen;
2615 char *outbuf = out_buffer;
2616 size_t outlen = sizeof(out_buffer);
2618 size_t ret;
2620 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2621 if (ret != (size_t) -1) {
2622 line = out_buffer;
2623 linelen = strlen(out_buffer);
2627 if (!view->ops->read(view, line))
2628 goto alloc_error;
2630 if (lines-- == 1)
2631 break;
2635 int digits;
2637 lines = view->lines;
2638 for (digits = 0; lines; digits++)
2639 lines /= 10;
2641 /* Keep the displayed view in sync with line number scaling. */
2642 if (digits != view->digits) {
2643 view->digits = digits;
2644 redraw_from = 0;
2648 if (io_error(view->pipe)) {
2649 report("Failed to read: %s", io_strerror(view->pipe));
2650 end_update(view, TRUE);
2652 } else if (io_eof(view->pipe)) {
2653 report("");
2654 end_update(view, FALSE);
2657 if (!view_is_displayed(view))
2658 return TRUE;
2660 if (view == VIEW(REQ_VIEW_TREE)) {
2661 /* Clear the view and redraw everything since the tree sorting
2662 * might have rearranged things. */
2663 redraw_view(view);
2665 } else if (redraw_from >= 0) {
2666 /* If this is an incremental update, redraw the previous line
2667 * since for commits some members could have changed when
2668 * loading the main view. */
2669 if (redraw_from > 0)
2670 redraw_from--;
2672 /* Since revision graph visualization requires knowledge
2673 * about the parent commit, it causes a further one-off
2674 * needed to be redrawn for incremental updates. */
2675 if (redraw_from > 0 && opt_rev_graph)
2676 redraw_from--;
2678 /* Incrementally draw avoids flickering. */
2679 redraw_view_from(view, redraw_from);
2682 if (view == VIEW(REQ_VIEW_BLAME))
2683 redraw_view_dirty(view);
2685 /* Update the title _after_ the redraw so that if the redraw picks up a
2686 * commit reference in view->ref it'll be available here. */
2687 update_view_title(view);
2688 return TRUE;
2690 alloc_error:
2691 report("Allocation failure");
2692 end_update(view, TRUE);
2693 return FALSE;
2696 static struct line *
2697 add_line_data(struct view *view, void *data, enum line_type type)
2699 struct line *line = &view->line[view->lines++];
2701 memset(line, 0, sizeof(*line));
2702 line->type = type;
2703 line->data = data;
2705 return line;
2708 static struct line *
2709 add_line_text(struct view *view, const char *text, enum line_type type)
2711 char *data = text ? strdup(text) : NULL;
2713 return data ? add_line_data(view, data, type) : NULL;
2718 * View opening
2721 enum open_flags {
2722 OPEN_DEFAULT = 0, /* Use default view switching. */
2723 OPEN_SPLIT = 1, /* Split current view. */
2724 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2725 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2726 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2727 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2728 OPEN_PREPARED = 32, /* Open already prepared command. */
2731 static void
2732 open_view(struct view *prev, enum request request, enum open_flags flags)
2734 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2735 bool split = !!(flags & OPEN_SPLIT);
2736 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2737 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2738 struct view *view = VIEW(request);
2739 int nviews = displayed_views();
2740 struct view *base_view = display[0];
2742 if (view == prev && nviews == 1 && !reload) {
2743 report("Already in %s view", view->name);
2744 return;
2747 if (view->git_dir && !opt_git_dir[0]) {
2748 report("The %s view is disabled in pager view", view->name);
2749 return;
2752 if (split) {
2753 display[1] = view;
2754 if (!backgrounded)
2755 current_view = 1;
2756 } else if (!nomaximize) {
2757 /* Maximize the current view. */
2758 memset(display, 0, sizeof(display));
2759 current_view = 0;
2760 display[current_view] = view;
2763 /* Resize the view when switching between split- and full-screen,
2764 * or when switching between two different full-screen views. */
2765 if (nviews != displayed_views() ||
2766 (nviews == 1 && base_view != display[0]))
2767 resize_display();
2769 if (view->pipe)
2770 end_update(view, TRUE);
2772 if (view->ops->open) {
2773 if (!view->ops->open(view)) {
2774 report("Failed to load %s view", view->name);
2775 return;
2778 } else if ((reload || strcmp(view->vid, view->id)) &&
2779 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2780 report("Failed to load %s view", view->name);
2781 return;
2784 if (split && prev->lineno - prev->offset >= prev->height) {
2785 /* Take the title line into account. */
2786 int lines = prev->lineno - prev->offset - prev->height + 1;
2788 /* Scroll the view that was split if the current line is
2789 * outside the new limited view. */
2790 do_scroll_view(prev, lines);
2793 if (prev && view != prev) {
2794 if (split && !backgrounded) {
2795 /* "Blur" the previous view. */
2796 update_view_title(prev);
2799 view->parent = prev;
2802 if (view->pipe && view->lines == 0) {
2803 /* Clear the old view and let the incremental updating refill
2804 * the screen. */
2805 werase(view->win);
2806 report("");
2807 } else if (view_is_displayed(view)) {
2808 redraw_view(view);
2809 report("");
2812 /* If the view is backgrounded the above calls to report()
2813 * won't redraw the view title. */
2814 if (backgrounded)
2815 update_view_title(view);
2818 static void
2819 open_external_viewer(const char *argv[], const char *dir)
2821 def_prog_mode(); /* save current tty modes */
2822 endwin(); /* restore original tty modes */
2823 run_io_fg(argv, dir);
2824 fprintf(stderr, "Press Enter to continue");
2825 getc(opt_tty);
2826 reset_prog_mode();
2827 redraw_display();
2830 static void
2831 open_mergetool(const char *file)
2833 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2835 open_external_viewer(mergetool_argv, NULL);
2838 static void
2839 open_editor(bool from_root, const char *file)
2841 const char *editor_argv[] = { "vi", file, NULL };
2842 const char *editor;
2844 editor = getenv("GIT_EDITOR");
2845 if (!editor && *opt_editor)
2846 editor = opt_editor;
2847 if (!editor)
2848 editor = getenv("VISUAL");
2849 if (!editor)
2850 editor = getenv("EDITOR");
2851 if (!editor)
2852 editor = "vi";
2854 editor_argv[0] = editor;
2855 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2858 static void
2859 open_run_request(enum request request)
2861 struct run_request *req = get_run_request(request);
2862 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2864 if (!req) {
2865 report("Unknown run request");
2866 return;
2869 if (format_argv(argv, req->argv, FORMAT_ALL))
2870 open_external_viewer(argv, NULL);
2871 free_argv(argv);
2875 * User request switch noodle
2878 static int
2879 view_driver(struct view *view, enum request request)
2881 int i;
2883 if (request == REQ_NONE) {
2884 doupdate();
2885 return TRUE;
2888 if (request > REQ_NONE) {
2889 open_run_request(request);
2890 /* FIXME: When all views can refresh always do this. */
2891 if (view == VIEW(REQ_VIEW_STATUS) ||
2892 view == VIEW(REQ_VIEW_MAIN) ||
2893 view == VIEW(REQ_VIEW_LOG) ||
2894 view == VIEW(REQ_VIEW_STAGE))
2895 request = REQ_REFRESH;
2896 else
2897 return TRUE;
2900 if (view && view->lines) {
2901 request = view->ops->request(view, request, &view->line[view->lineno]);
2902 if (request == REQ_NONE)
2903 return TRUE;
2906 switch (request) {
2907 case REQ_MOVE_UP:
2908 case REQ_MOVE_DOWN:
2909 case REQ_MOVE_PAGE_UP:
2910 case REQ_MOVE_PAGE_DOWN:
2911 case REQ_MOVE_FIRST_LINE:
2912 case REQ_MOVE_LAST_LINE:
2913 move_view(view, request);
2914 break;
2916 case REQ_SCROLL_LINE_DOWN:
2917 case REQ_SCROLL_LINE_UP:
2918 case REQ_SCROLL_PAGE_DOWN:
2919 case REQ_SCROLL_PAGE_UP:
2920 scroll_view(view, request);
2921 break;
2923 case REQ_VIEW_BLAME:
2924 if (!opt_file[0]) {
2925 report("No file chosen, press %s to open tree view",
2926 get_key(REQ_VIEW_TREE));
2927 break;
2929 open_view(view, request, OPEN_DEFAULT);
2930 break;
2932 case REQ_VIEW_BLOB:
2933 if (!ref_blob[0]) {
2934 report("No file chosen, press %s to open tree view",
2935 get_key(REQ_VIEW_TREE));
2936 break;
2938 open_view(view, request, OPEN_DEFAULT);
2939 break;
2941 case REQ_VIEW_PAGER:
2942 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2943 report("No pager content, press %s to run command from prompt",
2944 get_key(REQ_PROMPT));
2945 break;
2947 open_view(view, request, OPEN_DEFAULT);
2948 break;
2950 case REQ_VIEW_STAGE:
2951 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2952 report("No stage content, press %s to open the status view and choose file",
2953 get_key(REQ_VIEW_STATUS));
2954 break;
2956 open_view(view, request, OPEN_DEFAULT);
2957 break;
2959 case REQ_VIEW_STATUS:
2960 if (opt_is_inside_work_tree == FALSE) {
2961 report("The status view requires a working tree");
2962 break;
2964 open_view(view, request, OPEN_DEFAULT);
2965 break;
2967 case REQ_VIEW_MAIN:
2968 case REQ_VIEW_DIFF:
2969 case REQ_VIEW_LOG:
2970 case REQ_VIEW_TREE:
2971 case REQ_VIEW_HELP:
2972 open_view(view, request, OPEN_DEFAULT);
2973 break;
2975 case REQ_NEXT:
2976 case REQ_PREVIOUS:
2977 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2979 if ((view == VIEW(REQ_VIEW_DIFF) &&
2980 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2981 (view == VIEW(REQ_VIEW_DIFF) &&
2982 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2983 (view == VIEW(REQ_VIEW_STAGE) &&
2984 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2985 (view == VIEW(REQ_VIEW_BLOB) &&
2986 view->parent == VIEW(REQ_VIEW_TREE))) {
2987 int line;
2989 view = view->parent;
2990 line = view->lineno;
2991 move_view(view, request);
2992 if (view_is_displayed(view))
2993 update_view_title(view);
2994 if (line != view->lineno)
2995 view->ops->request(view, REQ_ENTER,
2996 &view->line[view->lineno]);
2998 } else {
2999 move_view(view, request);
3001 break;
3003 case REQ_VIEW_NEXT:
3005 int nviews = displayed_views();
3006 int next_view = (current_view + 1) % nviews;
3008 if (next_view == current_view) {
3009 report("Only one view is displayed");
3010 break;
3013 current_view = next_view;
3014 /* Blur out the title of the previous view. */
3015 update_view_title(view);
3016 report("");
3017 break;
3019 case REQ_REFRESH:
3020 report("Refreshing is not yet supported for the %s view", view->name);
3021 break;
3023 case REQ_MAXIMIZE:
3024 if (displayed_views() == 2)
3025 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3026 break;
3028 case REQ_TOGGLE_LINENO:
3029 opt_line_number = !opt_line_number;
3030 redraw_display();
3031 break;
3033 case REQ_TOGGLE_DATE:
3034 opt_date = !opt_date;
3035 redraw_display();
3036 break;
3038 case REQ_TOGGLE_AUTHOR:
3039 opt_author = !opt_author;
3040 redraw_display();
3041 break;
3043 case REQ_TOGGLE_REV_GRAPH:
3044 opt_rev_graph = !opt_rev_graph;
3045 redraw_display();
3046 break;
3048 case REQ_TOGGLE_REFS:
3049 opt_show_refs = !opt_show_refs;
3050 redraw_display();
3051 break;
3053 case REQ_SEARCH:
3054 case REQ_SEARCH_BACK:
3055 search_view(view, request);
3056 break;
3058 case REQ_FIND_NEXT:
3059 case REQ_FIND_PREV:
3060 find_next(view, request);
3061 break;
3063 case REQ_STOP_LOADING:
3064 for (i = 0; i < ARRAY_SIZE(views); i++) {
3065 view = &views[i];
3066 if (view->pipe)
3067 report("Stopped loading the %s view", view->name),
3068 end_update(view, TRUE);
3070 break;
3072 case REQ_SHOW_VERSION:
3073 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3074 return TRUE;
3076 case REQ_SCREEN_RESIZE:
3077 resize_display();
3078 /* Fall-through */
3079 case REQ_SCREEN_REDRAW:
3080 redraw_display();
3081 break;
3083 case REQ_EDIT:
3084 report("Nothing to edit");
3085 break;
3087 case REQ_ENTER:
3088 report("Nothing to enter");
3089 break;
3091 case REQ_VIEW_CLOSE:
3092 /* XXX: Mark closed views by letting view->parent point to the
3093 * view itself. Parents to closed view should never be
3094 * followed. */
3095 if (view->parent &&
3096 view->parent->parent != view->parent) {
3097 memset(display, 0, sizeof(display));
3098 current_view = 0;
3099 display[current_view] = view->parent;
3100 view->parent = view;
3101 resize_display();
3102 redraw_display();
3103 report("");
3104 break;
3106 /* Fall-through */
3107 case REQ_QUIT:
3108 return FALSE;
3110 default:
3111 report("Unknown key, press 'h' for help");
3112 return TRUE;
3115 return TRUE;
3120 * Pager backend
3123 static bool
3124 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3126 char *text = line->data;
3128 if (opt_line_number && draw_lineno(view, lineno))
3129 return TRUE;
3131 draw_text(view, line->type, text, TRUE);
3132 return TRUE;
3135 static bool
3136 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3138 char refbuf[SIZEOF_STR];
3139 char *ref = NULL;
3140 FILE *pipe;
3142 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3143 return TRUE;
3145 pipe = popen(refbuf, "r");
3146 if (!pipe)
3147 return TRUE;
3149 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3150 ref = chomp_string(ref);
3151 pclose(pipe);
3153 if (!ref || !*ref)
3154 return TRUE;
3156 /* This is the only fatal call, since it can "corrupt" the buffer. */
3157 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3158 return FALSE;
3160 return TRUE;
3163 static void
3164 add_pager_refs(struct view *view, struct line *line)
3166 char buf[SIZEOF_STR];
3167 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3168 struct ref **refs;
3169 size_t bufpos = 0, refpos = 0;
3170 const char *sep = "Refs: ";
3171 bool is_tag = FALSE;
3173 assert(line->type == LINE_COMMIT);
3175 refs = get_refs(commit_id);
3176 if (!refs) {
3177 if (view == VIEW(REQ_VIEW_DIFF))
3178 goto try_add_describe_ref;
3179 return;
3182 do {
3183 struct ref *ref = refs[refpos];
3184 const char *fmt = ref->tag ? "%s[%s]" :
3185 ref->remote ? "%s<%s>" : "%s%s";
3187 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3188 return;
3189 sep = ", ";
3190 if (ref->tag)
3191 is_tag = TRUE;
3192 } while (refs[refpos++]->next);
3194 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3195 try_add_describe_ref:
3196 /* Add <tag>-g<commit_id> "fake" reference. */
3197 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3198 return;
3201 if (bufpos == 0)
3202 return;
3204 if (!realloc_lines(view, view->line_size + 1))
3205 return;
3207 add_line_text(view, buf, LINE_PP_REFS);
3210 static bool
3211 pager_read(struct view *view, char *data)
3213 struct line *line;
3215 if (!data)
3216 return TRUE;
3218 line = add_line_text(view, data, get_line_type(data));
3219 if (!line)
3220 return FALSE;
3222 if (line->type == LINE_COMMIT &&
3223 (view == VIEW(REQ_VIEW_DIFF) ||
3224 view == VIEW(REQ_VIEW_LOG)))
3225 add_pager_refs(view, line);
3227 return TRUE;
3230 static enum request
3231 pager_request(struct view *view, enum request request, struct line *line)
3233 int split = 0;
3235 if (request != REQ_ENTER)
3236 return request;
3238 if (line->type == LINE_COMMIT &&
3239 (view == VIEW(REQ_VIEW_LOG) ||
3240 view == VIEW(REQ_VIEW_PAGER))) {
3241 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3242 split = 1;
3245 /* Always scroll the view even if it was split. That way
3246 * you can use Enter to scroll through the log view and
3247 * split open each commit diff. */
3248 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3250 /* FIXME: A minor workaround. Scrolling the view will call report("")
3251 * but if we are scrolling a non-current view this won't properly
3252 * update the view title. */
3253 if (split)
3254 update_view_title(view);
3256 return REQ_NONE;
3259 static bool
3260 pager_grep(struct view *view, struct line *line)
3262 regmatch_t pmatch;
3263 char *text = line->data;
3265 if (!*text)
3266 return FALSE;
3268 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3269 return FALSE;
3271 return TRUE;
3274 static void
3275 pager_select(struct view *view, struct line *line)
3277 if (line->type == LINE_COMMIT) {
3278 char *text = (char *)line->data + STRING_SIZE("commit ");
3280 if (view != VIEW(REQ_VIEW_PAGER))
3281 string_copy_rev(view->ref, text);
3282 string_copy_rev(ref_commit, text);
3286 static struct view_ops pager_ops = {
3287 "line",
3288 NULL,
3289 NULL,
3290 pager_read,
3291 pager_draw,
3292 pager_request,
3293 pager_grep,
3294 pager_select,
3297 static const char *log_argv[SIZEOF_ARG] = {
3298 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3301 static enum request
3302 log_request(struct view *view, enum request request, struct line *line)
3304 switch (request) {
3305 case REQ_REFRESH:
3306 load_refs();
3307 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3308 return REQ_NONE;
3309 default:
3310 return pager_request(view, request, line);
3314 static struct view_ops log_ops = {
3315 "line",
3316 log_argv,
3317 NULL,
3318 pager_read,
3319 pager_draw,
3320 log_request,
3321 pager_grep,
3322 pager_select,
3325 static const char *diff_argv[SIZEOF_ARG] = {
3326 "git", "show", "--pretty=fuller", "--no-color", "--root",
3327 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3330 static struct view_ops diff_ops = {
3331 "line",
3332 diff_argv,
3333 NULL,
3334 pager_read,
3335 pager_draw,
3336 pager_request,
3337 pager_grep,
3338 pager_select,
3342 * Help backend
3345 static bool
3346 help_open(struct view *view)
3348 char buf[BUFSIZ];
3349 int lines = ARRAY_SIZE(req_info) + 2;
3350 int i;
3352 if (view->lines > 0)
3353 return TRUE;
3355 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3356 if (!req_info[i].request)
3357 lines++;
3359 lines += run_requests + 1;
3361 view->line = calloc(lines, sizeof(*view->line));
3362 if (!view->line)
3363 return FALSE;
3365 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3367 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3368 const char *key;
3370 if (req_info[i].request == REQ_NONE)
3371 continue;
3373 if (!req_info[i].request) {
3374 add_line_text(view, "", LINE_DEFAULT);
3375 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3376 continue;
3379 key = get_key(req_info[i].request);
3380 if (!*key)
3381 key = "(no key defined)";
3383 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3384 continue;
3386 add_line_text(view, buf, LINE_DEFAULT);
3389 if (run_requests) {
3390 add_line_text(view, "", LINE_DEFAULT);
3391 add_line_text(view, "External commands:", LINE_DEFAULT);
3394 for (i = 0; i < run_requests; i++) {
3395 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3396 const char *key;
3397 char cmd[SIZEOF_STR];
3398 size_t bufpos;
3399 int argc;
3401 if (!req)
3402 continue;
3404 key = get_key_name(req->key);
3405 if (!*key)
3406 key = "(no key defined)";
3408 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3409 if (!string_format_from(cmd, &bufpos, "%s%s",
3410 argc ? " " : "", req->argv[argc]))
3411 return REQ_NONE;
3413 if (!string_format(buf, " %-10s %-14s `%s`",
3414 keymap_table[req->keymap].name, key, cmd))
3415 continue;
3417 add_line_text(view, buf, LINE_DEFAULT);
3420 return TRUE;
3423 static struct view_ops help_ops = {
3424 "line",
3425 NULL,
3426 help_open,
3427 NULL,
3428 pager_draw,
3429 pager_request,
3430 pager_grep,
3431 pager_select,
3436 * Tree backend
3439 struct tree_stack_entry {
3440 struct tree_stack_entry *prev; /* Entry below this in the stack */
3441 unsigned long lineno; /* Line number to restore */
3442 char *name; /* Position of name in opt_path */
3445 /* The top of the path stack. */
3446 static struct tree_stack_entry *tree_stack = NULL;
3447 unsigned long tree_lineno = 0;
3449 static void
3450 pop_tree_stack_entry(void)
3452 struct tree_stack_entry *entry = tree_stack;
3454 tree_lineno = entry->lineno;
3455 entry->name[0] = 0;
3456 tree_stack = entry->prev;
3457 free(entry);
3460 static void
3461 push_tree_stack_entry(const char *name, unsigned long lineno)
3463 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3464 size_t pathlen = strlen(opt_path);
3466 if (!entry)
3467 return;
3469 entry->prev = tree_stack;
3470 entry->name = opt_path + pathlen;
3471 tree_stack = entry;
3473 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3474 pop_tree_stack_entry();
3475 return;
3478 /* Move the current line to the first tree entry. */
3479 tree_lineno = 1;
3480 entry->lineno = lineno;
3483 /* Parse output from git-ls-tree(1):
3485 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3486 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3487 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3488 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3491 #define SIZEOF_TREE_ATTR \
3492 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3494 #define TREE_UP_FORMAT "040000 tree %s\t.."
3496 static int
3497 tree_compare_entry(enum line_type type1, const char *name1,
3498 enum line_type type2, const char *name2)
3500 if (type1 != type2) {
3501 if (type1 == LINE_TREE_DIR)
3502 return -1;
3503 return 1;
3506 return strcmp(name1, name2);
3509 static const char *
3510 tree_path(struct line *line)
3512 const char *path = line->data;
3514 return path + SIZEOF_TREE_ATTR;
3517 static bool
3518 tree_read(struct view *view, char *text)
3520 size_t textlen = text ? strlen(text) : 0;
3521 char buf[SIZEOF_STR];
3522 unsigned long pos;
3523 enum line_type type;
3524 bool first_read = view->lines == 0;
3526 if (!text)
3527 return TRUE;
3528 if (textlen <= SIZEOF_TREE_ATTR)
3529 return FALSE;
3531 type = text[STRING_SIZE("100644 ")] == 't'
3532 ? LINE_TREE_DIR : LINE_TREE_FILE;
3534 if (first_read) {
3535 /* Add path info line */
3536 if (!string_format(buf, "Directory path /%s", opt_path) ||
3537 !realloc_lines(view, view->line_size + 1) ||
3538 !add_line_text(view, buf, LINE_DEFAULT))
3539 return FALSE;
3541 /* Insert "link" to parent directory. */
3542 if (*opt_path) {
3543 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3544 !realloc_lines(view, view->line_size + 1) ||
3545 !add_line_text(view, buf, LINE_TREE_DIR))
3546 return FALSE;
3550 /* Strip the path part ... */
3551 if (*opt_path) {
3552 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3553 size_t striplen = strlen(opt_path);
3554 char *path = text + SIZEOF_TREE_ATTR;
3556 if (pathlen > striplen)
3557 memmove(path, path + striplen,
3558 pathlen - striplen + 1);
3561 /* Skip "Directory ..." and ".." line. */
3562 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3563 struct line *line = &view->line[pos];
3564 const char *path1 = tree_path(line);
3565 char *path2 = text + SIZEOF_TREE_ATTR;
3566 int cmp = tree_compare_entry(line->type, path1, type, path2);
3568 if (cmp <= 0)
3569 continue;
3571 text = strdup(text);
3572 if (!text)
3573 return FALSE;
3575 if (view->lines > pos)
3576 memmove(&view->line[pos + 1], &view->line[pos],
3577 (view->lines - pos) * sizeof(*line));
3579 line = &view->line[pos];
3580 line->data = text;
3581 line->type = type;
3582 view->lines++;
3583 return TRUE;
3586 if (!add_line_text(view, text, type))
3587 return FALSE;
3589 if (tree_lineno > view->lineno) {
3590 view->lineno = tree_lineno;
3591 tree_lineno = 0;
3594 return TRUE;
3597 static enum request
3598 tree_request(struct view *view, enum request request, struct line *line)
3600 enum open_flags flags;
3602 switch (request) {
3603 case REQ_VIEW_BLAME:
3604 if (line->type != LINE_TREE_FILE) {
3605 report("Blame only supported for files");
3606 return REQ_NONE;
3609 string_copy(opt_ref, view->vid);
3610 return request;
3612 case REQ_EDIT:
3613 if (line->type != LINE_TREE_FILE) {
3614 report("Edit only supported for files");
3615 } else if (!is_head_commit(view->vid)) {
3616 report("Edit only supported for files in the current work tree");
3617 } else {
3618 open_editor(TRUE, opt_file);
3620 return REQ_NONE;
3622 case REQ_TREE_PARENT:
3623 if (!*opt_path) {
3624 /* quit view if at top of tree */
3625 return REQ_VIEW_CLOSE;
3627 /* fake 'cd ..' */
3628 line = &view->line[1];
3629 break;
3631 case REQ_ENTER:
3632 break;
3634 default:
3635 return request;
3638 /* Cleanup the stack if the tree view is at a different tree. */
3639 while (!*opt_path && tree_stack)
3640 pop_tree_stack_entry();
3642 switch (line->type) {
3643 case LINE_TREE_DIR:
3644 /* Depending on whether it is a subdir or parent (updir?) link
3645 * mangle the path buffer. */
3646 if (line == &view->line[1] && *opt_path) {
3647 pop_tree_stack_entry();
3649 } else {
3650 const char *basename = tree_path(line);
3652 push_tree_stack_entry(basename, view->lineno);
3655 /* Trees and subtrees share the same ID, so they are not not
3656 * unique like blobs. */
3657 flags = OPEN_RELOAD;
3658 request = REQ_VIEW_TREE;
3659 break;
3661 case LINE_TREE_FILE:
3662 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3663 request = REQ_VIEW_BLOB;
3664 break;
3666 default:
3667 return TRUE;
3670 open_view(view, request, flags);
3671 if (request == REQ_VIEW_TREE) {
3672 view->lineno = tree_lineno;
3675 return REQ_NONE;
3678 static void
3679 tree_select(struct view *view, struct line *line)
3681 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3683 if (line->type == LINE_TREE_FILE) {
3684 string_copy_rev(ref_blob, text);
3685 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3687 } else if (line->type != LINE_TREE_DIR) {
3688 return;
3691 string_copy_rev(view->ref, text);
3694 static const char *tree_argv[SIZEOF_ARG] = {
3695 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3698 static struct view_ops tree_ops = {
3699 "file",
3700 tree_argv,
3701 NULL,
3702 tree_read,
3703 pager_draw,
3704 tree_request,
3705 pager_grep,
3706 tree_select,
3709 static bool
3710 blob_read(struct view *view, char *line)
3712 if (!line)
3713 return TRUE;
3714 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3717 static const char *blob_argv[SIZEOF_ARG] = {
3718 "git", "cat-file", "blob", "%(blob)", NULL
3721 static struct view_ops blob_ops = {
3722 "line",
3723 blob_argv,
3724 NULL,
3725 blob_read,
3726 pager_draw,
3727 pager_request,
3728 pager_grep,
3729 pager_select,
3733 * Blame backend
3735 * Loading the blame view is a two phase job:
3737 * 1. File content is read either using opt_file from the
3738 * filesystem or using git-cat-file.
3739 * 2. Then blame information is incrementally added by
3740 * reading output from git-blame.
3743 static const char *blame_head_argv[] = {
3744 "git", "blame", "--incremental", "--", "%(file)", NULL
3747 static const char *blame_ref_argv[] = {
3748 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3751 static const char *blame_cat_file_argv[] = {
3752 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3755 struct blame_commit {
3756 char id[SIZEOF_REV]; /* SHA1 ID. */
3757 char title[128]; /* First line of the commit message. */
3758 char author[75]; /* Author of the commit. */
3759 struct tm time; /* Date from the author ident. */
3760 char filename[128]; /* Name of file. */
3763 struct blame {
3764 struct blame_commit *commit;
3765 char text[1];
3768 static bool
3769 blame_open(struct view *view)
3771 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3772 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3773 return FALSE;
3776 setup_update(view, opt_file);
3777 string_format(view->ref, "%s ...", opt_file);
3779 return TRUE;
3782 static struct blame_commit *
3783 get_blame_commit(struct view *view, const char *id)
3785 size_t i;
3787 for (i = 0; i < view->lines; i++) {
3788 struct blame *blame = view->line[i].data;
3790 if (!blame->commit)
3791 continue;
3793 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3794 return blame->commit;
3798 struct blame_commit *commit = calloc(1, sizeof(*commit));
3800 if (commit)
3801 string_ncopy(commit->id, id, SIZEOF_REV);
3802 return commit;
3806 static bool
3807 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3809 const char *pos = *posref;
3811 *posref = NULL;
3812 pos = strchr(pos + 1, ' ');
3813 if (!pos || !isdigit(pos[1]))
3814 return FALSE;
3815 *number = atoi(pos + 1);
3816 if (*number < min || *number > max)
3817 return FALSE;
3819 *posref = pos;
3820 return TRUE;
3823 static struct blame_commit *
3824 parse_blame_commit(struct view *view, const char *text, int *blamed)
3826 struct blame_commit *commit;
3827 struct blame *blame;
3828 const char *pos = text + SIZEOF_REV - 1;
3829 size_t lineno;
3830 size_t group;
3832 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3833 return NULL;
3835 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3836 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3837 return NULL;
3839 commit = get_blame_commit(view, text);
3840 if (!commit)
3841 return NULL;
3843 *blamed += group;
3844 while (group--) {
3845 struct line *line = &view->line[lineno + group - 1];
3847 blame = line->data;
3848 blame->commit = commit;
3849 line->dirty = 1;
3852 return commit;
3855 static bool
3856 blame_read_file(struct view *view, const char *line, bool *read_file)
3858 if (!line) {
3859 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3860 struct io io = {};
3862 if (view->lines == 0 && !view->parent)
3863 die("No blame exist for %s", view->vid);
3865 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3866 report("Failed to load blame data");
3867 return TRUE;
3870 done_io(view->pipe);
3871 view->io = io;
3872 *read_file = FALSE;
3873 return FALSE;
3875 } else {
3876 size_t linelen = strlen(line);
3877 struct blame *blame = malloc(sizeof(*blame) + linelen);
3879 blame->commit = NULL;
3880 strncpy(blame->text, line, linelen);
3881 blame->text[linelen] = 0;
3882 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3886 static bool
3887 match_blame_header(const char *name, char **line)
3889 size_t namelen = strlen(name);
3890 bool matched = !strncmp(name, *line, namelen);
3892 if (matched)
3893 *line += namelen;
3895 return matched;
3898 static bool
3899 blame_read(struct view *view, char *line)
3901 static struct blame_commit *commit = NULL;
3902 static int blamed = 0;
3903 static time_t author_time;
3904 static bool read_file = TRUE;
3906 if (read_file)
3907 return blame_read_file(view, line, &read_file);
3909 if (!line) {
3910 /* Reset all! */
3911 commit = NULL;
3912 blamed = 0;
3913 read_file = TRUE;
3914 string_format(view->ref, "%s", view->vid);
3915 if (view_is_displayed(view)) {
3916 update_view_title(view);
3917 redraw_view_from(view, 0);
3919 return TRUE;
3922 if (!commit) {
3923 commit = parse_blame_commit(view, line, &blamed);
3924 string_format(view->ref, "%s %2d%%", view->vid,
3925 blamed * 100 / view->lines);
3927 } else if (match_blame_header("author ", &line)) {
3928 string_ncopy(commit->author, line, strlen(line));
3930 } else if (match_blame_header("author-time ", &line)) {
3931 author_time = (time_t) atol(line);
3933 } else if (match_blame_header("author-tz ", &line)) {
3934 long tz;
3936 tz = ('0' - line[1]) * 60 * 60 * 10;
3937 tz += ('0' - line[2]) * 60 * 60;
3938 tz += ('0' - line[3]) * 60;
3939 tz += ('0' - line[4]) * 60;
3941 if (line[0] == '-')
3942 tz = -tz;
3944 author_time -= tz;
3945 gmtime_r(&author_time, &commit->time);
3947 } else if (match_blame_header("summary ", &line)) {
3948 string_ncopy(commit->title, line, strlen(line));
3950 } else if (match_blame_header("filename ", &line)) {
3951 string_ncopy(commit->filename, line, strlen(line));
3952 commit = NULL;
3955 return TRUE;
3958 static bool
3959 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3961 struct blame *blame = line->data;
3962 struct tm *time = NULL;
3963 const char *id = NULL, *author = NULL;
3965 if (blame->commit && *blame->commit->filename) {
3966 id = blame->commit->id;
3967 author = blame->commit->author;
3968 time = &blame->commit->time;
3971 if (opt_date && draw_date(view, time))
3972 return TRUE;
3974 if (opt_author &&
3975 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3976 return TRUE;
3978 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3979 return TRUE;
3981 if (draw_lineno(view, lineno))
3982 return TRUE;
3984 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3985 return TRUE;
3988 static enum request
3989 blame_request(struct view *view, enum request request, struct line *line)
3991 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3992 struct blame *blame = line->data;
3994 switch (request) {
3995 case REQ_VIEW_BLAME:
3996 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3997 report("Commit ID unknown");
3998 break;
4000 string_copy(opt_ref, blame->commit->id);
4001 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4002 return request;
4004 case REQ_ENTER:
4005 if (!blame->commit) {
4006 report("No commit loaded yet");
4007 break;
4010 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4011 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4012 break;
4014 if (!strcmp(blame->commit->id, NULL_ID)) {
4015 struct view *diff = VIEW(REQ_VIEW_DIFF);
4016 const char *diff_index_argv[] = {
4017 "git", "diff-index", "--root", "--cached",
4018 "--patch-with-stat", "-C", "-M",
4019 "HEAD", "--", view->vid, NULL
4022 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4023 report("Failed to allocate diff command");
4024 break;
4026 flags |= OPEN_PREPARED;
4029 open_view(view, REQ_VIEW_DIFF, flags);
4030 break;
4032 default:
4033 return request;
4036 return REQ_NONE;
4039 static bool
4040 blame_grep(struct view *view, struct line *line)
4042 struct blame *blame = line->data;
4043 struct blame_commit *commit = blame->commit;
4044 regmatch_t pmatch;
4046 #define MATCH(text, on) \
4047 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4049 if (commit) {
4050 char buf[DATE_COLS + 1];
4052 if (MATCH(commit->title, 1) ||
4053 MATCH(commit->author, opt_author) ||
4054 MATCH(commit->id, opt_date))
4055 return TRUE;
4057 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4058 MATCH(buf, 1))
4059 return TRUE;
4062 return MATCH(blame->text, 1);
4064 #undef MATCH
4067 static void
4068 blame_select(struct view *view, struct line *line)
4070 struct blame *blame = line->data;
4071 struct blame_commit *commit = blame->commit;
4073 if (!commit)
4074 return;
4076 if (!strcmp(commit->id, NULL_ID))
4077 string_ncopy(ref_commit, "HEAD", 4);
4078 else
4079 string_copy_rev(ref_commit, commit->id);
4082 static struct view_ops blame_ops = {
4083 "line",
4084 NULL,
4085 blame_open,
4086 blame_read,
4087 blame_draw,
4088 blame_request,
4089 blame_grep,
4090 blame_select,
4094 * Status backend
4097 struct status {
4098 char status;
4099 struct {
4100 mode_t mode;
4101 char rev[SIZEOF_REV];
4102 char name[SIZEOF_STR];
4103 } old;
4104 struct {
4105 mode_t mode;
4106 char rev[SIZEOF_REV];
4107 char name[SIZEOF_STR];
4108 } new;
4111 static char status_onbranch[SIZEOF_STR];
4112 static struct status stage_status;
4113 static enum line_type stage_line_type;
4114 static size_t stage_chunks;
4115 static int *stage_chunk;
4117 /* This should work even for the "On branch" line. */
4118 static inline bool
4119 status_has_none(struct view *view, struct line *line)
4121 return line < view->line + view->lines && !line[1].data;
4124 /* Get fields from the diff line:
4125 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4127 static inline bool
4128 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4130 const char *old_mode = buf + 1;
4131 const char *new_mode = buf + 8;
4132 const char *old_rev = buf + 15;
4133 const char *new_rev = buf + 56;
4134 const char *status = buf + 97;
4136 if (bufsize < 99 ||
4137 old_mode[-1] != ':' ||
4138 new_mode[-1] != ' ' ||
4139 old_rev[-1] != ' ' ||
4140 new_rev[-1] != ' ' ||
4141 status[-1] != ' ')
4142 return FALSE;
4144 file->status = *status;
4146 string_copy_rev(file->old.rev, old_rev);
4147 string_copy_rev(file->new.rev, new_rev);
4149 file->old.mode = strtoul(old_mode, NULL, 8);
4150 file->new.mode = strtoul(new_mode, NULL, 8);
4152 file->old.name[0] = file->new.name[0] = 0;
4154 return TRUE;
4157 static bool
4158 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4160 struct status *file = NULL;
4161 struct status *unmerged = NULL;
4162 char buf[SIZEOF_STR * 4];
4163 size_t bufsize = 0;
4164 FILE *pipe;
4166 pipe = popen(cmd, "r");
4167 if (!pipe)
4168 return FALSE;
4170 add_line_data(view, NULL, type);
4172 while (!feof(pipe) && !ferror(pipe)) {
4173 char *sep;
4174 size_t readsize;
4176 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4177 if (!readsize)
4178 break;
4179 bufsize += readsize;
4181 /* Process while we have NUL chars. */
4182 while ((sep = memchr(buf, 0, bufsize))) {
4183 size_t sepsize = sep - buf + 1;
4185 if (!file) {
4186 if (!realloc_lines(view, view->line_size + 1))
4187 goto error_out;
4189 file = calloc(1, sizeof(*file));
4190 if (!file)
4191 goto error_out;
4193 add_line_data(view, file, type);
4196 /* Parse diff info part. */
4197 if (status) {
4198 file->status = status;
4199 if (status == 'A')
4200 string_copy(file->old.rev, NULL_ID);
4202 } else if (!file->status) {
4203 if (!status_get_diff(file, buf, sepsize))
4204 goto error_out;
4206 bufsize -= sepsize;
4207 memmove(buf, sep + 1, bufsize);
4209 sep = memchr(buf, 0, bufsize);
4210 if (!sep)
4211 break;
4212 sepsize = sep - buf + 1;
4214 /* Collapse all 'M'odified entries that
4215 * follow a associated 'U'nmerged entry.
4217 if (file->status == 'U') {
4218 unmerged = file;
4220 } else if (unmerged) {
4221 int collapse = !strcmp(buf, unmerged->new.name);
4223 unmerged = NULL;
4224 if (collapse) {
4225 free(file);
4226 view->lines--;
4227 continue;
4232 /* Grab the old name for rename/copy. */
4233 if (!*file->old.name &&
4234 (file->status == 'R' || file->status == 'C')) {
4235 sepsize = sep - buf + 1;
4236 string_ncopy(file->old.name, buf, sepsize);
4237 bufsize -= sepsize;
4238 memmove(buf, sep + 1, bufsize);
4240 sep = memchr(buf, 0, bufsize);
4241 if (!sep)
4242 break;
4243 sepsize = sep - buf + 1;
4246 /* git-ls-files just delivers a NUL separated
4247 * list of file names similar to the second half
4248 * of the git-diff-* output. */
4249 string_ncopy(file->new.name, buf, sepsize);
4250 if (!*file->old.name)
4251 string_copy(file->old.name, file->new.name);
4252 bufsize -= sepsize;
4253 memmove(buf, sep + 1, bufsize);
4254 file = NULL;
4258 if (ferror(pipe)) {
4259 error_out:
4260 pclose(pipe);
4261 return FALSE;
4264 if (!view->line[view->lines - 1].data)
4265 add_line_data(view, NULL, LINE_STAT_NONE);
4267 pclose(pipe);
4268 return TRUE;
4271 /* Don't show unmerged entries in the staged section. */
4272 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4273 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4274 #define STATUS_LIST_OTHER_CMD \
4275 "git ls-files -z --others --exclude-standard"
4276 #define STATUS_LIST_NO_HEAD_CMD \
4277 "git ls-files -z --cached --exclude-standard"
4279 #define STATUS_DIFF_INDEX_SHOW_CMD \
4280 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4282 #define STATUS_DIFF_FILES_SHOW_CMD \
4283 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4285 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4286 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4288 /* First parse staged info using git-diff-index(1), then parse unstaged
4289 * info using git-diff-files(1), and finally untracked files using
4290 * git-ls-files(1). */
4291 static bool
4292 status_open(struct view *view)
4294 unsigned long prev_lineno = view->lineno;
4296 reset_view(view);
4298 if (!realloc_lines(view, view->line_size + 7))
4299 return FALSE;
4301 add_line_data(view, NULL, LINE_STAT_HEAD);
4302 if (is_initial_commit())
4303 string_copy(status_onbranch, "Initial commit");
4304 else if (!*opt_head)
4305 string_copy(status_onbranch, "Not currently on any branch");
4306 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4307 return FALSE;
4309 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4311 if (is_initial_commit()) {
4312 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4313 return FALSE;
4314 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4315 return FALSE;
4318 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4319 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4320 return FALSE;
4322 /* If all went well restore the previous line number to stay in
4323 * the context or select a line with something that can be
4324 * updated. */
4325 if (prev_lineno >= view->lines)
4326 prev_lineno = view->lines - 1;
4327 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4328 prev_lineno++;
4329 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4330 prev_lineno--;
4332 /* If the above fails, always skip the "On branch" line. */
4333 if (prev_lineno < view->lines)
4334 view->lineno = prev_lineno;
4335 else
4336 view->lineno = 1;
4338 if (view->lineno < view->offset)
4339 view->offset = view->lineno;
4340 else if (view->offset + view->height <= view->lineno)
4341 view->offset = view->lineno - view->height + 1;
4343 return TRUE;
4346 static bool
4347 status_draw(struct view *view, struct line *line, unsigned int lineno)
4349 struct status *status = line->data;
4350 enum line_type type;
4351 const char *text;
4353 if (!status) {
4354 switch (line->type) {
4355 case LINE_STAT_STAGED:
4356 type = LINE_STAT_SECTION;
4357 text = "Changes to be committed:";
4358 break;
4360 case LINE_STAT_UNSTAGED:
4361 type = LINE_STAT_SECTION;
4362 text = "Changed but not updated:";
4363 break;
4365 case LINE_STAT_UNTRACKED:
4366 type = LINE_STAT_SECTION;
4367 text = "Untracked files:";
4368 break;
4370 case LINE_STAT_NONE:
4371 type = LINE_DEFAULT;
4372 text = " (no files)";
4373 break;
4375 case LINE_STAT_HEAD:
4376 type = LINE_STAT_HEAD;
4377 text = status_onbranch;
4378 break;
4380 default:
4381 return FALSE;
4383 } else {
4384 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4386 buf[0] = status->status;
4387 if (draw_text(view, line->type, buf, TRUE))
4388 return TRUE;
4389 type = LINE_DEFAULT;
4390 text = status->new.name;
4393 draw_text(view, type, text, TRUE);
4394 return TRUE;
4397 static enum request
4398 status_enter(struct view *view, struct line *line)
4400 struct status *status = line->data;
4401 char oldpath[SIZEOF_STR] = "";
4402 char newpath[SIZEOF_STR] = "";
4403 const char *info;
4404 size_t cmdsize = 0;
4405 enum open_flags split;
4407 if (line->type == LINE_STAT_NONE ||
4408 (!status && line[1].type == LINE_STAT_NONE)) {
4409 report("No file to diff");
4410 return REQ_NONE;
4413 if (status) {
4414 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4415 return REQ_QUIT;
4416 /* Diffs for unmerged entries are empty when pasing the
4417 * new path, so leave it empty. */
4418 if (status->status != 'U' &&
4419 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4420 return REQ_QUIT;
4423 if (opt_cdup[0] &&
4424 line->type != LINE_STAT_UNTRACKED &&
4425 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4426 return REQ_QUIT;
4428 switch (line->type) {
4429 case LINE_STAT_STAGED:
4430 if (is_initial_commit()) {
4431 if (!string_format_from(opt_cmd, &cmdsize,
4432 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4433 newpath))
4434 return REQ_QUIT;
4435 } else {
4436 if (!string_format_from(opt_cmd, &cmdsize,
4437 STATUS_DIFF_INDEX_SHOW_CMD,
4438 oldpath, newpath))
4439 return REQ_QUIT;
4442 if (status)
4443 info = "Staged changes to %s";
4444 else
4445 info = "Staged changes";
4446 break;
4448 case LINE_STAT_UNSTAGED:
4449 if (!string_format_from(opt_cmd, &cmdsize,
4450 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4451 return REQ_QUIT;
4452 if (status)
4453 info = "Unstaged changes to %s";
4454 else
4455 info = "Unstaged changes";
4456 break;
4458 case LINE_STAT_UNTRACKED:
4459 if (opt_pipe)
4460 return REQ_QUIT;
4462 if (!status) {
4463 report("No file to show");
4464 return REQ_NONE;
4467 if (!suffixcmp(status->new.name, -1, "/")) {
4468 report("Cannot display a directory");
4469 return REQ_NONE;
4472 opt_pipe = fopen(status->new.name, "r");
4473 info = "Untracked file %s";
4474 break;
4476 case LINE_STAT_HEAD:
4477 return REQ_NONE;
4479 default:
4480 die("line type %d not handled in switch", line->type);
4483 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4484 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4485 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4486 if (status) {
4487 stage_status = *status;
4488 } else {
4489 memset(&stage_status, 0, sizeof(stage_status));
4492 stage_line_type = line->type;
4493 stage_chunks = 0;
4494 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4497 return REQ_NONE;
4500 static bool
4501 status_exists(struct status *status, enum line_type type)
4503 struct view *view = VIEW(REQ_VIEW_STATUS);
4504 struct line *line;
4506 for (line = view->line; line < view->line + view->lines; line++) {
4507 struct status *pos = line->data;
4509 if (line->type == type && pos &&
4510 !strcmp(status->new.name, pos->new.name))
4511 return TRUE;
4514 return FALSE;
4518 static FILE *
4519 status_update_prepare(enum line_type type)
4521 char cmd[SIZEOF_STR];
4522 size_t cmdsize = 0;
4524 if (opt_cdup[0] &&
4525 type != LINE_STAT_UNTRACKED &&
4526 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4527 return NULL;
4529 switch (type) {
4530 case LINE_STAT_STAGED:
4531 string_add(cmd, cmdsize, "git update-index -z --index-info");
4532 break;
4534 case LINE_STAT_UNSTAGED:
4535 case LINE_STAT_UNTRACKED:
4536 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4537 break;
4539 default:
4540 die("line type %d not handled in switch", type);
4543 return popen(cmd, "w");
4546 static bool
4547 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4549 char buf[SIZEOF_STR];
4550 size_t bufsize = 0;
4551 size_t written = 0;
4553 switch (type) {
4554 case LINE_STAT_STAGED:
4555 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4556 status->old.mode,
4557 status->old.rev,
4558 status->old.name, 0))
4559 return FALSE;
4560 break;
4562 case LINE_STAT_UNSTAGED:
4563 case LINE_STAT_UNTRACKED:
4564 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4565 return FALSE;
4566 break;
4568 default:
4569 die("line type %d not handled in switch", type);
4572 while (!ferror(pipe) && written < bufsize) {
4573 written += fwrite(buf + written, 1, bufsize - written, pipe);
4576 return written == bufsize;
4579 static bool
4580 status_update_file(struct status *status, enum line_type type)
4582 FILE *pipe = status_update_prepare(type);
4583 bool result;
4585 if (!pipe)
4586 return FALSE;
4588 result = status_update_write(pipe, status, type);
4589 pclose(pipe);
4590 return result;
4593 static bool
4594 status_update_files(struct view *view, struct line *line)
4596 FILE *pipe = status_update_prepare(line->type);
4597 bool result = TRUE;
4598 struct line *pos = view->line + view->lines;
4599 int files = 0;
4600 int file, done;
4602 if (!pipe)
4603 return FALSE;
4605 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4606 files++;
4608 for (file = 0, done = 0; result && file < files; line++, file++) {
4609 int almost_done = file * 100 / files;
4611 if (almost_done > done) {
4612 done = almost_done;
4613 string_format(view->ref, "updating file %u of %u (%d%% done)",
4614 file, files, done);
4615 update_view_title(view);
4617 result = status_update_write(pipe, line->data, line->type);
4620 pclose(pipe);
4621 return result;
4624 static bool
4625 status_update(struct view *view)
4627 struct line *line = &view->line[view->lineno];
4629 assert(view->lines);
4631 if (!line->data) {
4632 /* This should work even for the "On branch" line. */
4633 if (line < view->line + view->lines && !line[1].data) {
4634 report("Nothing to update");
4635 return FALSE;
4638 if (!status_update_files(view, line + 1)) {
4639 report("Failed to update file status");
4640 return FALSE;
4643 } else if (!status_update_file(line->data, line->type)) {
4644 report("Failed to update file status");
4645 return FALSE;
4648 return TRUE;
4651 static bool
4652 status_revert(struct status *status, enum line_type type, bool has_none)
4654 if (!status || type != LINE_STAT_UNSTAGED) {
4655 if (type == LINE_STAT_STAGED) {
4656 report("Cannot revert changes to staged files");
4657 } else if (type == LINE_STAT_UNTRACKED) {
4658 report("Cannot revert changes to untracked files");
4659 } else if (has_none) {
4660 report("Nothing to revert");
4661 } else {
4662 report("Cannot revert changes to multiple files");
4664 return FALSE;
4666 } else {
4667 const char *checkout_argv[] = {
4668 "git", "checkout", "--", status->old.name, NULL
4671 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4672 return FALSE;
4673 return run_io_fg(checkout_argv, opt_cdup);
4677 static enum request
4678 status_request(struct view *view, enum request request, struct line *line)
4680 struct status *status = line->data;
4682 switch (request) {
4683 case REQ_STATUS_UPDATE:
4684 if (!status_update(view))
4685 return REQ_NONE;
4686 break;
4688 case REQ_STATUS_REVERT:
4689 if (!status_revert(status, line->type, status_has_none(view, line)))
4690 return REQ_NONE;
4691 break;
4693 case REQ_STATUS_MERGE:
4694 if (!status || status->status != 'U') {
4695 report("Merging only possible for files with unmerged status ('U').");
4696 return REQ_NONE;
4698 open_mergetool(status->new.name);
4699 break;
4701 case REQ_EDIT:
4702 if (!status)
4703 return request;
4704 if (status->status == 'D') {
4705 report("File has been deleted.");
4706 return REQ_NONE;
4709 open_editor(status->status != '?', status->new.name);
4710 break;
4712 case REQ_VIEW_BLAME:
4713 if (status) {
4714 string_copy(opt_file, status->new.name);
4715 opt_ref[0] = 0;
4717 return request;
4719 case REQ_ENTER:
4720 /* After returning the status view has been split to
4721 * show the stage view. No further reloading is
4722 * necessary. */
4723 status_enter(view, line);
4724 return REQ_NONE;
4726 case REQ_REFRESH:
4727 /* Simply reload the view. */
4728 break;
4730 default:
4731 return request;
4734 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4736 return REQ_NONE;
4739 static void
4740 status_select(struct view *view, struct line *line)
4742 struct status *status = line->data;
4743 char file[SIZEOF_STR] = "all files";
4744 const char *text;
4745 const char *key;
4747 if (status && !string_format(file, "'%s'", status->new.name))
4748 return;
4750 if (!status && line[1].type == LINE_STAT_NONE)
4751 line++;
4753 switch (line->type) {
4754 case LINE_STAT_STAGED:
4755 text = "Press %s to unstage %s for commit";
4756 break;
4758 case LINE_STAT_UNSTAGED:
4759 text = "Press %s to stage %s for commit";
4760 break;
4762 case LINE_STAT_UNTRACKED:
4763 text = "Press %s to stage %s for addition";
4764 break;
4766 case LINE_STAT_HEAD:
4767 case LINE_STAT_NONE:
4768 text = "Nothing to update";
4769 break;
4771 default:
4772 die("line type %d not handled in switch", line->type);
4775 if (status && status->status == 'U') {
4776 text = "Press %s to resolve conflict in %s";
4777 key = get_key(REQ_STATUS_MERGE);
4779 } else {
4780 key = get_key(REQ_STATUS_UPDATE);
4783 string_format(view->ref, text, key, file);
4786 static bool
4787 status_grep(struct view *view, struct line *line)
4789 struct status *status = line->data;
4790 enum { S_STATUS, S_NAME, S_END } state;
4791 char buf[2] = "?";
4792 regmatch_t pmatch;
4794 if (!status)
4795 return FALSE;
4797 for (state = S_STATUS; state < S_END; state++) {
4798 const char *text;
4800 switch (state) {
4801 case S_NAME: text = status->new.name; break;
4802 case S_STATUS:
4803 buf[0] = status->status;
4804 text = buf;
4805 break;
4807 default:
4808 return FALSE;
4811 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4812 return TRUE;
4815 return FALSE;
4818 static struct view_ops status_ops = {
4819 "file",
4820 NULL,
4821 status_open,
4822 NULL,
4823 status_draw,
4824 status_request,
4825 status_grep,
4826 status_select,
4830 static bool
4831 stage_diff_line(FILE *pipe, struct line *line)
4833 const char *buf = line->data;
4834 size_t bufsize = strlen(buf);
4835 size_t written = 0;
4837 while (!ferror(pipe) && written < bufsize) {
4838 written += fwrite(buf + written, 1, bufsize - written, pipe);
4841 fputc('\n', pipe);
4843 return written == bufsize;
4846 static bool
4847 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4849 while (line < end) {
4850 if (!stage_diff_line(pipe, line++))
4851 return FALSE;
4852 if (line->type == LINE_DIFF_CHUNK ||
4853 line->type == LINE_DIFF_HEADER)
4854 break;
4857 return TRUE;
4860 static struct line *
4861 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4863 for (; view->line < line; line--)
4864 if (line->type == type)
4865 return line;
4867 return NULL;
4870 static bool
4871 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4873 char cmd[SIZEOF_STR];
4874 size_t cmdsize = 0;
4875 struct line *diff_hdr;
4876 FILE *pipe;
4878 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4879 if (!diff_hdr)
4880 return FALSE;
4882 if (opt_cdup[0] &&
4883 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4884 return FALSE;
4886 if (!string_format_from(cmd, &cmdsize,
4887 "git apply --whitespace=nowarn %s %s - && "
4888 "git update-index -q --unmerged --refresh 2>/dev/null",
4889 revert ? "" : "--cached",
4890 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4891 return FALSE;
4893 pipe = popen(cmd, "w");
4894 if (!pipe)
4895 return FALSE;
4897 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4898 !stage_diff_write(pipe, chunk, view->line + view->lines))
4899 chunk = NULL;
4901 pclose(pipe);
4903 return chunk ? TRUE : FALSE;
4906 static bool
4907 stage_update(struct view *view, struct line *line)
4909 struct line *chunk = NULL;
4911 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4912 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4914 if (chunk) {
4915 if (!stage_apply_chunk(view, chunk, FALSE)) {
4916 report("Failed to apply chunk");
4917 return FALSE;
4920 } else if (!stage_status.status) {
4921 view = VIEW(REQ_VIEW_STATUS);
4923 for (line = view->line; line < view->line + view->lines; line++)
4924 if (line->type == stage_line_type)
4925 break;
4927 if (!status_update_files(view, line + 1)) {
4928 report("Failed to update files");
4929 return FALSE;
4932 } else if (!status_update_file(&stage_status, stage_line_type)) {
4933 report("Failed to update file");
4934 return FALSE;
4937 return TRUE;
4940 static bool
4941 stage_revert(struct view *view, struct line *line)
4943 struct line *chunk = NULL;
4945 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4946 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4948 if (chunk) {
4949 if (!prompt_yesno("Are you sure you want to revert changes?"))
4950 return FALSE;
4952 if (!stage_apply_chunk(view, chunk, TRUE)) {
4953 report("Failed to revert chunk");
4954 return FALSE;
4956 return TRUE;
4958 } else {
4959 return status_revert(stage_status.status ? &stage_status : NULL,
4960 stage_line_type, FALSE);
4965 static void
4966 stage_next(struct view *view, struct line *line)
4968 int i;
4970 if (!stage_chunks) {
4971 static size_t alloc = 0;
4972 int *tmp;
4974 for (line = view->line; line < view->line + view->lines; line++) {
4975 if (line->type != LINE_DIFF_CHUNK)
4976 continue;
4978 tmp = realloc_items(stage_chunk, &alloc,
4979 stage_chunks, sizeof(*tmp));
4980 if (!tmp) {
4981 report("Allocation failure");
4982 return;
4985 stage_chunk = tmp;
4986 stage_chunk[stage_chunks++] = line - view->line;
4990 for (i = 0; i < stage_chunks; i++) {
4991 if (stage_chunk[i] > view->lineno) {
4992 do_scroll_view(view, stage_chunk[i] - view->lineno);
4993 report("Chunk %d of %d", i + 1, stage_chunks);
4994 return;
4998 report("No next chunk found");
5001 static enum request
5002 stage_request(struct view *view, enum request request, struct line *line)
5004 switch (request) {
5005 case REQ_STATUS_UPDATE:
5006 if (!stage_update(view, line))
5007 return REQ_NONE;
5008 break;
5010 case REQ_STATUS_REVERT:
5011 if (!stage_revert(view, line))
5012 return REQ_NONE;
5013 break;
5015 case REQ_STAGE_NEXT:
5016 if (stage_line_type == LINE_STAT_UNTRACKED) {
5017 report("File is untracked; press %s to add",
5018 get_key(REQ_STATUS_UPDATE));
5019 return REQ_NONE;
5021 stage_next(view, line);
5022 return REQ_NONE;
5024 case REQ_EDIT:
5025 if (!stage_status.new.name[0])
5026 return request;
5027 if (stage_status.status == 'D') {
5028 report("File has been deleted.");
5029 return REQ_NONE;
5032 open_editor(stage_status.status != '?', stage_status.new.name);
5033 break;
5035 case REQ_REFRESH:
5036 /* Reload everything ... */
5037 break;
5039 case REQ_VIEW_BLAME:
5040 if (stage_status.new.name[0]) {
5041 string_copy(opt_file, stage_status.new.name);
5042 opt_ref[0] = 0;
5044 return request;
5046 case REQ_ENTER:
5047 return pager_request(view, request, line);
5049 default:
5050 return request;
5053 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5055 /* Check whether the staged entry still exists, and close the
5056 * stage view if it doesn't. */
5057 if (!status_exists(&stage_status, stage_line_type))
5058 return REQ_VIEW_CLOSE;
5060 if (stage_line_type == LINE_STAT_UNTRACKED) {
5061 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5062 report("Cannot display a directory");
5063 return REQ_NONE;
5066 opt_pipe = fopen(stage_status.new.name, "r");
5068 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5070 return REQ_NONE;
5073 static struct view_ops stage_ops = {
5074 "line",
5075 NULL,
5076 NULL,
5077 pager_read,
5078 pager_draw,
5079 stage_request,
5080 pager_grep,
5081 pager_select,
5086 * Revision graph
5089 struct commit {
5090 char id[SIZEOF_REV]; /* SHA1 ID. */
5091 char title[128]; /* First line of the commit message. */
5092 char author[75]; /* Author of the commit. */
5093 struct tm time; /* Date from the author ident. */
5094 struct ref **refs; /* Repository references. */
5095 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5096 size_t graph_size; /* The width of the graph array. */
5097 bool has_parents; /* Rewritten --parents seen. */
5100 /* Size of rev graph with no "padding" columns */
5101 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5103 struct rev_graph {
5104 struct rev_graph *prev, *next, *parents;
5105 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5106 size_t size;
5107 struct commit *commit;
5108 size_t pos;
5109 unsigned int boundary:1;
5112 /* Parents of the commit being visualized. */
5113 static struct rev_graph graph_parents[4];
5115 /* The current stack of revisions on the graph. */
5116 static struct rev_graph graph_stacks[4] = {
5117 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5118 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5119 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5120 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5123 static inline bool
5124 graph_parent_is_merge(struct rev_graph *graph)
5126 return graph->parents->size > 1;
5129 static inline void
5130 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5132 struct commit *commit = graph->commit;
5134 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5135 commit->graph[commit->graph_size++] = symbol;
5138 static void
5139 clear_rev_graph(struct rev_graph *graph)
5141 graph->boundary = 0;
5142 graph->size = graph->pos = 0;
5143 graph->commit = NULL;
5144 memset(graph->parents, 0, sizeof(*graph->parents));
5147 static void
5148 done_rev_graph(struct rev_graph *graph)
5150 if (graph_parent_is_merge(graph) &&
5151 graph->pos < graph->size - 1 &&
5152 graph->next->size == graph->size + graph->parents->size - 1) {
5153 size_t i = graph->pos + graph->parents->size - 1;
5155 graph->commit->graph_size = i * 2;
5156 while (i < graph->next->size - 1) {
5157 append_to_rev_graph(graph, ' ');
5158 append_to_rev_graph(graph, '\\');
5159 i++;
5163 clear_rev_graph(graph);
5166 static void
5167 push_rev_graph(struct rev_graph *graph, const char *parent)
5169 int i;
5171 /* "Collapse" duplicate parents lines.
5173 * FIXME: This needs to also update update the drawn graph but
5174 * for now it just serves as a method for pruning graph lines. */
5175 for (i = 0; i < graph->size; i++)
5176 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5177 return;
5179 if (graph->size < SIZEOF_REVITEMS) {
5180 string_copy_rev(graph->rev[graph->size++], parent);
5184 static chtype
5185 get_rev_graph_symbol(struct rev_graph *graph)
5187 chtype symbol;
5189 if (graph->boundary)
5190 symbol = REVGRAPH_BOUND;
5191 else if (graph->parents->size == 0)
5192 symbol = REVGRAPH_INIT;
5193 else if (graph_parent_is_merge(graph))
5194 symbol = REVGRAPH_MERGE;
5195 else if (graph->pos >= graph->size)
5196 symbol = REVGRAPH_BRANCH;
5197 else
5198 symbol = REVGRAPH_COMMIT;
5200 return symbol;
5203 static void
5204 draw_rev_graph(struct rev_graph *graph)
5206 struct rev_filler {
5207 chtype separator, line;
5209 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5210 static struct rev_filler fillers[] = {
5211 { ' ', '|' },
5212 { '`', '.' },
5213 { '\'', ' ' },
5214 { '/', ' ' },
5216 chtype symbol = get_rev_graph_symbol(graph);
5217 struct rev_filler *filler;
5218 size_t i;
5220 if (opt_line_graphics)
5221 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5223 filler = &fillers[DEFAULT];
5225 for (i = 0; i < graph->pos; i++) {
5226 append_to_rev_graph(graph, filler->line);
5227 if (graph_parent_is_merge(graph->prev) &&
5228 graph->prev->pos == i)
5229 filler = &fillers[RSHARP];
5231 append_to_rev_graph(graph, filler->separator);
5234 /* Place the symbol for this revision. */
5235 append_to_rev_graph(graph, symbol);
5237 if (graph->prev->size > graph->size)
5238 filler = &fillers[RDIAG];
5239 else
5240 filler = &fillers[DEFAULT];
5242 i++;
5244 for (; i < graph->size; i++) {
5245 append_to_rev_graph(graph, filler->separator);
5246 append_to_rev_graph(graph, filler->line);
5247 if (graph_parent_is_merge(graph->prev) &&
5248 i < graph->prev->pos + graph->parents->size)
5249 filler = &fillers[RSHARP];
5250 if (graph->prev->size > graph->size)
5251 filler = &fillers[LDIAG];
5254 if (graph->prev->size > graph->size) {
5255 append_to_rev_graph(graph, filler->separator);
5256 if (filler->line != ' ')
5257 append_to_rev_graph(graph, filler->line);
5261 /* Prepare the next rev graph */
5262 static void
5263 prepare_rev_graph(struct rev_graph *graph)
5265 size_t i;
5267 /* First, traverse all lines of revisions up to the active one. */
5268 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5269 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5270 break;
5272 push_rev_graph(graph->next, graph->rev[graph->pos]);
5275 /* Interleave the new revision parent(s). */
5276 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5277 push_rev_graph(graph->next, graph->parents->rev[i]);
5279 /* Lastly, put any remaining revisions. */
5280 for (i = graph->pos + 1; i < graph->size; i++)
5281 push_rev_graph(graph->next, graph->rev[i]);
5284 static void
5285 update_rev_graph(struct rev_graph *graph)
5287 /* If this is the finalizing update ... */
5288 if (graph->commit)
5289 prepare_rev_graph(graph);
5291 /* Graph visualization needs a one rev look-ahead,
5292 * so the first update doesn't visualize anything. */
5293 if (!graph->prev->commit)
5294 return;
5296 draw_rev_graph(graph->prev);
5297 done_rev_graph(graph->prev->prev);
5302 * Main view backend
5305 static const char *main_argv[SIZEOF_ARG] = {
5306 "git", "log", "--no-color", "--pretty=raw", "--parents",
5307 "--topo-order", "%(head)", NULL
5310 static bool
5311 main_draw(struct view *view, struct line *line, unsigned int lineno)
5313 struct commit *commit = line->data;
5315 if (!*commit->author)
5316 return FALSE;
5318 if (opt_date && draw_date(view, &commit->time))
5319 return TRUE;
5321 if (opt_author &&
5322 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5323 return TRUE;
5325 if (opt_rev_graph && commit->graph_size &&
5326 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5327 return TRUE;
5329 if (opt_show_refs && commit->refs) {
5330 size_t i = 0;
5332 do {
5333 enum line_type type;
5335 if (commit->refs[i]->head)
5336 type = LINE_MAIN_HEAD;
5337 else if (commit->refs[i]->ltag)
5338 type = LINE_MAIN_LOCAL_TAG;
5339 else if (commit->refs[i]->tag)
5340 type = LINE_MAIN_TAG;
5341 else if (commit->refs[i]->tracked)
5342 type = LINE_MAIN_TRACKED;
5343 else if (commit->refs[i]->remote)
5344 type = LINE_MAIN_REMOTE;
5345 else
5346 type = LINE_MAIN_REF;
5348 if (draw_text(view, type, "[", TRUE) ||
5349 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5350 draw_text(view, type, "]", TRUE))
5351 return TRUE;
5353 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5354 return TRUE;
5355 } while (commit->refs[i++]->next);
5358 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5359 return TRUE;
5362 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5363 static bool
5364 main_read(struct view *view, char *line)
5366 static struct rev_graph *graph = graph_stacks;
5367 enum line_type type;
5368 struct commit *commit;
5370 if (!line) {
5371 int i;
5373 if (!view->lines && !view->parent)
5374 die("No revisions match the given arguments.");
5375 if (view->lines > 0) {
5376 commit = view->line[view->lines - 1].data;
5377 if (!*commit->author) {
5378 view->lines--;
5379 free(commit);
5380 graph->commit = NULL;
5383 update_rev_graph(graph);
5385 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5386 clear_rev_graph(&graph_stacks[i]);
5387 return TRUE;
5390 type = get_line_type(line);
5391 if (type == LINE_COMMIT) {
5392 commit = calloc(1, sizeof(struct commit));
5393 if (!commit)
5394 return FALSE;
5396 line += STRING_SIZE("commit ");
5397 if (*line == '-') {
5398 graph->boundary = 1;
5399 line++;
5402 string_copy_rev(commit->id, line);
5403 commit->refs = get_refs(commit->id);
5404 graph->commit = commit;
5405 add_line_data(view, commit, LINE_MAIN_COMMIT);
5407 while ((line = strchr(line, ' '))) {
5408 line++;
5409 push_rev_graph(graph->parents, line);
5410 commit->has_parents = TRUE;
5412 return TRUE;
5415 if (!view->lines)
5416 return TRUE;
5417 commit = view->line[view->lines - 1].data;
5419 switch (type) {
5420 case LINE_PARENT:
5421 if (commit->has_parents)
5422 break;
5423 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5424 break;
5426 case LINE_AUTHOR:
5428 /* Parse author lines where the name may be empty:
5429 * author <email@address.tld> 1138474660 +0100
5431 char *ident = line + STRING_SIZE("author ");
5432 char *nameend = strchr(ident, '<');
5433 char *emailend = strchr(ident, '>');
5435 if (!nameend || !emailend)
5436 break;
5438 update_rev_graph(graph);
5439 graph = graph->next;
5441 *nameend = *emailend = 0;
5442 ident = chomp_string(ident);
5443 if (!*ident) {
5444 ident = chomp_string(nameend + 1);
5445 if (!*ident)
5446 ident = "Unknown";
5449 string_ncopy(commit->author, ident, strlen(ident));
5451 /* Parse epoch and timezone */
5452 if (emailend[1] == ' ') {
5453 char *secs = emailend + 2;
5454 char *zone = strchr(secs, ' ');
5455 time_t time = (time_t) atol(secs);
5457 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5458 long tz;
5460 zone++;
5461 tz = ('0' - zone[1]) * 60 * 60 * 10;
5462 tz += ('0' - zone[2]) * 60 * 60;
5463 tz += ('0' - zone[3]) * 60;
5464 tz += ('0' - zone[4]) * 60;
5466 if (zone[0] == '-')
5467 tz = -tz;
5469 time -= tz;
5472 gmtime_r(&time, &commit->time);
5474 break;
5476 default:
5477 /* Fill in the commit title if it has not already been set. */
5478 if (commit->title[0])
5479 break;
5481 /* Require titles to start with a non-space character at the
5482 * offset used by git log. */
5483 if (strncmp(line, " ", 4))
5484 break;
5485 line += 4;
5486 /* Well, if the title starts with a whitespace character,
5487 * try to be forgiving. Otherwise we end up with no title. */
5488 while (isspace(*line))
5489 line++;
5490 if (*line == '\0')
5491 break;
5492 /* FIXME: More graceful handling of titles; append "..." to
5493 * shortened titles, etc. */
5495 string_ncopy(commit->title, line, strlen(line));
5498 return TRUE;
5501 static enum request
5502 main_request(struct view *view, enum request request, struct line *line)
5504 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5506 switch (request) {
5507 case REQ_ENTER:
5508 open_view(view, REQ_VIEW_DIFF, flags);
5509 break;
5510 case REQ_REFRESH:
5511 load_refs();
5512 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5513 break;
5514 default:
5515 return request;
5518 return REQ_NONE;
5521 static bool
5522 grep_refs(struct ref **refs, regex_t *regex)
5524 regmatch_t pmatch;
5525 size_t i = 0;
5527 if (!refs)
5528 return FALSE;
5529 do {
5530 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5531 return TRUE;
5532 } while (refs[i++]->next);
5534 return FALSE;
5537 static bool
5538 main_grep(struct view *view, struct line *line)
5540 struct commit *commit = line->data;
5541 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5542 char buf[DATE_COLS + 1];
5543 regmatch_t pmatch;
5545 for (state = S_TITLE; state < S_END; state++) {
5546 char *text;
5548 switch (state) {
5549 case S_TITLE: text = commit->title; break;
5550 case S_AUTHOR:
5551 if (!opt_author)
5552 continue;
5553 text = commit->author;
5554 break;
5555 case S_DATE:
5556 if (!opt_date)
5557 continue;
5558 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5559 continue;
5560 text = buf;
5561 break;
5562 case S_REFS:
5563 if (!opt_show_refs)
5564 continue;
5565 if (grep_refs(commit->refs, view->regex) == TRUE)
5566 return TRUE;
5567 continue;
5568 default:
5569 return FALSE;
5572 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5573 return TRUE;
5576 return FALSE;
5579 static void
5580 main_select(struct view *view, struct line *line)
5582 struct commit *commit = line->data;
5584 string_copy_rev(view->ref, commit->id);
5585 string_copy_rev(ref_commit, view->ref);
5588 static struct view_ops main_ops = {
5589 "commit",
5590 main_argv,
5591 NULL,
5592 main_read,
5593 main_draw,
5594 main_request,
5595 main_grep,
5596 main_select,
5601 * Unicode / UTF-8 handling
5603 * NOTE: Much of the following code for dealing with unicode is derived from
5604 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5605 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5608 /* I've (over)annotated a lot of code snippets because I am not entirely
5609 * confident that the approach taken by this small UTF-8 interface is correct.
5610 * --jonas */
5612 static inline int
5613 unicode_width(unsigned long c)
5615 if (c >= 0x1100 &&
5616 (c <= 0x115f /* Hangul Jamo */
5617 || c == 0x2329
5618 || c == 0x232a
5619 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5620 /* CJK ... Yi */
5621 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5622 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5623 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5624 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5625 || (c >= 0xffe0 && c <= 0xffe6)
5626 || (c >= 0x20000 && c <= 0x2fffd)
5627 || (c >= 0x30000 && c <= 0x3fffd)))
5628 return 2;
5630 if (c == '\t')
5631 return opt_tab_size;
5633 return 1;
5636 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5637 * Illegal bytes are set one. */
5638 static const unsigned char utf8_bytes[256] = {
5639 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,
5640 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,
5641 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,
5642 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,
5643 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,
5644 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,
5645 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,
5646 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,
5649 /* Decode UTF-8 multi-byte representation into a unicode character. */
5650 static inline unsigned long
5651 utf8_to_unicode(const char *string, size_t length)
5653 unsigned long unicode;
5655 switch (length) {
5656 case 1:
5657 unicode = string[0];
5658 break;
5659 case 2:
5660 unicode = (string[0] & 0x1f) << 6;
5661 unicode += (string[1] & 0x3f);
5662 break;
5663 case 3:
5664 unicode = (string[0] & 0x0f) << 12;
5665 unicode += ((string[1] & 0x3f) << 6);
5666 unicode += (string[2] & 0x3f);
5667 break;
5668 case 4:
5669 unicode = (string[0] & 0x0f) << 18;
5670 unicode += ((string[1] & 0x3f) << 12);
5671 unicode += ((string[2] & 0x3f) << 6);
5672 unicode += (string[3] & 0x3f);
5673 break;
5674 case 5:
5675 unicode = (string[0] & 0x0f) << 24;
5676 unicode += ((string[1] & 0x3f) << 18);
5677 unicode += ((string[2] & 0x3f) << 12);
5678 unicode += ((string[3] & 0x3f) << 6);
5679 unicode += (string[4] & 0x3f);
5680 break;
5681 case 6:
5682 unicode = (string[0] & 0x01) << 30;
5683 unicode += ((string[1] & 0x3f) << 24);
5684 unicode += ((string[2] & 0x3f) << 18);
5685 unicode += ((string[3] & 0x3f) << 12);
5686 unicode += ((string[4] & 0x3f) << 6);
5687 unicode += (string[5] & 0x3f);
5688 break;
5689 default:
5690 die("Invalid unicode length");
5693 /* Invalid characters could return the special 0xfffd value but NUL
5694 * should be just as good. */
5695 return unicode > 0xffff ? 0 : unicode;
5698 /* Calculates how much of string can be shown within the given maximum width
5699 * and sets trimmed parameter to non-zero value if all of string could not be
5700 * shown. If the reserve flag is TRUE, it will reserve at least one
5701 * trailing character, which can be useful when drawing a delimiter.
5703 * Returns the number of bytes to output from string to satisfy max_width. */
5704 static size_t
5705 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5707 const char *start = string;
5708 const char *end = strchr(string, '\0');
5709 unsigned char last_bytes = 0;
5710 size_t last_ucwidth = 0;
5712 *width = 0;
5713 *trimmed = 0;
5715 while (string < end) {
5716 int c = *(unsigned char *) string;
5717 unsigned char bytes = utf8_bytes[c];
5718 size_t ucwidth;
5719 unsigned long unicode;
5721 if (string + bytes > end)
5722 break;
5724 /* Change representation to figure out whether
5725 * it is a single- or double-width character. */
5727 unicode = utf8_to_unicode(string, bytes);
5728 /* FIXME: Graceful handling of invalid unicode character. */
5729 if (!unicode)
5730 break;
5732 ucwidth = unicode_width(unicode);
5733 *width += ucwidth;
5734 if (*width > max_width) {
5735 *trimmed = 1;
5736 *width -= ucwidth;
5737 if (reserve && *width == max_width) {
5738 string -= last_bytes;
5739 *width -= last_ucwidth;
5741 break;
5744 string += bytes;
5745 last_bytes = bytes;
5746 last_ucwidth = ucwidth;
5749 return string - start;
5754 * Status management
5757 /* Whether or not the curses interface has been initialized. */
5758 static bool cursed = FALSE;
5760 /* The status window is used for polling keystrokes. */
5761 static WINDOW *status_win;
5763 static bool status_empty = TRUE;
5765 /* Update status and title window. */
5766 static void
5767 report(const char *msg, ...)
5769 struct view *view = display[current_view];
5771 if (input_mode)
5772 return;
5774 if (!view) {
5775 char buf[SIZEOF_STR];
5776 va_list args;
5778 va_start(args, msg);
5779 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5780 buf[sizeof(buf) - 1] = 0;
5781 buf[sizeof(buf) - 2] = '.';
5782 buf[sizeof(buf) - 3] = '.';
5783 buf[sizeof(buf) - 4] = '.';
5785 va_end(args);
5786 die("%s", buf);
5789 if (!status_empty || *msg) {
5790 va_list args;
5792 va_start(args, msg);
5794 wmove(status_win, 0, 0);
5795 if (*msg) {
5796 vwprintw(status_win, msg, args);
5797 status_empty = FALSE;
5798 } else {
5799 status_empty = TRUE;
5801 wclrtoeol(status_win);
5802 wrefresh(status_win);
5804 va_end(args);
5807 update_view_title(view);
5808 update_display_cursor(view);
5811 /* Controls when nodelay should be in effect when polling user input. */
5812 static void
5813 set_nonblocking_input(bool loading)
5815 static unsigned int loading_views;
5817 if ((loading == FALSE && loading_views-- == 1) ||
5818 (loading == TRUE && loading_views++ == 0))
5819 nodelay(status_win, loading);
5822 static void
5823 init_display(void)
5825 int x, y;
5827 /* Initialize the curses library */
5828 if (isatty(STDIN_FILENO)) {
5829 cursed = !!initscr();
5830 opt_tty = stdin;
5831 } else {
5832 /* Leave stdin and stdout alone when acting as a pager. */
5833 opt_tty = fopen("/dev/tty", "r+");
5834 if (!opt_tty)
5835 die("Failed to open /dev/tty");
5836 cursed = !!newterm(NULL, opt_tty, opt_tty);
5839 if (!cursed)
5840 die("Failed to initialize curses");
5842 nonl(); /* Tell curses not to do NL->CR/NL on output */
5843 cbreak(); /* Take input chars one at a time, no wait for \n */
5844 noecho(); /* Don't echo input */
5845 leaveok(stdscr, TRUE);
5847 if (has_colors())
5848 init_colors();
5850 getmaxyx(stdscr, y, x);
5851 status_win = newwin(1, 0, y - 1, 0);
5852 if (!status_win)
5853 die("Failed to create status window");
5855 /* Enable keyboard mapping */
5856 keypad(status_win, TRUE);
5857 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5859 TABSIZE = opt_tab_size;
5860 if (opt_line_graphics) {
5861 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5865 static bool
5866 prompt_yesno(const char *prompt)
5868 enum { WAIT, STOP, CANCEL } status = WAIT;
5869 bool answer = FALSE;
5871 while (status == WAIT) {
5872 struct view *view;
5873 int i, key;
5875 input_mode = TRUE;
5877 foreach_view (view, i)
5878 update_view(view);
5880 input_mode = FALSE;
5882 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5883 wclrtoeol(status_win);
5885 /* Refresh, accept single keystroke of input */
5886 key = wgetch(status_win);
5887 switch (key) {
5888 case ERR:
5889 break;
5891 case 'y':
5892 case 'Y':
5893 answer = TRUE;
5894 status = STOP;
5895 break;
5897 case KEY_ESC:
5898 case KEY_RETURN:
5899 case KEY_ENTER:
5900 case KEY_BACKSPACE:
5901 case 'n':
5902 case 'N':
5903 case '\n':
5904 default:
5905 answer = FALSE;
5906 status = CANCEL;
5910 /* Clear the status window */
5911 status_empty = FALSE;
5912 report("");
5914 return answer;
5917 static char *
5918 read_prompt(const char *prompt)
5920 enum { READING, STOP, CANCEL } status = READING;
5921 static char buf[SIZEOF_STR];
5922 int pos = 0;
5924 while (status == READING) {
5925 struct view *view;
5926 int i, key;
5928 input_mode = TRUE;
5930 foreach_view (view, i)
5931 update_view(view);
5933 input_mode = FALSE;
5935 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5936 wclrtoeol(status_win);
5938 /* Refresh, accept single keystroke of input */
5939 key = wgetch(status_win);
5940 switch (key) {
5941 case KEY_RETURN:
5942 case KEY_ENTER:
5943 case '\n':
5944 status = pos ? STOP : CANCEL;
5945 break;
5947 case KEY_BACKSPACE:
5948 if (pos > 0)
5949 pos--;
5950 else
5951 status = CANCEL;
5952 break;
5954 case KEY_ESC:
5955 status = CANCEL;
5956 break;
5958 case ERR:
5959 break;
5961 default:
5962 if (pos >= sizeof(buf)) {
5963 report("Input string too long");
5964 return NULL;
5967 if (isprint(key))
5968 buf[pos++] = (char) key;
5972 /* Clear the status window */
5973 status_empty = FALSE;
5974 report("");
5976 if (status == CANCEL)
5977 return NULL;
5979 buf[pos++] = 0;
5981 return buf;
5985 * Repository references
5988 static struct ref *refs = NULL;
5989 static size_t refs_alloc = 0;
5990 static size_t refs_size = 0;
5992 /* Id <-> ref store */
5993 static struct ref ***id_refs = NULL;
5994 static size_t id_refs_alloc = 0;
5995 static size_t id_refs_size = 0;
5997 static int
5998 compare_refs(const void *ref1_, const void *ref2_)
6000 const struct ref *ref1 = *(const struct ref **)ref1_;
6001 const struct ref *ref2 = *(const struct ref **)ref2_;
6003 if (ref1->tag != ref2->tag)
6004 return ref2->tag - ref1->tag;
6005 if (ref1->ltag != ref2->ltag)
6006 return ref2->ltag - ref2->ltag;
6007 if (ref1->head != ref2->head)
6008 return ref2->head - ref1->head;
6009 if (ref1->tracked != ref2->tracked)
6010 return ref2->tracked - ref1->tracked;
6011 if (ref1->remote != ref2->remote)
6012 return ref2->remote - ref1->remote;
6013 return strcmp(ref1->name, ref2->name);
6016 static struct ref **
6017 get_refs(const char *id)
6019 struct ref ***tmp_id_refs;
6020 struct ref **ref_list = NULL;
6021 size_t ref_list_alloc = 0;
6022 size_t ref_list_size = 0;
6023 size_t i;
6025 for (i = 0; i < id_refs_size; i++)
6026 if (!strcmp(id, id_refs[i][0]->id))
6027 return id_refs[i];
6029 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6030 sizeof(*id_refs));
6031 if (!tmp_id_refs)
6032 return NULL;
6034 id_refs = tmp_id_refs;
6036 for (i = 0; i < refs_size; i++) {
6037 struct ref **tmp;
6039 if (strcmp(id, refs[i].id))
6040 continue;
6042 tmp = realloc_items(ref_list, &ref_list_alloc,
6043 ref_list_size + 1, sizeof(*ref_list));
6044 if (!tmp) {
6045 if (ref_list)
6046 free(ref_list);
6047 return NULL;
6050 ref_list = tmp;
6051 ref_list[ref_list_size] = &refs[i];
6052 /* XXX: The properties of the commit chains ensures that we can
6053 * safely modify the shared ref. The repo references will
6054 * always be similar for the same id. */
6055 ref_list[ref_list_size]->next = 1;
6057 ref_list_size++;
6060 if (ref_list) {
6061 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6062 ref_list[ref_list_size - 1]->next = 0;
6063 id_refs[id_refs_size++] = ref_list;
6066 return ref_list;
6069 static int
6070 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6072 struct ref *ref;
6073 bool tag = FALSE;
6074 bool ltag = FALSE;
6075 bool remote = FALSE;
6076 bool tracked = FALSE;
6077 bool check_replace = FALSE;
6078 bool head = FALSE;
6080 if (!prefixcmp(name, "refs/tags/")) {
6081 if (!suffixcmp(name, namelen, "^{}")) {
6082 namelen -= 3;
6083 name[namelen] = 0;
6084 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6085 check_replace = TRUE;
6086 } else {
6087 ltag = TRUE;
6090 tag = TRUE;
6091 namelen -= STRING_SIZE("refs/tags/");
6092 name += STRING_SIZE("refs/tags/");
6094 } else if (!prefixcmp(name, "refs/remotes/")) {
6095 remote = TRUE;
6096 namelen -= STRING_SIZE("refs/remotes/");
6097 name += STRING_SIZE("refs/remotes/");
6098 tracked = !strcmp(opt_remote, name);
6100 } else if (!prefixcmp(name, "refs/heads/")) {
6101 namelen -= STRING_SIZE("refs/heads/");
6102 name += STRING_SIZE("refs/heads/");
6103 head = !strncmp(opt_head, name, namelen);
6105 } else if (!strcmp(name, "HEAD")) {
6106 string_ncopy(opt_head_rev, id, idlen);
6107 return OK;
6110 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6111 /* it's an annotated tag, replace the previous sha1 with the
6112 * resolved commit id; relies on the fact git-ls-remote lists
6113 * the commit id of an annotated tag right before the commit id
6114 * it points to. */
6115 refs[refs_size - 1].ltag = ltag;
6116 string_copy_rev(refs[refs_size - 1].id, id);
6118 return OK;
6120 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6121 if (!refs)
6122 return ERR;
6124 ref = &refs[refs_size++];
6125 ref->name = malloc(namelen + 1);
6126 if (!ref->name)
6127 return ERR;
6129 strncpy(ref->name, name, namelen);
6130 ref->name[namelen] = 0;
6131 ref->head = head;
6132 ref->tag = tag;
6133 ref->ltag = ltag;
6134 ref->remote = remote;
6135 ref->tracked = tracked;
6136 string_copy_rev(ref->id, id);
6138 return OK;
6141 static int
6142 load_refs(void)
6144 const char *cmd_env = getenv("TIG_LS_REMOTE");
6145 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6147 if (!*opt_git_dir)
6148 return OK;
6150 while (refs_size > 0)
6151 free(refs[--refs_size].name);
6152 while (id_refs_size > 0)
6153 free(id_refs[--id_refs_size]);
6155 return read_properties(popen(cmd, "r"), "\t", read_ref);
6158 static int
6159 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6161 if (!strcmp(name, "i18n.commitencoding"))
6162 string_ncopy(opt_encoding, value, valuelen);
6164 if (!strcmp(name, "core.editor"))
6165 string_ncopy(opt_editor, value, valuelen);
6167 /* branch.<head>.remote */
6168 if (*opt_head &&
6169 !strncmp(name, "branch.", 7) &&
6170 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6171 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6172 string_ncopy(opt_remote, value, valuelen);
6174 if (*opt_head && *opt_remote &&
6175 !strncmp(name, "branch.", 7) &&
6176 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6177 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6178 size_t from = strlen(opt_remote);
6180 if (!prefixcmp(value, "refs/heads/")) {
6181 value += STRING_SIZE("refs/heads/");
6182 valuelen -= STRING_SIZE("refs/heads/");
6185 if (!string_format_from(opt_remote, &from, "/%s", value))
6186 opt_remote[0] = 0;
6189 return OK;
6192 static int
6193 load_git_config(void)
6195 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6196 "=", read_repo_config_option);
6199 static int
6200 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6202 if (!opt_git_dir[0]) {
6203 string_ncopy(opt_git_dir, name, namelen);
6205 } else if (opt_is_inside_work_tree == -1) {
6206 /* This can be 3 different values depending on the
6207 * version of git being used. If git-rev-parse does not
6208 * understand --is-inside-work-tree it will simply echo
6209 * the option else either "true" or "false" is printed.
6210 * Default to true for the unknown case. */
6211 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6213 } else if (opt_cdup[0] == ' ') {
6214 string_ncopy(opt_cdup, name, namelen);
6215 } else {
6216 if (!prefixcmp(name, "refs/heads/")) {
6217 namelen -= STRING_SIZE("refs/heads/");
6218 name += STRING_SIZE("refs/heads/");
6219 string_ncopy(opt_head, name, namelen);
6223 return OK;
6226 static int
6227 load_repo_info(void)
6229 int result;
6230 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6231 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6233 /* XXX: The line outputted by "--show-cdup" can be empty so
6234 * initialize it to something invalid to make it possible to
6235 * detect whether it has been set or not. */
6236 opt_cdup[0] = ' ';
6238 result = read_properties(pipe, "=", read_repo_info);
6239 if (opt_cdup[0] == ' ')
6240 opt_cdup[0] = 0;
6242 return result;
6245 static int
6246 read_properties(FILE *pipe, const char *separators,
6247 int (*read_property)(char *, size_t, char *, size_t))
6249 char buffer[BUFSIZ];
6250 char *name;
6251 int state = OK;
6253 if (!pipe)
6254 return ERR;
6256 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6257 char *value;
6258 size_t namelen;
6259 size_t valuelen;
6261 name = chomp_string(name);
6262 namelen = strcspn(name, separators);
6264 if (name[namelen]) {
6265 name[namelen] = 0;
6266 value = chomp_string(name + namelen + 1);
6267 valuelen = strlen(value);
6269 } else {
6270 value = "";
6271 valuelen = 0;
6274 state = read_property(name, namelen, value, valuelen);
6277 if (state != ERR && ferror(pipe))
6278 state = ERR;
6280 pclose(pipe);
6282 return state;
6287 * Main
6290 static void __NORETURN
6291 quit(int sig)
6293 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6294 if (cursed)
6295 endwin();
6296 exit(0);
6299 static void __NORETURN
6300 die(const char *err, ...)
6302 va_list args;
6304 endwin();
6306 va_start(args, err);
6307 fputs("tig: ", stderr);
6308 vfprintf(stderr, err, args);
6309 fputs("\n", stderr);
6310 va_end(args);
6312 exit(1);
6315 static void
6316 warn(const char *msg, ...)
6318 va_list args;
6320 va_start(args, msg);
6321 fputs("tig warning: ", stderr);
6322 vfprintf(stderr, msg, args);
6323 fputs("\n", stderr);
6324 va_end(args);
6328 main(int argc, const char *argv[])
6330 struct view *view;
6331 enum request request;
6332 size_t i;
6334 signal(SIGINT, quit);
6336 if (setlocale(LC_ALL, "")) {
6337 char *codeset = nl_langinfo(CODESET);
6339 string_ncopy(opt_codeset, codeset, strlen(codeset));
6342 if (load_repo_info() == ERR)
6343 die("Failed to load repo info.");
6345 if (load_options() == ERR)
6346 die("Failed to load user config.");
6348 if (load_git_config() == ERR)
6349 die("Failed to load repo config.");
6351 request = parse_options(argc, argv);
6352 if (request == REQ_NONE)
6353 return 0;
6355 /* Require a git repository unless when running in pager mode. */
6356 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6357 die("Not a git repository");
6359 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6360 opt_utf8 = FALSE;
6362 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6363 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6364 if (opt_iconv == ICONV_NONE)
6365 die("Failed to initialize character set conversion");
6368 if (load_refs() == ERR)
6369 die("Failed to load refs.");
6371 foreach_view (view, i)
6372 argv_from_env(view->ops->argv, view->cmd_env);
6374 init_display();
6376 while (view_driver(display[current_view], request)) {
6377 int key;
6378 int i;
6380 foreach_view (view, i)
6381 update_view(view);
6382 view = display[current_view];
6384 /* Refresh, accept single keystroke of input */
6385 key = wgetch(status_win);
6387 /* wgetch() with nodelay() enabled returns ERR when there's no
6388 * input. */
6389 if (key == ERR) {
6390 request = REQ_NONE;
6391 continue;
6394 request = get_keybinding(view->keymap, key);
6396 /* Some low-level request handling. This keeps access to
6397 * status_win restricted. */
6398 switch (request) {
6399 case REQ_PROMPT:
6401 char *cmd = read_prompt(":");
6403 if (cmd) {
6404 struct view *next = VIEW(REQ_VIEW_PAGER);
6405 const char *argv[SIZEOF_ARG] = { "git" };
6406 int argc = 1;
6408 /* When running random commands, initially show the
6409 * command in the title. However, it maybe later be
6410 * overwritten if a commit line is selected. */
6411 string_ncopy(next->ref, cmd, strlen(cmd));
6413 if (!argv_from_string(argv, &argc, cmd)) {
6414 report("Too many arguments");
6415 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6416 report("Failed to format command");
6417 } else {
6418 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6422 request = REQ_NONE;
6423 break;
6425 case REQ_SEARCH:
6426 case REQ_SEARCH_BACK:
6428 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6429 char *search = read_prompt(prompt);
6431 if (search)
6432 string_ncopy(opt_search, search, strlen(search));
6433 else
6434 request = REQ_NONE;
6435 break;
6437 case REQ_SCREEN_RESIZE:
6439 int height, width;
6441 getmaxyx(stdscr, height, width);
6443 /* Resize the status view and let the view driver take
6444 * care of resizing the displayed views. */
6445 wresize(status_win, 1, width);
6446 mvwin(status_win, height - 1, 0);
6447 wrefresh(status_win);
6448 break;
6450 default:
6451 break;
6455 quit(0);
6457 return 0;