1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #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 void set_nonblocking_input(bool loading);
68 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
69 static bool prompt_yesno(const char *prompt);
70 static int load_refs(void);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x) (sizeof(x) - 1)
78 #define SIZEOF_STR 1024 /* Default string size. */
79 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
80 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
81 #define SIZEOF_ARG 32 /* Default argument array size. */
83 /* Revision graph */
85 #define REVGRAPH_INIT 'I'
86 #define REVGRAPH_MERGE 'M'
87 #define REVGRAPH_BRANCH '+'
88 #define REVGRAPH_COMMIT '*'
89 #define REVGRAPH_BOUND '^'
91 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
93 /* This color name can be used to refer to the default term colors. */
94 #define COLOR_DEFAULT (-1)
96 #define ICONV_NONE ((iconv_t) -1)
97 #ifndef ICONV_CONST
98 #define ICONV_CONST /* nothing */
99 #endif
101 /* The format and size of the date column in the main view. */
102 #define DATE_FORMAT "%Y-%m-%d %H:%M"
103 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define AUTHOR_COLS 20
106 #define ID_COLS 8
108 /* The default interval between line numbers. */
109 #define NUMBER_INTERVAL 5
111 #define TAB_SIZE 8
113 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #ifndef GIT_CONFIG
118 #define GIT_CONFIG "config"
119 #endif
121 /* Some ascii-shorthands fitted into the ncurses namespace. */
122 #define KEY_TAB '\t'
123 #define KEY_RETURN '\r'
124 #define KEY_ESC 27
127 struct ref {
128 char *name; /* Ref name; tag or head names are shortened. */
129 char id[SIZEOF_REV]; /* Commit SHA1 ID */
130 unsigned int head:1; /* Is it the current HEAD? */
131 unsigned int tag:1; /* Is it a tag? */
132 unsigned int ltag:1; /* If so, is the tag local? */
133 unsigned int remote:1; /* Is it a remote ref? */
134 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
135 unsigned int next:1; /* For ref lists: are there more refs? */
136 };
138 static struct ref **get_refs(const char *id);
140 enum format_flags {
141 FORMAT_ALL, /* Perform replacement in all arguments. */
142 FORMAT_DASH, /* Perform replacement up until "--". */
143 FORMAT_NONE /* No replacement should be performed. */
144 };
146 static bool format_command(char dst[], const char *src[], enum format_flags flags);
147 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
149 struct int_map {
150 const char *name;
151 int namelen;
152 int value;
153 };
155 static int
156 set_from_int_map(struct int_map *map, size_t map_size,
157 int *value, const char *name, int namelen)
158 {
160 int i;
162 for (i = 0; i < map_size; i++)
163 if (namelen == map[i].namelen &&
164 !strncasecmp(name, map[i].name, namelen)) {
165 *value = map[i].value;
166 return OK;
167 }
169 return ERR;
170 }
173 /*
174 * String helpers
175 */
177 static inline void
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
179 {
180 if (srclen > dstlen - 1)
181 srclen = dstlen - 1;
183 strncpy(dst, src, srclen);
184 dst[srclen] = 0;
185 }
187 /* Shorthands for safely copying into a fixed buffer. */
189 #define string_copy(dst, src) \
190 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
192 #define string_ncopy(dst, src, srclen) \
193 string_ncopy_do(dst, sizeof(dst), src, srclen)
195 #define string_copy_rev(dst, src) \
196 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
198 #define string_add(dst, from, src) \
199 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
201 static char *
202 chomp_string(char *name)
203 {
204 int namelen;
206 while (isspace(*name))
207 name++;
209 namelen = strlen(name) - 1;
210 while (namelen > 0 && isspace(name[namelen]))
211 name[namelen--] = 0;
213 return name;
214 }
216 static bool
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
218 {
219 va_list args;
220 size_t pos = bufpos ? *bufpos : 0;
222 va_start(args, fmt);
223 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
224 va_end(args);
226 if (bufpos)
227 *bufpos = pos;
229 return pos >= bufsize ? FALSE : TRUE;
230 }
232 #define string_format(buf, fmt, args...) \
233 string_nformat(buf, sizeof(buf), NULL, fmt, args)
235 #define string_format_from(buf, from, fmt, args...) \
236 string_nformat(buf, sizeof(buf), from, fmt, args)
238 static int
239 string_enum_compare(const char *str1, const char *str2, int len)
240 {
241 size_t i;
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
245 /* Diff-Header == DIFF_HEADER */
246 for (i = 0; i < len; i++) {
247 if (toupper(str1[i]) == toupper(str2[i]))
248 continue;
250 if (string_enum_sep(str1[i]) &&
251 string_enum_sep(str2[i]))
252 continue;
254 return str1[i] - str2[i];
255 }
257 return 0;
258 }
260 #define prefixcmp(str1, str2) \
261 strncmp(str1, str2, STRING_SIZE(str2))
263 static inline int
264 suffixcmp(const char *str, int slen, const char *suffix)
265 {
266 size_t len = slen >= 0 ? slen : strlen(str);
267 size_t suffixlen = strlen(suffix);
269 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
270 }
272 /* Shell quoting
273 *
274 * NOTE: The following is a slightly modified copy of the git project's shell
275 * quoting routines found in the quote.c file.
276 *
277 * Help to copy the thing properly quoted for the shell safety. any single
278 * quote is replaced with '\'', any exclamation point is replaced with '\!',
279 * and the whole thing is enclosed in a
280 *
281 * E.g.
282 * original sq_quote result
283 * name ==> name ==> 'name'
284 * a b ==> a b ==> 'a b'
285 * a'b ==> a'\''b ==> 'a'\''b'
286 * a!b ==> a'\!'b ==> 'a'\!'b'
287 */
289 static size_t
290 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
291 {
292 char c;
294 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
296 BUFPUT('\'');
297 while ((c = *src++)) {
298 if (c == '\'' || c == '!') {
299 BUFPUT('\'');
300 BUFPUT('\\');
301 BUFPUT(c);
302 BUFPUT('\'');
303 } else {
304 BUFPUT(c);
305 }
306 }
307 BUFPUT('\'');
309 if (bufsize < SIZEOF_STR)
310 buf[bufsize] = 0;
312 return bufsize;
313 }
315 static bool
316 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
317 {
318 int valuelen;
320 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
321 bool advance = cmd[valuelen] != 0;
323 cmd[valuelen] = 0;
324 argv[(*argc)++] = chomp_string(cmd);
325 cmd += valuelen + advance;
326 }
328 if (*argc < SIZEOF_ARG)
329 argv[*argc] = NULL;
330 return *argc < SIZEOF_ARG;
331 }
333 static void
334 argv_from_env(const char **argv, const char *name)
335 {
336 char *env = argv ? getenv(name) : NULL;
337 int argc = 0;
339 if (env && *env)
340 env = strdup(env);
341 if (env && !argv_from_string(argv, &argc, env))
342 die("Too many arguments in the `%s` environment variable", name);
343 }
346 /*
347 * Executing external commands.
348 */
350 enum io_type {
351 IO_FD, /* File descriptor based IO. */
352 IO_BG, /* Execute command in the background. */
353 IO_FG, /* Execute command with same std{in,out,err}. */
354 IO_RD, /* Read only fork+exec IO. */
355 IO_WR, /* Write only fork+exec IO. */
356 };
358 struct io {
359 enum io_type type; /* The requested type of pipe. */
360 const char *dir; /* Directory from which to execute. */
361 FILE *pipe; /* Pipe for reading or writing. */
362 int error; /* Error status. */
363 char sh[SIZEOF_STR]; /* Shell command buffer. */
364 char *buf; /* Read/write buffer. */
365 size_t bufalloc; /* Allocated buffer size. */
366 };
368 static void
369 reset_io(struct io *io)
370 {
371 io->pipe = NULL;
372 io->buf = NULL;
373 io->bufalloc = 0;
374 io->error = 0;
375 }
377 static void
378 init_io(struct io *io, const char *dir, enum io_type type)
379 {
380 reset_io(io);
381 io->type = type;
382 io->dir = dir;
383 }
385 static bool
386 init_io_rd(struct io *io, const char *argv[], const char *dir,
387 enum format_flags flags)
388 {
389 init_io(io, dir, IO_RD);
390 return format_command(io->sh, argv, flags);
391 }
393 static bool
394 init_io_fd(struct io *io, FILE *pipe)
395 {
396 init_io(io, NULL, IO_FD);
397 io->pipe = pipe;
398 return io->pipe != NULL;
399 }
401 static bool
402 done_io(struct io *io)
403 {
404 free(io->buf);
405 if (io->type == IO_FD)
406 fclose(io->pipe);
407 else if (io->type == IO_RD || io->type == IO_WR)
408 pclose(io->pipe);
409 reset_io(io);
410 return TRUE;
411 }
413 static bool
414 start_io(struct io *io)
415 {
416 char buf[SIZEOF_STR * 2];
417 size_t bufpos = 0;
419 if (io->type == IO_FD)
420 return TRUE;
422 if (io->dir && *io->dir &&
423 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
424 return FALSE;
426 if (!string_format_from(buf, &bufpos, "%s", io->sh))
427 return FALSE;
429 if (io->type == IO_FG || io->type == IO_BG)
430 return system(buf) == 0;
432 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
433 return io->pipe != NULL;
434 }
436 static bool
437 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
438 {
439 init_io(io, dir, type);
440 if (!format_command(io->sh, argv, FORMAT_NONE))
441 return FALSE;
442 return start_io(io);
443 }
445 static int
446 run_io_do(struct io *io)
447 {
448 return start_io(io) && done_io(io);
449 }
451 static int
452 run_io_bg(const char **argv)
453 {
454 struct io io = {};
456 init_io(&io, NULL, IO_BG);
457 if (!format_command(io.sh, argv, FORMAT_NONE))
458 return FALSE;
459 return run_io_do(&io);
460 }
462 static bool
463 run_io_fg(const char **argv, const char *dir)
464 {
465 struct io io = {};
467 init_io(&io, dir, IO_FG);
468 if (!format_command(io.sh, argv, FORMAT_NONE))
469 return FALSE;
470 return run_io_do(&io);
471 }
473 static bool
474 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
475 {
476 return init_io_rd(io, argv, NULL, flags) && start_io(io);
477 }
479 static bool
480 io_eof(struct io *io)
481 {
482 return feof(io->pipe);
483 }
485 static int
486 io_error(struct io *io)
487 {
488 return io->error;
489 }
491 static bool
492 io_strerror(struct io *io)
493 {
494 return strerror(io->error);
495 }
497 static size_t
498 io_read(struct io *io, void *buf, size_t bufsize)
499 {
500 size_t readsize = fread(buf, 1, bufsize, io->pipe);
502 if (ferror(io->pipe))
503 io->error = errno;
505 return readsize;
506 }
508 static char *
509 io_gets(struct io *io)
510 {
511 if (!io->buf) {
512 io->buf = malloc(BUFSIZ);
513 if (!io->buf)
514 return NULL;
515 io->bufalloc = BUFSIZ;
516 }
518 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
519 if (ferror(io->pipe))
520 io->error = errno;
521 return NULL;
522 }
524 return io->buf;
525 }
527 static bool
528 io_write(struct io *io, const void *buf, size_t bufsize)
529 {
530 size_t written = 0;
532 while (!io_error(io) && written < bufsize) {
533 written += fwrite(buf + written, 1, bufsize - written, io->pipe);
534 if (ferror(io->pipe))
535 io->error = errno;
536 }
538 return written == bufsize;
539 }
541 static bool
542 run_io_buf(const char **argv, char buf[], size_t bufsize)
543 {
544 struct io io = {};
545 bool error;
547 if (!run_io_rd(&io, argv, FORMAT_NONE))
548 return FALSE;
550 io.buf = buf;
551 io.bufalloc = bufsize;
552 error = !io_gets(&io) && io_error(&io);
553 io.buf = NULL;
555 return done_io(&io) || error;
556 }
558 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
560 /*
561 * User requests
562 */
564 #define REQ_INFO \
565 /* XXX: Keep the view request first and in sync with views[]. */ \
566 REQ_GROUP("View switching") \
567 REQ_(VIEW_MAIN, "Show main view"), \
568 REQ_(VIEW_DIFF, "Show diff view"), \
569 REQ_(VIEW_LOG, "Show log view"), \
570 REQ_(VIEW_TREE, "Show tree view"), \
571 REQ_(VIEW_BLOB, "Show blob view"), \
572 REQ_(VIEW_BLAME, "Show blame view"), \
573 REQ_(VIEW_HELP, "Show help page"), \
574 REQ_(VIEW_PAGER, "Show pager view"), \
575 REQ_(VIEW_STATUS, "Show status view"), \
576 REQ_(VIEW_STAGE, "Show stage view"), \
577 \
578 REQ_GROUP("View manipulation") \
579 REQ_(ENTER, "Enter current line and scroll"), \
580 REQ_(NEXT, "Move to next"), \
581 REQ_(PREVIOUS, "Move to previous"), \
582 REQ_(VIEW_NEXT, "Move focus to next view"), \
583 REQ_(REFRESH, "Reload and refresh"), \
584 REQ_(MAXIMIZE, "Maximize the current view"), \
585 REQ_(VIEW_CLOSE, "Close the current view"), \
586 REQ_(QUIT, "Close all views and quit"), \
587 \
588 REQ_GROUP("View specific requests") \
589 REQ_(STATUS_UPDATE, "Update file status"), \
590 REQ_(STATUS_REVERT, "Revert file changes"), \
591 REQ_(STATUS_MERGE, "Merge file using external tool"), \
592 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
593 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
594 \
595 REQ_GROUP("Cursor navigation") \
596 REQ_(MOVE_UP, "Move cursor one line up"), \
597 REQ_(MOVE_DOWN, "Move cursor one line down"), \
598 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
599 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
600 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
601 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
602 \
603 REQ_GROUP("Scrolling") \
604 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
605 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
606 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
607 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
608 \
609 REQ_GROUP("Searching") \
610 REQ_(SEARCH, "Search the view"), \
611 REQ_(SEARCH_BACK, "Search backwards in the view"), \
612 REQ_(FIND_NEXT, "Find next search match"), \
613 REQ_(FIND_PREV, "Find previous search match"), \
614 \
615 REQ_GROUP("Option manipulation") \
616 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
617 REQ_(TOGGLE_DATE, "Toggle date display"), \
618 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
619 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
620 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
621 \
622 REQ_GROUP("Misc") \
623 REQ_(PROMPT, "Bring up the prompt"), \
624 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
625 REQ_(SCREEN_RESIZE, "Resize the screen"), \
626 REQ_(SHOW_VERSION, "Show version information"), \
627 REQ_(STOP_LOADING, "Stop all loading views"), \
628 REQ_(EDIT, "Open in editor"), \
629 REQ_(NONE, "Do nothing")
632 /* User action requests. */
633 enum request {
634 #define REQ_GROUP(help)
635 #define REQ_(req, help) REQ_##req
637 /* Offset all requests to avoid conflicts with ncurses getch values. */
638 REQ_OFFSET = KEY_MAX + 1,
639 REQ_INFO
641 #undef REQ_GROUP
642 #undef REQ_
643 };
645 struct request_info {
646 enum request request;
647 const char *name;
648 int namelen;
649 const char *help;
650 };
652 static struct request_info req_info[] = {
653 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
654 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
655 REQ_INFO
656 #undef REQ_GROUP
657 #undef REQ_
658 };
660 static enum request
661 get_request(const char *name)
662 {
663 int namelen = strlen(name);
664 int i;
666 for (i = 0; i < ARRAY_SIZE(req_info); i++)
667 if (req_info[i].namelen == namelen &&
668 !string_enum_compare(req_info[i].name, name, namelen))
669 return req_info[i].request;
671 return REQ_NONE;
672 }
675 /*
676 * Options
677 */
679 static const char usage[] =
680 "tig " TIG_VERSION " (" __DATE__ ")\n"
681 "\n"
682 "Usage: tig [options] [revs] [--] [paths]\n"
683 " or: tig show [options] [revs] [--] [paths]\n"
684 " or: tig blame [rev] path\n"
685 " or: tig status\n"
686 " or: tig < [git command output]\n"
687 "\n"
688 "Options:\n"
689 " -v, --version Show version and exit\n"
690 " -h, --help Show help message and exit";
692 /* Option and state variables. */
693 static bool opt_date = TRUE;
694 static bool opt_author = TRUE;
695 static bool opt_line_number = FALSE;
696 static bool opt_line_graphics = TRUE;
697 static bool opt_rev_graph = FALSE;
698 static bool opt_show_refs = TRUE;
699 static int opt_num_interval = NUMBER_INTERVAL;
700 static int opt_tab_size = TAB_SIZE;
701 static int opt_author_cols = AUTHOR_COLS-1;
702 static char opt_path[SIZEOF_STR] = "";
703 static char opt_file[SIZEOF_STR] = "";
704 static char opt_ref[SIZEOF_REF] = "";
705 static char opt_head[SIZEOF_REF] = "";
706 static char opt_head_rev[SIZEOF_REV] = "";
707 static char opt_remote[SIZEOF_REF] = "";
708 static char opt_encoding[20] = "UTF-8";
709 static bool opt_utf8 = TRUE;
710 static char opt_codeset[20] = "UTF-8";
711 static iconv_t opt_iconv = ICONV_NONE;
712 static char opt_search[SIZEOF_STR] = "";
713 static char opt_cdup[SIZEOF_STR] = "";
714 static char opt_git_dir[SIZEOF_STR] = "";
715 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
716 static char opt_editor[SIZEOF_STR] = "";
717 static FILE *opt_tty = NULL;
719 #define is_initial_commit() (!*opt_head_rev)
720 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
722 static enum request
723 parse_options(int argc, const char *argv[], const char ***run_argv)
724 {
725 enum request request = REQ_VIEW_MAIN;
726 const char *subcommand;
727 bool seen_dashdash = FALSE;
728 /* XXX: This is vulnerable to the user overriding options
729 * required for the main view parser. */
730 const char *custom_argv[SIZEOF_ARG] = {
731 "git", "log", "--no-color", "--pretty=raw", "--parents",
732 "--topo-order", NULL
733 };
734 int i, j = 6;
736 if (!isatty(STDIN_FILENO))
737 return REQ_VIEW_PAGER;
739 if (argc <= 1)
740 return REQ_VIEW_MAIN;
742 subcommand = argv[1];
743 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
744 if (!strcmp(subcommand, "-S"))
745 warn("`-S' has been deprecated; use `tig status' instead");
746 if (argc > 2)
747 warn("ignoring arguments after `%s'", subcommand);
748 return REQ_VIEW_STATUS;
750 } else if (!strcmp(subcommand, "blame")) {
751 if (argc <= 2 || argc > 4)
752 die("invalid number of options to blame\n\n%s", usage);
754 i = 2;
755 if (argc == 4) {
756 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
757 i++;
758 }
760 string_ncopy(opt_file, argv[i], strlen(argv[i]));
761 return REQ_VIEW_BLAME;
763 } else if (!strcmp(subcommand, "show")) {
764 request = REQ_VIEW_DIFF;
766 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
767 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
768 warn("`tig %s' has been deprecated", subcommand);
770 } else {
771 subcommand = NULL;
772 }
774 if (subcommand) {
775 custom_argv[1] = subcommand;
776 j = 2;
777 }
779 for (i = 1 + !!subcommand; i < argc; i++) {
780 const char *opt = argv[i];
782 if (seen_dashdash || !strcmp(opt, "--")) {
783 seen_dashdash = TRUE;
785 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
786 printf("tig version %s\n", TIG_VERSION);
787 return REQ_NONE;
789 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
790 printf("%s\n", usage);
791 return REQ_NONE;
792 }
794 custom_argv[j++] = opt;
795 if (j >= ARRAY_SIZE(custom_argv))
796 die("command too long");
797 }
799 custom_argv[j] = NULL;
800 *run_argv = custom_argv;
802 return request;
803 }
806 /*
807 * Line-oriented content detection.
808 */
810 #define LINE_INFO \
811 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
812 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
814 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
815 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
816 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
817 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
819 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
820 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
822 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
823 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
824 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
825 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
826 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
827 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
828 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
829 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
830 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
831 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
832 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
833 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
834 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
835 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
836 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
837 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
838 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
839 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
840 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
841 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
842 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
843 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
844 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
845 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
846 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
847 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
848 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
849 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
850 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
851 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
852 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
853 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
854 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
855 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
856 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
857 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
858 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
859 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
860 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
861 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
862 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
863 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
864 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
866 enum line_type {
867 #define LINE(type, line, fg, bg, attr) \
868 LINE_##type
869 LINE_INFO,
870 LINE_NONE
871 #undef LINE
872 };
874 struct line_info {
875 const char *name; /* Option name. */
876 int namelen; /* Size of option name. */
877 const char *line; /* The start of line to match. */
878 int linelen; /* Size of string to match. */
879 int fg, bg, attr; /* Color and text attributes for the lines. */
880 };
882 static struct line_info line_info[] = {
883 #define LINE(type, line, fg, bg, attr) \
884 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
885 LINE_INFO
886 #undef LINE
887 };
889 static enum line_type
890 get_line_type(const char *line)
891 {
892 int linelen = strlen(line);
893 enum line_type type;
895 for (type = 0; type < ARRAY_SIZE(line_info); type++)
896 /* Case insensitive search matches Signed-off-by lines better. */
897 if (linelen >= line_info[type].linelen &&
898 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
899 return type;
901 return LINE_DEFAULT;
902 }
904 static inline int
905 get_line_attr(enum line_type type)
906 {
907 assert(type < ARRAY_SIZE(line_info));
908 return COLOR_PAIR(type) | line_info[type].attr;
909 }
911 static struct line_info *
912 get_line_info(const char *name)
913 {
914 size_t namelen = strlen(name);
915 enum line_type type;
917 for (type = 0; type < ARRAY_SIZE(line_info); type++)
918 if (namelen == line_info[type].namelen &&
919 !string_enum_compare(line_info[type].name, name, namelen))
920 return &line_info[type];
922 return NULL;
923 }
925 static void
926 init_colors(void)
927 {
928 int default_bg = line_info[LINE_DEFAULT].bg;
929 int default_fg = line_info[LINE_DEFAULT].fg;
930 enum line_type type;
932 start_color();
934 if (assume_default_colors(default_fg, default_bg) == ERR) {
935 default_bg = COLOR_BLACK;
936 default_fg = COLOR_WHITE;
937 }
939 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
940 struct line_info *info = &line_info[type];
941 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
942 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
944 init_pair(type, fg, bg);
945 }
946 }
948 struct line {
949 enum line_type type;
951 /* State flags */
952 unsigned int selected:1;
953 unsigned int dirty:1;
955 void *data; /* User data */
956 };
959 /*
960 * Keys
961 */
963 struct keybinding {
964 int alias;
965 enum request request;
966 };
968 static struct keybinding default_keybindings[] = {
969 /* View switching */
970 { 'm', REQ_VIEW_MAIN },
971 { 'd', REQ_VIEW_DIFF },
972 { 'l', REQ_VIEW_LOG },
973 { 't', REQ_VIEW_TREE },
974 { 'f', REQ_VIEW_BLOB },
975 { 'B', REQ_VIEW_BLAME },
976 { 'p', REQ_VIEW_PAGER },
977 { 'h', REQ_VIEW_HELP },
978 { 'S', REQ_VIEW_STATUS },
979 { 'c', REQ_VIEW_STAGE },
981 /* View manipulation */
982 { 'q', REQ_VIEW_CLOSE },
983 { KEY_TAB, REQ_VIEW_NEXT },
984 { KEY_RETURN, REQ_ENTER },
985 { KEY_UP, REQ_PREVIOUS },
986 { KEY_DOWN, REQ_NEXT },
987 { 'R', REQ_REFRESH },
988 { KEY_F(5), REQ_REFRESH },
989 { 'O', REQ_MAXIMIZE },
991 /* Cursor navigation */
992 { 'k', REQ_MOVE_UP },
993 { 'j', REQ_MOVE_DOWN },
994 { KEY_HOME, REQ_MOVE_FIRST_LINE },
995 { KEY_END, REQ_MOVE_LAST_LINE },
996 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
997 { ' ', REQ_MOVE_PAGE_DOWN },
998 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
999 { 'b', REQ_MOVE_PAGE_UP },
1000 { '-', REQ_MOVE_PAGE_UP },
1002 /* Scrolling */
1003 { KEY_IC, REQ_SCROLL_LINE_UP },
1004 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1005 { 'w', REQ_SCROLL_PAGE_UP },
1006 { 's', REQ_SCROLL_PAGE_DOWN },
1008 /* Searching */
1009 { '/', REQ_SEARCH },
1010 { '?', REQ_SEARCH_BACK },
1011 { 'n', REQ_FIND_NEXT },
1012 { 'N', REQ_FIND_PREV },
1014 /* Misc */
1015 { 'Q', REQ_QUIT },
1016 { 'z', REQ_STOP_LOADING },
1017 { 'v', REQ_SHOW_VERSION },
1018 { 'r', REQ_SCREEN_REDRAW },
1019 { '.', REQ_TOGGLE_LINENO },
1020 { 'D', REQ_TOGGLE_DATE },
1021 { 'A', REQ_TOGGLE_AUTHOR },
1022 { 'g', REQ_TOGGLE_REV_GRAPH },
1023 { 'F', REQ_TOGGLE_REFS },
1024 { ':', REQ_PROMPT },
1025 { 'u', REQ_STATUS_UPDATE },
1026 { '!', REQ_STATUS_REVERT },
1027 { 'M', REQ_STATUS_MERGE },
1028 { '@', REQ_STAGE_NEXT },
1029 { ',', REQ_TREE_PARENT },
1030 { 'e', REQ_EDIT },
1032 /* Using the ncurses SIGWINCH handler. */
1033 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1034 };
1036 #define KEYMAP_INFO \
1037 KEYMAP_(GENERIC), \
1038 KEYMAP_(MAIN), \
1039 KEYMAP_(DIFF), \
1040 KEYMAP_(LOG), \
1041 KEYMAP_(TREE), \
1042 KEYMAP_(BLOB), \
1043 KEYMAP_(BLAME), \
1044 KEYMAP_(PAGER), \
1045 KEYMAP_(HELP), \
1046 KEYMAP_(STATUS), \
1047 KEYMAP_(STAGE)
1049 enum keymap {
1050 #define KEYMAP_(name) KEYMAP_##name
1051 KEYMAP_INFO
1052 #undef KEYMAP_
1053 };
1055 static struct int_map keymap_table[] = {
1056 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1057 KEYMAP_INFO
1058 #undef KEYMAP_
1059 };
1061 #define set_keymap(map, name) \
1062 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1064 struct keybinding_table {
1065 struct keybinding *data;
1066 size_t size;
1067 };
1069 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1071 static void
1072 add_keybinding(enum keymap keymap, enum request request, int key)
1073 {
1074 struct keybinding_table *table = &keybindings[keymap];
1076 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1077 if (!table->data)
1078 die("Failed to allocate keybinding");
1079 table->data[table->size].alias = key;
1080 table->data[table->size++].request = request;
1081 }
1083 /* Looks for a key binding first in the given map, then in the generic map, and
1084 * lastly in the default keybindings. */
1085 static enum request
1086 get_keybinding(enum keymap keymap, int key)
1087 {
1088 size_t i;
1090 for (i = 0; i < keybindings[keymap].size; i++)
1091 if (keybindings[keymap].data[i].alias == key)
1092 return keybindings[keymap].data[i].request;
1094 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1095 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1096 return keybindings[KEYMAP_GENERIC].data[i].request;
1098 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1099 if (default_keybindings[i].alias == key)
1100 return default_keybindings[i].request;
1102 return (enum request) key;
1103 }
1106 struct key {
1107 const char *name;
1108 int value;
1109 };
1111 static struct key key_table[] = {
1112 { "Enter", KEY_RETURN },
1113 { "Space", ' ' },
1114 { "Backspace", KEY_BACKSPACE },
1115 { "Tab", KEY_TAB },
1116 { "Escape", KEY_ESC },
1117 { "Left", KEY_LEFT },
1118 { "Right", KEY_RIGHT },
1119 { "Up", KEY_UP },
1120 { "Down", KEY_DOWN },
1121 { "Insert", KEY_IC },
1122 { "Delete", KEY_DC },
1123 { "Hash", '#' },
1124 { "Home", KEY_HOME },
1125 { "End", KEY_END },
1126 { "PageUp", KEY_PPAGE },
1127 { "PageDown", KEY_NPAGE },
1128 { "F1", KEY_F(1) },
1129 { "F2", KEY_F(2) },
1130 { "F3", KEY_F(3) },
1131 { "F4", KEY_F(4) },
1132 { "F5", KEY_F(5) },
1133 { "F6", KEY_F(6) },
1134 { "F7", KEY_F(7) },
1135 { "F8", KEY_F(8) },
1136 { "F9", KEY_F(9) },
1137 { "F10", KEY_F(10) },
1138 { "F11", KEY_F(11) },
1139 { "F12", KEY_F(12) },
1140 };
1142 static int
1143 get_key_value(const char *name)
1144 {
1145 int i;
1147 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1148 if (!strcasecmp(key_table[i].name, name))
1149 return key_table[i].value;
1151 if (strlen(name) == 1 && isprint(*name))
1152 return (int) *name;
1154 return ERR;
1155 }
1157 static const char *
1158 get_key_name(int key_value)
1159 {
1160 static char key_char[] = "'X'";
1161 const char *seq = NULL;
1162 int key;
1164 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1165 if (key_table[key].value == key_value)
1166 seq = key_table[key].name;
1168 if (seq == NULL &&
1169 key_value < 127 &&
1170 isprint(key_value)) {
1171 key_char[1] = (char) key_value;
1172 seq = key_char;
1173 }
1175 return seq ? seq : "(no key)";
1176 }
1178 static const char *
1179 get_key(enum request request)
1180 {
1181 static char buf[BUFSIZ];
1182 size_t pos = 0;
1183 char *sep = "";
1184 int i;
1186 buf[pos] = 0;
1188 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1189 struct keybinding *keybinding = &default_keybindings[i];
1191 if (keybinding->request != request)
1192 continue;
1194 if (!string_format_from(buf, &pos, "%s%s", sep,
1195 get_key_name(keybinding->alias)))
1196 return "Too many keybindings!";
1197 sep = ", ";
1198 }
1200 return buf;
1201 }
1203 struct run_request {
1204 enum keymap keymap;
1205 int key;
1206 const char *argv[SIZEOF_ARG];
1207 };
1209 static struct run_request *run_request;
1210 static size_t run_requests;
1212 static enum request
1213 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1214 {
1215 struct run_request *req;
1217 if (argc >= ARRAY_SIZE(req->argv) - 1)
1218 return REQ_NONE;
1220 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1221 if (!req)
1222 return REQ_NONE;
1224 run_request = req;
1225 req = &run_request[run_requests];
1226 req->keymap = keymap;
1227 req->key = key;
1228 req->argv[0] = NULL;
1230 if (!format_argv(req->argv, argv, FORMAT_NONE))
1231 return REQ_NONE;
1233 return REQ_NONE + ++run_requests;
1234 }
1236 static struct run_request *
1237 get_run_request(enum request request)
1238 {
1239 if (request <= REQ_NONE)
1240 return NULL;
1241 return &run_request[request - REQ_NONE - 1];
1242 }
1244 static void
1245 add_builtin_run_requests(void)
1246 {
1247 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1248 const char *gc[] = { "git", "gc", NULL };
1249 struct {
1250 enum keymap keymap;
1251 int key;
1252 int argc;
1253 const char **argv;
1254 } reqs[] = {
1255 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1256 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1257 };
1258 int i;
1260 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1261 enum request req;
1263 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1264 if (req != REQ_NONE)
1265 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1266 }
1267 }
1269 /*
1270 * User config file handling.
1271 */
1273 static struct int_map color_map[] = {
1274 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1275 COLOR_MAP(DEFAULT),
1276 COLOR_MAP(BLACK),
1277 COLOR_MAP(BLUE),
1278 COLOR_MAP(CYAN),
1279 COLOR_MAP(GREEN),
1280 COLOR_MAP(MAGENTA),
1281 COLOR_MAP(RED),
1282 COLOR_MAP(WHITE),
1283 COLOR_MAP(YELLOW),
1284 };
1286 #define set_color(color, name) \
1287 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1289 static struct int_map attr_map[] = {
1290 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1291 ATTR_MAP(NORMAL),
1292 ATTR_MAP(BLINK),
1293 ATTR_MAP(BOLD),
1294 ATTR_MAP(DIM),
1295 ATTR_MAP(REVERSE),
1296 ATTR_MAP(STANDOUT),
1297 ATTR_MAP(UNDERLINE),
1298 };
1300 #define set_attribute(attr, name) \
1301 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1303 static int config_lineno;
1304 static bool config_errors;
1305 static const char *config_msg;
1307 /* Wants: object fgcolor bgcolor [attr] */
1308 static int
1309 option_color_command(int argc, const char *argv[])
1310 {
1311 struct line_info *info;
1313 if (argc != 3 && argc != 4) {
1314 config_msg = "Wrong number of arguments given to color command";
1315 return ERR;
1316 }
1318 info = get_line_info(argv[0]);
1319 if (!info) {
1320 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1321 info = get_line_info("delimiter");
1323 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1324 info = get_line_info("date");
1326 } else {
1327 config_msg = "Unknown color name";
1328 return ERR;
1329 }
1330 }
1332 if (set_color(&info->fg, argv[1]) == ERR ||
1333 set_color(&info->bg, argv[2]) == ERR) {
1334 config_msg = "Unknown color";
1335 return ERR;
1336 }
1338 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1339 config_msg = "Unknown attribute";
1340 return ERR;
1341 }
1343 return OK;
1344 }
1346 static bool parse_bool(const char *s)
1347 {
1348 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1349 !strcmp(s, "yes")) ? TRUE : FALSE;
1350 }
1352 static int
1353 parse_int(const char *s, int default_value, int min, int max)
1354 {
1355 int value = atoi(s);
1357 return (value < min || value > max) ? default_value : value;
1358 }
1360 /* Wants: name = value */
1361 static int
1362 option_set_command(int argc, const char *argv[])
1363 {
1364 if (argc != 3) {
1365 config_msg = "Wrong number of arguments given to set command";
1366 return ERR;
1367 }
1369 if (strcmp(argv[1], "=")) {
1370 config_msg = "No value assigned";
1371 return ERR;
1372 }
1374 if (!strcmp(argv[0], "show-author")) {
1375 opt_author = parse_bool(argv[2]);
1376 return OK;
1377 }
1379 if (!strcmp(argv[0], "show-date")) {
1380 opt_date = parse_bool(argv[2]);
1381 return OK;
1382 }
1384 if (!strcmp(argv[0], "show-rev-graph")) {
1385 opt_rev_graph = parse_bool(argv[2]);
1386 return OK;
1387 }
1389 if (!strcmp(argv[0], "show-refs")) {
1390 opt_show_refs = parse_bool(argv[2]);
1391 return OK;
1392 }
1394 if (!strcmp(argv[0], "show-line-numbers")) {
1395 opt_line_number = parse_bool(argv[2]);
1396 return OK;
1397 }
1399 if (!strcmp(argv[0], "line-graphics")) {
1400 opt_line_graphics = parse_bool(argv[2]);
1401 return OK;
1402 }
1404 if (!strcmp(argv[0], "line-number-interval")) {
1405 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1406 return OK;
1407 }
1409 if (!strcmp(argv[0], "author-width")) {
1410 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1411 return OK;
1412 }
1414 if (!strcmp(argv[0], "tab-size")) {
1415 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1416 return OK;
1417 }
1419 if (!strcmp(argv[0], "commit-encoding")) {
1420 const char *arg = argv[2];
1421 int arglen = strlen(arg);
1423 switch (arg[0]) {
1424 case '"':
1425 case '\'':
1426 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1427 config_msg = "Unmatched quotation";
1428 return ERR;
1429 }
1430 arg += 1; arglen -= 2;
1431 default:
1432 string_ncopy(opt_encoding, arg, strlen(arg));
1433 return OK;
1434 }
1435 }
1437 config_msg = "Unknown variable name";
1438 return ERR;
1439 }
1441 /* Wants: mode request key */
1442 static int
1443 option_bind_command(int argc, const char *argv[])
1444 {
1445 enum request request;
1446 int keymap;
1447 int key;
1449 if (argc < 3) {
1450 config_msg = "Wrong number of arguments given to bind command";
1451 return ERR;
1452 }
1454 if (set_keymap(&keymap, argv[0]) == ERR) {
1455 config_msg = "Unknown key map";
1456 return ERR;
1457 }
1459 key = get_key_value(argv[1]);
1460 if (key == ERR) {
1461 config_msg = "Unknown key";
1462 return ERR;
1463 }
1465 request = get_request(argv[2]);
1466 if (request == REQ_NONE) {
1467 const char *obsolete[] = { "cherry-pick" };
1468 size_t namelen = strlen(argv[2]);
1469 int i;
1471 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1472 if (namelen == strlen(obsolete[i]) &&
1473 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1474 config_msg = "Obsolete request name";
1475 return ERR;
1476 }
1477 }
1478 }
1479 if (request == REQ_NONE && *argv[2]++ == '!')
1480 request = add_run_request(keymap, key, argc - 2, argv + 2);
1481 if (request == REQ_NONE) {
1482 config_msg = "Unknown request name";
1483 return ERR;
1484 }
1486 add_keybinding(keymap, request, key);
1488 return OK;
1489 }
1491 static int
1492 set_option(const char *opt, char *value)
1493 {
1494 const char *argv[SIZEOF_ARG];
1495 int argc = 0;
1497 if (!argv_from_string(argv, &argc, value)) {
1498 config_msg = "Too many option arguments";
1499 return ERR;
1500 }
1502 if (!strcmp(opt, "color"))
1503 return option_color_command(argc, argv);
1505 if (!strcmp(opt, "set"))
1506 return option_set_command(argc, argv);
1508 if (!strcmp(opt, "bind"))
1509 return option_bind_command(argc, argv);
1511 config_msg = "Unknown option command";
1512 return ERR;
1513 }
1515 static int
1516 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1517 {
1518 int status = OK;
1520 config_lineno++;
1521 config_msg = "Internal error";
1523 /* Check for comment markers, since read_properties() will
1524 * only ensure opt and value are split at first " \t". */
1525 optlen = strcspn(opt, "#");
1526 if (optlen == 0)
1527 return OK;
1529 if (opt[optlen] != 0) {
1530 config_msg = "No option value";
1531 status = ERR;
1533 } else {
1534 /* Look for comment endings in the value. */
1535 size_t len = strcspn(value, "#");
1537 if (len < valuelen) {
1538 valuelen = len;
1539 value[valuelen] = 0;
1540 }
1542 status = set_option(opt, value);
1543 }
1545 if (status == ERR) {
1546 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1547 config_lineno, (int) optlen, opt, config_msg);
1548 config_errors = TRUE;
1549 }
1551 /* Always keep going if errors are encountered. */
1552 return OK;
1553 }
1555 static void
1556 load_option_file(const char *path)
1557 {
1558 struct io io = {};
1560 /* It's ok that the file doesn't exist. */
1561 if (!init_io_fd(&io, fopen(path, "r")))
1562 return;
1564 config_lineno = 0;
1565 config_errors = FALSE;
1567 if (read_properties(&io, " \t", read_option) == ERR ||
1568 config_errors == TRUE)
1569 fprintf(stderr, "Errors while loading %s.\n", path);
1570 }
1572 static int
1573 load_options(void)
1574 {
1575 const char *home = getenv("HOME");
1576 const char *tigrc_user = getenv("TIGRC_USER");
1577 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1578 char buf[SIZEOF_STR];
1580 add_builtin_run_requests();
1582 if (!tigrc_system) {
1583 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1584 return ERR;
1585 tigrc_system = buf;
1586 }
1587 load_option_file(tigrc_system);
1589 if (!tigrc_user) {
1590 if (!home || !string_format(buf, "%s/.tigrc", home))
1591 return ERR;
1592 tigrc_user = buf;
1593 }
1594 load_option_file(tigrc_user);
1596 return OK;
1597 }
1600 /*
1601 * The viewer
1602 */
1604 struct view;
1605 struct view_ops;
1607 /* The display array of active views and the index of the current view. */
1608 static struct view *display[2];
1609 static unsigned int current_view;
1611 /* Reading from the prompt? */
1612 static bool input_mode = FALSE;
1614 #define foreach_displayed_view(view, i) \
1615 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1617 #define displayed_views() (display[1] != NULL ? 2 : 1)
1619 /* Current head and commit ID */
1620 static char ref_blob[SIZEOF_REF] = "";
1621 static char ref_commit[SIZEOF_REF] = "HEAD";
1622 static char ref_head[SIZEOF_REF] = "HEAD";
1624 struct view {
1625 const char *name; /* View name */
1626 const char *cmd_env; /* Command line set via environment */
1627 const char *id; /* Points to either of ref_{head,commit,blob} */
1629 struct view_ops *ops; /* View operations */
1631 enum keymap keymap; /* What keymap does this view have */
1632 bool git_dir; /* Whether the view requires a git directory. */
1634 char ref[SIZEOF_REF]; /* Hovered commit reference */
1635 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1637 int height, width; /* The width and height of the main window */
1638 WINDOW *win; /* The main window */
1639 WINDOW *title; /* The title window living below the main window */
1641 /* Navigation */
1642 unsigned long offset; /* Offset of the window top */
1643 unsigned long lineno; /* Current line number */
1645 /* Searching */
1646 char grep[SIZEOF_STR]; /* Search string */
1647 regex_t *regex; /* Pre-compiled regex */
1649 /* If non-NULL, points to the view that opened this view. If this view
1650 * is closed tig will switch back to the parent view. */
1651 struct view *parent;
1653 /* Buffering */
1654 size_t lines; /* Total number of lines */
1655 struct line *line; /* Line index */
1656 size_t line_alloc; /* Total number of allocated lines */
1657 size_t line_size; /* Total number of used lines */
1658 unsigned int digits; /* Number of digits in the lines member. */
1660 /* Drawing */
1661 struct line *curline; /* Line currently being drawn. */
1662 enum line_type curtype; /* Attribute currently used for drawing. */
1663 unsigned long col; /* Column when drawing. */
1665 /* Loading */
1666 struct io io;
1667 struct io *pipe;
1668 time_t start_time;
1669 };
1671 struct view_ops {
1672 /* What type of content being displayed. Used in the title bar. */
1673 const char *type;
1674 /* Default command arguments. */
1675 const char **argv;
1676 /* Open and reads in all view content. */
1677 bool (*open)(struct view *view);
1678 /* Read one line; updates view->line. */
1679 bool (*read)(struct view *view, char *data);
1680 /* Draw one line; @lineno must be < view->height. */
1681 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1682 /* Depending on view handle a special requests. */
1683 enum request (*request)(struct view *view, enum request request, struct line *line);
1684 /* Search for regex in a line. */
1685 bool (*grep)(struct view *view, struct line *line);
1686 /* Select line */
1687 void (*select)(struct view *view, struct line *line);
1688 };
1690 static struct view_ops blame_ops;
1691 static struct view_ops blob_ops;
1692 static struct view_ops diff_ops;
1693 static struct view_ops help_ops;
1694 static struct view_ops log_ops;
1695 static struct view_ops main_ops;
1696 static struct view_ops pager_ops;
1697 static struct view_ops stage_ops;
1698 static struct view_ops status_ops;
1699 static struct view_ops tree_ops;
1701 #define VIEW_STR(name, env, ref, ops, map, git) \
1702 { name, #env, ref, ops, map, git }
1704 #define VIEW_(id, name, ops, git, ref) \
1705 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1708 static struct view views[] = {
1709 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1710 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1711 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1712 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1713 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1714 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1715 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1716 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1717 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1718 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1719 };
1721 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1722 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1724 #define foreach_view(view, i) \
1725 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1727 #define view_is_displayed(view) \
1728 (view == display[0] || view == display[1])
1731 enum line_graphic {
1732 LINE_GRAPHIC_VLINE
1733 };
1735 static int line_graphics[] = {
1736 /* LINE_GRAPHIC_VLINE: */ '|'
1737 };
1739 static inline void
1740 set_view_attr(struct view *view, enum line_type type)
1741 {
1742 if (!view->curline->selected && view->curtype != type) {
1743 wattrset(view->win, get_line_attr(type));
1744 wchgat(view->win, -1, 0, type, NULL);
1745 view->curtype = type;
1746 }
1747 }
1749 static int
1750 draw_chars(struct view *view, enum line_type type, const char *string,
1751 int max_len, bool use_tilde)
1752 {
1753 int len = 0;
1754 int col = 0;
1755 int trimmed = FALSE;
1757 if (max_len <= 0)
1758 return 0;
1760 if (opt_utf8) {
1761 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1762 } else {
1763 col = len = strlen(string);
1764 if (len > max_len) {
1765 if (use_tilde) {
1766 max_len -= 1;
1767 }
1768 col = len = max_len;
1769 trimmed = TRUE;
1770 }
1771 }
1773 set_view_attr(view, type);
1774 waddnstr(view->win, string, len);
1775 if (trimmed && use_tilde) {
1776 set_view_attr(view, LINE_DELIMITER);
1777 waddch(view->win, '~');
1778 col++;
1779 }
1781 return col;
1782 }
1784 static int
1785 draw_space(struct view *view, enum line_type type, int max, int spaces)
1786 {
1787 static char space[] = " ";
1788 int col = 0;
1790 spaces = MIN(max, spaces);
1792 while (spaces > 0) {
1793 int len = MIN(spaces, sizeof(space) - 1);
1795 col += draw_chars(view, type, space, spaces, FALSE);
1796 spaces -= len;
1797 }
1799 return col;
1800 }
1802 static bool
1803 draw_lineno(struct view *view, unsigned int lineno)
1804 {
1805 char number[10];
1806 int digits3 = view->digits < 3 ? 3 : view->digits;
1807 int max_number = MIN(digits3, STRING_SIZE(number));
1808 int max = view->width - view->col;
1809 int col;
1811 if (max < max_number)
1812 max_number = max;
1814 lineno += view->offset + 1;
1815 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1816 static char fmt[] = "%1ld";
1818 if (view->digits <= 9)
1819 fmt[1] = '0' + digits3;
1821 if (!string_format(number, fmt, lineno))
1822 number[0] = 0;
1823 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1824 } else {
1825 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1826 }
1828 if (col < max) {
1829 set_view_attr(view, LINE_DEFAULT);
1830 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1831 col++;
1832 }
1834 if (col < max)
1835 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1836 view->col += col;
1838 return view->width - view->col <= 0;
1839 }
1841 static bool
1842 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1843 {
1844 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1845 return view->width - view->col <= 0;
1846 }
1848 static bool
1849 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1850 {
1851 int max = view->width - view->col;
1852 int i;
1854 if (max < size)
1855 size = max;
1857 set_view_attr(view, type);
1858 /* Using waddch() instead of waddnstr() ensures that
1859 * they'll be rendered correctly for the cursor line. */
1860 for (i = 0; i < size; i++)
1861 waddch(view->win, graphic[i]);
1863 view->col += size;
1864 if (size < max) {
1865 waddch(view->win, ' ');
1866 view->col++;
1867 }
1869 return view->width - view->col <= 0;
1870 }
1872 static bool
1873 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1874 {
1875 int max = MIN(view->width - view->col, len);
1876 int col;
1878 if (text)
1879 col = draw_chars(view, type, text, max - 1, trim);
1880 else
1881 col = draw_space(view, type, max - 1, max - 1);
1883 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1884 return view->width - view->col <= 0;
1885 }
1887 static bool
1888 draw_date(struct view *view, struct tm *time)
1889 {
1890 char buf[DATE_COLS];
1891 char *date;
1892 int timelen = 0;
1894 if (time)
1895 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1896 date = timelen ? buf : NULL;
1898 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1899 }
1901 static bool
1902 draw_view_line(struct view *view, unsigned int lineno)
1903 {
1904 struct line *line;
1905 bool selected = (view->offset + lineno == view->lineno);
1906 bool draw_ok;
1908 assert(view_is_displayed(view));
1910 if (view->offset + lineno >= view->lines)
1911 return FALSE;
1913 line = &view->line[view->offset + lineno];
1915 wmove(view->win, lineno, 0);
1916 view->col = 0;
1917 view->curline = line;
1918 view->curtype = LINE_NONE;
1919 line->selected = FALSE;
1921 if (selected) {
1922 set_view_attr(view, LINE_CURSOR);
1923 line->selected = TRUE;
1924 view->ops->select(view, line);
1925 } else if (line->selected) {
1926 wclrtoeol(view->win);
1927 }
1929 scrollok(view->win, FALSE);
1930 draw_ok = view->ops->draw(view, line, lineno);
1931 scrollok(view->win, TRUE);
1933 return draw_ok;
1934 }
1936 static void
1937 redraw_view_dirty(struct view *view)
1938 {
1939 bool dirty = FALSE;
1940 int lineno;
1942 for (lineno = 0; lineno < view->height; lineno++) {
1943 struct line *line = &view->line[view->offset + lineno];
1945 if (!line->dirty)
1946 continue;
1947 line->dirty = 0;
1948 dirty = TRUE;
1949 if (!draw_view_line(view, lineno))
1950 break;
1951 }
1953 if (!dirty)
1954 return;
1955 redrawwin(view->win);
1956 if (input_mode)
1957 wnoutrefresh(view->win);
1958 else
1959 wrefresh(view->win);
1960 }
1962 static void
1963 redraw_view_from(struct view *view, int lineno)
1964 {
1965 assert(0 <= lineno && lineno < view->height);
1967 for (; lineno < view->height; lineno++) {
1968 if (!draw_view_line(view, lineno))
1969 break;
1970 }
1972 redrawwin(view->win);
1973 if (input_mode)
1974 wnoutrefresh(view->win);
1975 else
1976 wrefresh(view->win);
1977 }
1979 static void
1980 redraw_view(struct view *view)
1981 {
1982 wclear(view->win);
1983 redraw_view_from(view, 0);
1984 }
1987 static void
1988 update_view_title(struct view *view)
1989 {
1990 char buf[SIZEOF_STR];
1991 char state[SIZEOF_STR];
1992 size_t bufpos = 0, statelen = 0;
1994 assert(view_is_displayed(view));
1996 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1997 unsigned int view_lines = view->offset + view->height;
1998 unsigned int lines = view->lines
1999 ? MIN(view_lines, view->lines) * 100 / view->lines
2000 : 0;
2002 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2003 view->ops->type,
2004 view->lineno + 1,
2005 view->lines,
2006 lines);
2008 if (view->pipe) {
2009 time_t secs = time(NULL) - view->start_time;
2011 /* Three git seconds are a long time ... */
2012 if (secs > 2)
2013 string_format_from(state, &statelen, " %lds", secs);
2014 }
2015 }
2017 string_format_from(buf, &bufpos, "[%s]", view->name);
2018 if (*view->ref && bufpos < view->width) {
2019 size_t refsize = strlen(view->ref);
2020 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2022 if (minsize < view->width)
2023 refsize = view->width - minsize + 7;
2024 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2025 }
2027 if (statelen && bufpos < view->width) {
2028 string_format_from(buf, &bufpos, " %s", state);
2029 }
2031 if (view == display[current_view])
2032 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2033 else
2034 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2036 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2037 wclrtoeol(view->title);
2038 wmove(view->title, 0, view->width - 1);
2040 if (input_mode)
2041 wnoutrefresh(view->title);
2042 else
2043 wrefresh(view->title);
2044 }
2046 static void
2047 resize_display(void)
2048 {
2049 int offset, i;
2050 struct view *base = display[0];
2051 struct view *view = display[1] ? display[1] : display[0];
2053 /* Setup window dimensions */
2055 getmaxyx(stdscr, base->height, base->width);
2057 /* Make room for the status window. */
2058 base->height -= 1;
2060 if (view != base) {
2061 /* Horizontal split. */
2062 view->width = base->width;
2063 view->height = SCALE_SPLIT_VIEW(base->height);
2064 base->height -= view->height;
2066 /* Make room for the title bar. */
2067 view->height -= 1;
2068 }
2070 /* Make room for the title bar. */
2071 base->height -= 1;
2073 offset = 0;
2075 foreach_displayed_view (view, i) {
2076 if (!view->win) {
2077 view->win = newwin(view->height, 0, offset, 0);
2078 if (!view->win)
2079 die("Failed to create %s view", view->name);
2081 scrollok(view->win, TRUE);
2083 view->title = newwin(1, 0, offset + view->height, 0);
2084 if (!view->title)
2085 die("Failed to create title window");
2087 } else {
2088 wresize(view->win, view->height, view->width);
2089 mvwin(view->win, offset, 0);
2090 mvwin(view->title, offset + view->height, 0);
2091 }
2093 offset += view->height + 1;
2094 }
2095 }
2097 static void
2098 redraw_display(void)
2099 {
2100 struct view *view;
2101 int i;
2103 foreach_displayed_view (view, i) {
2104 redraw_view(view);
2105 update_view_title(view);
2106 }
2107 }
2109 static void
2110 update_display_cursor(struct view *view)
2111 {
2112 /* Move the cursor to the right-most column of the cursor line.
2113 *
2114 * XXX: This could turn out to be a bit expensive, but it ensures that
2115 * the cursor does not jump around. */
2116 if (view->lines) {
2117 wmove(view->win, view->lineno - view->offset, view->width - 1);
2118 wrefresh(view->win);
2119 }
2120 }
2122 /*
2123 * Navigation
2124 */
2126 /* Scrolling backend */
2127 static void
2128 do_scroll_view(struct view *view, int lines)
2129 {
2130 bool redraw_current_line = FALSE;
2132 /* The rendering expects the new offset. */
2133 view->offset += lines;
2135 assert(0 <= view->offset && view->offset < view->lines);
2136 assert(lines);
2138 /* Move current line into the view. */
2139 if (view->lineno < view->offset) {
2140 view->lineno = view->offset;
2141 redraw_current_line = TRUE;
2142 } else if (view->lineno >= view->offset + view->height) {
2143 view->lineno = view->offset + view->height - 1;
2144 redraw_current_line = TRUE;
2145 }
2147 assert(view->offset <= view->lineno && view->lineno < view->lines);
2149 /* Redraw the whole screen if scrolling is pointless. */
2150 if (view->height < ABS(lines)) {
2151 redraw_view(view);
2153 } else {
2154 int line = lines > 0 ? view->height - lines : 0;
2155 int end = line + ABS(lines);
2157 wscrl(view->win, lines);
2159 for (; line < end; line++) {
2160 if (!draw_view_line(view, line))
2161 break;
2162 }
2164 if (redraw_current_line)
2165 draw_view_line(view, view->lineno - view->offset);
2166 }
2168 redrawwin(view->win);
2169 wrefresh(view->win);
2170 report("");
2171 }
2173 /* Scroll frontend */
2174 static void
2175 scroll_view(struct view *view, enum request request)
2176 {
2177 int lines = 1;
2179 assert(view_is_displayed(view));
2181 switch (request) {
2182 case REQ_SCROLL_PAGE_DOWN:
2183 lines = view->height;
2184 case REQ_SCROLL_LINE_DOWN:
2185 if (view->offset + lines > view->lines)
2186 lines = view->lines - view->offset;
2188 if (lines == 0 || view->offset + view->height >= view->lines) {
2189 report("Cannot scroll beyond the last line");
2190 return;
2191 }
2192 break;
2194 case REQ_SCROLL_PAGE_UP:
2195 lines = view->height;
2196 case REQ_SCROLL_LINE_UP:
2197 if (lines > view->offset)
2198 lines = view->offset;
2200 if (lines == 0) {
2201 report("Cannot scroll beyond the first line");
2202 return;
2203 }
2205 lines = -lines;
2206 break;
2208 default:
2209 die("request %d not handled in switch", request);
2210 }
2212 do_scroll_view(view, lines);
2213 }
2215 /* Cursor moving */
2216 static void
2217 move_view(struct view *view, enum request request)
2218 {
2219 int scroll_steps = 0;
2220 int steps;
2222 switch (request) {
2223 case REQ_MOVE_FIRST_LINE:
2224 steps = -view->lineno;
2225 break;
2227 case REQ_MOVE_LAST_LINE:
2228 steps = view->lines - view->lineno - 1;
2229 break;
2231 case REQ_MOVE_PAGE_UP:
2232 steps = view->height > view->lineno
2233 ? -view->lineno : -view->height;
2234 break;
2236 case REQ_MOVE_PAGE_DOWN:
2237 steps = view->lineno + view->height >= view->lines
2238 ? view->lines - view->lineno - 1 : view->height;
2239 break;
2241 case REQ_MOVE_UP:
2242 steps = -1;
2243 break;
2245 case REQ_MOVE_DOWN:
2246 steps = 1;
2247 break;
2249 default:
2250 die("request %d not handled in switch", request);
2251 }
2253 if (steps <= 0 && view->lineno == 0) {
2254 report("Cannot move beyond the first line");
2255 return;
2257 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2258 report("Cannot move beyond the last line");
2259 return;
2260 }
2262 /* Move the current line */
2263 view->lineno += steps;
2264 assert(0 <= view->lineno && view->lineno < view->lines);
2266 /* Check whether the view needs to be scrolled */
2267 if (view->lineno < view->offset ||
2268 view->lineno >= view->offset + view->height) {
2269 scroll_steps = steps;
2270 if (steps < 0 && -steps > view->offset) {
2271 scroll_steps = -view->offset;
2273 } else if (steps > 0) {
2274 if (view->lineno == view->lines - 1 &&
2275 view->lines > view->height) {
2276 scroll_steps = view->lines - view->offset - 1;
2277 if (scroll_steps >= view->height)
2278 scroll_steps -= view->height - 1;
2279 }
2280 }
2281 }
2283 if (!view_is_displayed(view)) {
2284 view->offset += scroll_steps;
2285 assert(0 <= view->offset && view->offset < view->lines);
2286 view->ops->select(view, &view->line[view->lineno]);
2287 return;
2288 }
2290 /* Repaint the old "current" line if we be scrolling */
2291 if (ABS(steps) < view->height)
2292 draw_view_line(view, view->lineno - steps - view->offset);
2294 if (scroll_steps) {
2295 do_scroll_view(view, scroll_steps);
2296 return;
2297 }
2299 /* Draw the current line */
2300 draw_view_line(view, view->lineno - view->offset);
2302 redrawwin(view->win);
2303 wrefresh(view->win);
2304 report("");
2305 }
2308 /*
2309 * Searching
2310 */
2312 static void search_view(struct view *view, enum request request);
2314 static bool
2315 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2316 {
2317 assert(view_is_displayed(view));
2319 if (!view->ops->grep(view, line))
2320 return FALSE;
2322 if (lineno - view->offset >= view->height) {
2323 view->offset = lineno;
2324 view->lineno = lineno;
2325 redraw_view(view);
2327 } else {
2328 unsigned long old_lineno = view->lineno - view->offset;
2330 view->lineno = lineno;
2331 draw_view_line(view, old_lineno);
2333 draw_view_line(view, view->lineno - view->offset);
2334 redrawwin(view->win);
2335 wrefresh(view->win);
2336 }
2338 report("Line %ld matches '%s'", lineno + 1, view->grep);
2339 return TRUE;
2340 }
2342 static void
2343 find_next(struct view *view, enum request request)
2344 {
2345 unsigned long lineno = view->lineno;
2346 int direction;
2348 if (!*view->grep) {
2349 if (!*opt_search)
2350 report("No previous search");
2351 else
2352 search_view(view, request);
2353 return;
2354 }
2356 switch (request) {
2357 case REQ_SEARCH:
2358 case REQ_FIND_NEXT:
2359 direction = 1;
2360 break;
2362 case REQ_SEARCH_BACK:
2363 case REQ_FIND_PREV:
2364 direction = -1;
2365 break;
2367 default:
2368 return;
2369 }
2371 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2372 lineno += direction;
2374 /* Note, lineno is unsigned long so will wrap around in which case it
2375 * will become bigger than view->lines. */
2376 for (; lineno < view->lines; lineno += direction) {
2377 struct line *line = &view->line[lineno];
2379 if (find_next_line(view, lineno, line))
2380 return;
2381 }
2383 report("No match found for '%s'", view->grep);
2384 }
2386 static void
2387 search_view(struct view *view, enum request request)
2388 {
2389 int regex_err;
2391 if (view->regex) {
2392 regfree(view->regex);
2393 *view->grep = 0;
2394 } else {
2395 view->regex = calloc(1, sizeof(*view->regex));
2396 if (!view->regex)
2397 return;
2398 }
2400 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2401 if (regex_err != 0) {
2402 char buf[SIZEOF_STR] = "unknown error";
2404 regerror(regex_err, view->regex, buf, sizeof(buf));
2405 report("Search failed: %s", buf);
2406 return;
2407 }
2409 string_copy(view->grep, opt_search);
2411 find_next(view, request);
2412 }
2414 /*
2415 * Incremental updating
2416 */
2418 static void
2419 reset_view(struct view *view)
2420 {
2421 int i;
2423 for (i = 0; i < view->lines; i++)
2424 free(view->line[i].data);
2425 free(view->line);
2427 view->line = NULL;
2428 view->offset = 0;
2429 view->lines = 0;
2430 view->lineno = 0;
2431 view->line_size = 0;
2432 view->line_alloc = 0;
2433 view->vid[0] = 0;
2434 }
2436 static void
2437 free_argv(const char *argv[])
2438 {
2439 int argc;
2441 for (argc = 0; argv[argc]; argc++)
2442 free((void *) argv[argc]);
2443 }
2445 static bool
2446 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2447 {
2448 char buf[SIZEOF_STR];
2449 int argc;
2450 bool noreplace = flags == FORMAT_NONE;
2452 free_argv(dst_argv);
2454 for (argc = 0; src_argv[argc]; argc++) {
2455 const char *arg = src_argv[argc];
2456 size_t bufpos = 0;
2458 while (arg) {
2459 char *next = strstr(arg, "%(");
2460 int len = next - arg;
2461 const char *value;
2463 if (!next || noreplace) {
2464 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2465 noreplace = TRUE;
2466 len = strlen(arg);
2467 value = "";
2469 } else if (!prefixcmp(next, "%(directory)")) {
2470 value = opt_path;
2472 } else if (!prefixcmp(next, "%(file)")) {
2473 value = opt_file;
2475 } else if (!prefixcmp(next, "%(ref)")) {
2476 value = *opt_ref ? opt_ref : "HEAD";
2478 } else if (!prefixcmp(next, "%(head)")) {
2479 value = ref_head;
2481 } else if (!prefixcmp(next, "%(commit)")) {
2482 value = ref_commit;
2484 } else if (!prefixcmp(next, "%(blob)")) {
2485 value = ref_blob;
2487 } else {
2488 report("Unknown replacement: `%s`", next);
2489 return FALSE;
2490 }
2492 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2493 return FALSE;
2495 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2496 }
2498 dst_argv[argc] = strdup(buf);
2499 if (!dst_argv[argc])
2500 break;
2501 }
2503 dst_argv[argc] = NULL;
2505 return src_argv[argc] == NULL;
2506 }
2508 static bool
2509 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2510 {
2511 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2512 int bufsize = 0;
2513 int argc;
2515 if (!format_argv(dst_argv, src_argv, flags)) {
2516 free_argv(dst_argv);
2517 return FALSE;
2518 }
2520 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2521 if (bufsize > 0)
2522 dst[bufsize++] = ' ';
2523 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2524 }
2526 if (bufsize < SIZEOF_STR)
2527 dst[bufsize] = 0;
2528 free_argv(dst_argv);
2530 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2531 }
2533 static void
2534 end_update(struct view *view, bool force)
2535 {
2536 if (!view->pipe)
2537 return;
2538 while (!view->ops->read(view, NULL))
2539 if (!force)
2540 return;
2541 set_nonblocking_input(FALSE);
2542 done_io(view->pipe);
2543 view->pipe = NULL;
2544 }
2546 static void
2547 setup_update(struct view *view, const char *vid)
2548 {
2549 set_nonblocking_input(TRUE);
2550 reset_view(view);
2551 string_copy_rev(view->vid, vid);
2552 view->pipe = &view->io;
2553 view->start_time = time(NULL);
2554 }
2556 static bool
2557 prepare_update(struct view *view, const char *argv[], const char *dir,
2558 enum format_flags flags)
2559 {
2560 if (view->pipe)
2561 end_update(view, TRUE);
2562 return init_io_rd(&view->io, argv, dir, flags);
2563 }
2565 static bool
2566 prepare_update_file(struct view *view, const char *name)
2567 {
2568 if (view->pipe)
2569 end_update(view, TRUE);
2570 return init_io_fd(&view->io, fopen(name, "r"));
2571 }
2573 static bool
2574 begin_update(struct view *view, bool refresh)
2575 {
2576 if (refresh) {
2577 if (!start_io(&view->io))
2578 return FALSE;
2580 } else {
2581 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2582 opt_path[0] = 0;
2584 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2585 return FALSE;
2587 /* Put the current ref_* value to the view title ref
2588 * member. This is needed by the blob view. Most other
2589 * views sets it automatically after loading because the
2590 * first line is a commit line. */
2591 string_copy_rev(view->ref, view->id);
2592 }
2594 setup_update(view, view->id);
2596 return TRUE;
2597 }
2599 #define ITEM_CHUNK_SIZE 256
2600 static void *
2601 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2602 {
2603 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2604 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2606 if (mem == NULL || num_chunks != num_chunks_new) {
2607 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2608 mem = realloc(mem, *size * item_size);
2609 }
2611 return mem;
2612 }
2614 static struct line *
2615 realloc_lines(struct view *view, size_t line_size)
2616 {
2617 size_t alloc = view->line_alloc;
2618 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2619 sizeof(*view->line));
2621 if (!tmp)
2622 return NULL;
2624 view->line = tmp;
2625 view->line_alloc = alloc;
2626 view->line_size = line_size;
2627 return view->line;
2628 }
2630 static bool
2631 update_view(struct view *view)
2632 {
2633 char out_buffer[BUFSIZ * 2];
2634 char *line;
2635 /* The number of lines to read. If too low it will cause too much
2636 * redrawing (and possible flickering), if too high responsiveness
2637 * will suffer. */
2638 unsigned long lines = view->height;
2639 int redraw_from = -1;
2641 if (!view->pipe)
2642 return TRUE;
2644 /* Only redraw if lines are visible. */
2645 if (view->offset + view->height >= view->lines)
2646 redraw_from = view->lines - view->offset;
2648 /* FIXME: This is probably not perfect for backgrounded views. */
2649 if (!realloc_lines(view, view->lines + lines))
2650 goto alloc_error;
2652 while ((line = io_gets(view->pipe))) {
2653 size_t linelen = strlen(line);
2655 if (linelen)
2656 line[linelen - 1] = 0;
2658 if (opt_iconv != ICONV_NONE) {
2659 ICONV_CONST char *inbuf = line;
2660 size_t inlen = linelen;
2662 char *outbuf = out_buffer;
2663 size_t outlen = sizeof(out_buffer);
2665 size_t ret;
2667 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2668 if (ret != (size_t) -1) {
2669 line = out_buffer;
2670 linelen = strlen(out_buffer);
2671 }
2672 }
2674 if (!view->ops->read(view, line))
2675 goto alloc_error;
2677 if (lines-- == 1)
2678 break;
2679 }
2681 {
2682 int digits;
2684 lines = view->lines;
2685 for (digits = 0; lines; digits++)
2686 lines /= 10;
2688 /* Keep the displayed view in sync with line number scaling. */
2689 if (digits != view->digits) {
2690 view->digits = digits;
2691 redraw_from = 0;
2692 }
2693 }
2695 if (io_error(view->pipe)) {
2696 report("Failed to read: %s", io_strerror(view->pipe));
2697 end_update(view, TRUE);
2699 } else if (io_eof(view->pipe)) {
2700 report("");
2701 end_update(view, FALSE);
2702 }
2704 if (!view_is_displayed(view))
2705 return TRUE;
2707 if (view == VIEW(REQ_VIEW_TREE)) {
2708 /* Clear the view and redraw everything since the tree sorting
2709 * might have rearranged things. */
2710 redraw_view(view);
2712 } else if (redraw_from >= 0) {
2713 /* If this is an incremental update, redraw the previous line
2714 * since for commits some members could have changed when
2715 * loading the main view. */
2716 if (redraw_from > 0)
2717 redraw_from--;
2719 /* Since revision graph visualization requires knowledge
2720 * about the parent commit, it causes a further one-off
2721 * needed to be redrawn for incremental updates. */
2722 if (redraw_from > 0 && opt_rev_graph)
2723 redraw_from--;
2725 /* Incrementally draw avoids flickering. */
2726 redraw_view_from(view, redraw_from);
2727 }
2729 if (view == VIEW(REQ_VIEW_BLAME))
2730 redraw_view_dirty(view);
2732 /* Update the title _after_ the redraw so that if the redraw picks up a
2733 * commit reference in view->ref it'll be available here. */
2734 update_view_title(view);
2735 return TRUE;
2737 alloc_error:
2738 report("Allocation failure");
2739 end_update(view, TRUE);
2740 return FALSE;
2741 }
2743 static struct line *
2744 add_line_data(struct view *view, void *data, enum line_type type)
2745 {
2746 struct line *line = &view->line[view->lines++];
2748 memset(line, 0, sizeof(*line));
2749 line->type = type;
2750 line->data = data;
2752 return line;
2753 }
2755 static struct line *
2756 add_line_text(struct view *view, const char *text, enum line_type type)
2757 {
2758 char *data = text ? strdup(text) : NULL;
2760 return data ? add_line_data(view, data, type) : NULL;
2761 }
2764 /*
2765 * View opening
2766 */
2768 enum open_flags {
2769 OPEN_DEFAULT = 0, /* Use default view switching. */
2770 OPEN_SPLIT = 1, /* Split current view. */
2771 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2772 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2773 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2774 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2775 OPEN_PREPARED = 32, /* Open already prepared command. */
2776 };
2778 static void
2779 open_view(struct view *prev, enum request request, enum open_flags flags)
2780 {
2781 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2782 bool split = !!(flags & OPEN_SPLIT);
2783 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2784 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2785 struct view *view = VIEW(request);
2786 int nviews = displayed_views();
2787 struct view *base_view = display[0];
2789 if (view == prev && nviews == 1 && !reload) {
2790 report("Already in %s view", view->name);
2791 return;
2792 }
2794 if (view->git_dir && !opt_git_dir[0]) {
2795 report("The %s view is disabled in pager view", view->name);
2796 return;
2797 }
2799 if (split) {
2800 display[1] = view;
2801 if (!backgrounded)
2802 current_view = 1;
2803 } else if (!nomaximize) {
2804 /* Maximize the current view. */
2805 memset(display, 0, sizeof(display));
2806 current_view = 0;
2807 display[current_view] = view;
2808 }
2810 /* Resize the view when switching between split- and full-screen,
2811 * or when switching between two different full-screen views. */
2812 if (nviews != displayed_views() ||
2813 (nviews == 1 && base_view != display[0]))
2814 resize_display();
2816 if (view->pipe)
2817 end_update(view, TRUE);
2819 if (view->ops->open) {
2820 if (!view->ops->open(view)) {
2821 report("Failed to load %s view", view->name);
2822 return;
2823 }
2825 } else if ((reload || strcmp(view->vid, view->id)) &&
2826 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2827 report("Failed to load %s view", view->name);
2828 return;
2829 }
2831 if (split && prev->lineno - prev->offset >= prev->height) {
2832 /* Take the title line into account. */
2833 int lines = prev->lineno - prev->offset - prev->height + 1;
2835 /* Scroll the view that was split if the current line is
2836 * outside the new limited view. */
2837 do_scroll_view(prev, lines);
2838 }
2840 if (prev && view != prev) {
2841 if (split && !backgrounded) {
2842 /* "Blur" the previous view. */
2843 update_view_title(prev);
2844 }
2846 view->parent = prev;
2847 }
2849 if (view->pipe && view->lines == 0) {
2850 /* Clear the old view and let the incremental updating refill
2851 * the screen. */
2852 werase(view->win);
2853 report("");
2854 } else if (view_is_displayed(view)) {
2855 redraw_view(view);
2856 report("");
2857 }
2859 /* If the view is backgrounded the above calls to report()
2860 * won't redraw the view title. */
2861 if (backgrounded)
2862 update_view_title(view);
2863 }
2865 static void
2866 open_external_viewer(const char *argv[], const char *dir)
2867 {
2868 def_prog_mode(); /* save current tty modes */
2869 endwin(); /* restore original tty modes */
2870 run_io_fg(argv, dir);
2871 fprintf(stderr, "Press Enter to continue");
2872 getc(opt_tty);
2873 reset_prog_mode();
2874 redraw_display();
2875 }
2877 static void
2878 open_mergetool(const char *file)
2879 {
2880 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2882 open_external_viewer(mergetool_argv, NULL);
2883 }
2885 static void
2886 open_editor(bool from_root, const char *file)
2887 {
2888 const char *editor_argv[] = { "vi", file, NULL };
2889 const char *editor;
2891 editor = getenv("GIT_EDITOR");
2892 if (!editor && *opt_editor)
2893 editor = opt_editor;
2894 if (!editor)
2895 editor = getenv("VISUAL");
2896 if (!editor)
2897 editor = getenv("EDITOR");
2898 if (!editor)
2899 editor = "vi";
2901 editor_argv[0] = editor;
2902 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2903 }
2905 static void
2906 open_run_request(enum request request)
2907 {
2908 struct run_request *req = get_run_request(request);
2909 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2911 if (!req) {
2912 report("Unknown run request");
2913 return;
2914 }
2916 if (format_argv(argv, req->argv, FORMAT_ALL))
2917 open_external_viewer(argv, NULL);
2918 free_argv(argv);
2919 }
2921 /*
2922 * User request switch noodle
2923 */
2925 static int
2926 view_driver(struct view *view, enum request request)
2927 {
2928 int i;
2930 if (request == REQ_NONE) {
2931 doupdate();
2932 return TRUE;
2933 }
2935 if (request > REQ_NONE) {
2936 open_run_request(request);
2937 /* FIXME: When all views can refresh always do this. */
2938 if (view == VIEW(REQ_VIEW_STATUS) ||
2939 view == VIEW(REQ_VIEW_MAIN) ||
2940 view == VIEW(REQ_VIEW_LOG) ||
2941 view == VIEW(REQ_VIEW_STAGE))
2942 request = REQ_REFRESH;
2943 else
2944 return TRUE;
2945 }
2947 if (view && view->lines) {
2948 request = view->ops->request(view, request, &view->line[view->lineno]);
2949 if (request == REQ_NONE)
2950 return TRUE;
2951 }
2953 switch (request) {
2954 case REQ_MOVE_UP:
2955 case REQ_MOVE_DOWN:
2956 case REQ_MOVE_PAGE_UP:
2957 case REQ_MOVE_PAGE_DOWN:
2958 case REQ_MOVE_FIRST_LINE:
2959 case REQ_MOVE_LAST_LINE:
2960 move_view(view, request);
2961 break;
2963 case REQ_SCROLL_LINE_DOWN:
2964 case REQ_SCROLL_LINE_UP:
2965 case REQ_SCROLL_PAGE_DOWN:
2966 case REQ_SCROLL_PAGE_UP:
2967 scroll_view(view, request);
2968 break;
2970 case REQ_VIEW_BLAME:
2971 if (!opt_file[0]) {
2972 report("No file chosen, press %s to open tree view",
2973 get_key(REQ_VIEW_TREE));
2974 break;
2975 }
2976 open_view(view, request, OPEN_DEFAULT);
2977 break;
2979 case REQ_VIEW_BLOB:
2980 if (!ref_blob[0]) {
2981 report("No file chosen, press %s to open tree view",
2982 get_key(REQ_VIEW_TREE));
2983 break;
2984 }
2985 open_view(view, request, OPEN_DEFAULT);
2986 break;
2988 case REQ_VIEW_PAGER:
2989 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2990 report("No pager content, press %s to run command from prompt",
2991 get_key(REQ_PROMPT));
2992 break;
2993 }
2994 open_view(view, request, OPEN_DEFAULT);
2995 break;
2997 case REQ_VIEW_STAGE:
2998 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2999 report("No stage content, press %s to open the status view and choose file",
3000 get_key(REQ_VIEW_STATUS));
3001 break;
3002 }
3003 open_view(view, request, OPEN_DEFAULT);
3004 break;
3006 case REQ_VIEW_STATUS:
3007 if (opt_is_inside_work_tree == FALSE) {
3008 report("The status view requires a working tree");
3009 break;
3010 }
3011 open_view(view, request, OPEN_DEFAULT);
3012 break;
3014 case REQ_VIEW_MAIN:
3015 case REQ_VIEW_DIFF:
3016 case REQ_VIEW_LOG:
3017 case REQ_VIEW_TREE:
3018 case REQ_VIEW_HELP:
3019 open_view(view, request, OPEN_DEFAULT);
3020 break;
3022 case REQ_NEXT:
3023 case REQ_PREVIOUS:
3024 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3026 if ((view == VIEW(REQ_VIEW_DIFF) &&
3027 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3028 (view == VIEW(REQ_VIEW_DIFF) &&
3029 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3030 (view == VIEW(REQ_VIEW_STAGE) &&
3031 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3032 (view == VIEW(REQ_VIEW_BLOB) &&
3033 view->parent == VIEW(REQ_VIEW_TREE))) {
3034 int line;
3036 view = view->parent;
3037 line = view->lineno;
3038 move_view(view, request);
3039 if (view_is_displayed(view))
3040 update_view_title(view);
3041 if (line != view->lineno)
3042 view->ops->request(view, REQ_ENTER,
3043 &view->line[view->lineno]);
3045 } else {
3046 move_view(view, request);
3047 }
3048 break;
3050 case REQ_VIEW_NEXT:
3051 {
3052 int nviews = displayed_views();
3053 int next_view = (current_view + 1) % nviews;
3055 if (next_view == current_view) {
3056 report("Only one view is displayed");
3057 break;
3058 }
3060 current_view = next_view;
3061 /* Blur out the title of the previous view. */
3062 update_view_title(view);
3063 report("");
3064 break;
3065 }
3066 case REQ_REFRESH:
3067 report("Refreshing is not yet supported for the %s view", view->name);
3068 break;
3070 case REQ_MAXIMIZE:
3071 if (displayed_views() == 2)
3072 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3073 break;
3075 case REQ_TOGGLE_LINENO:
3076 opt_line_number = !opt_line_number;
3077 redraw_display();
3078 break;
3080 case REQ_TOGGLE_DATE:
3081 opt_date = !opt_date;
3082 redraw_display();
3083 break;
3085 case REQ_TOGGLE_AUTHOR:
3086 opt_author = !opt_author;
3087 redraw_display();
3088 break;
3090 case REQ_TOGGLE_REV_GRAPH:
3091 opt_rev_graph = !opt_rev_graph;
3092 redraw_display();
3093 break;
3095 case REQ_TOGGLE_REFS:
3096 opt_show_refs = !opt_show_refs;
3097 redraw_display();
3098 break;
3100 case REQ_SEARCH:
3101 case REQ_SEARCH_BACK:
3102 search_view(view, request);
3103 break;
3105 case REQ_FIND_NEXT:
3106 case REQ_FIND_PREV:
3107 find_next(view, request);
3108 break;
3110 case REQ_STOP_LOADING:
3111 for (i = 0; i < ARRAY_SIZE(views); i++) {
3112 view = &views[i];
3113 if (view->pipe)
3114 report("Stopped loading the %s view", view->name),
3115 end_update(view, TRUE);
3116 }
3117 break;
3119 case REQ_SHOW_VERSION:
3120 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3121 return TRUE;
3123 case REQ_SCREEN_RESIZE:
3124 resize_display();
3125 /* Fall-through */
3126 case REQ_SCREEN_REDRAW:
3127 redraw_display();
3128 break;
3130 case REQ_EDIT:
3131 report("Nothing to edit");
3132 break;
3134 case REQ_ENTER:
3135 report("Nothing to enter");
3136 break;
3138 case REQ_VIEW_CLOSE:
3139 /* XXX: Mark closed views by letting view->parent point to the
3140 * view itself. Parents to closed view should never be
3141 * followed. */
3142 if (view->parent &&
3143 view->parent->parent != view->parent) {
3144 memset(display, 0, sizeof(display));
3145 current_view = 0;
3146 display[current_view] = view->parent;
3147 view->parent = view;
3148 resize_display();
3149 redraw_display();
3150 report("");
3151 break;
3152 }
3153 /* Fall-through */
3154 case REQ_QUIT:
3155 return FALSE;
3157 default:
3158 report("Unknown key, press 'h' for help");
3159 return TRUE;
3160 }
3162 return TRUE;
3163 }
3166 /*
3167 * Pager backend
3168 */
3170 static bool
3171 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3172 {
3173 char *text = line->data;
3175 if (opt_line_number && draw_lineno(view, lineno))
3176 return TRUE;
3178 draw_text(view, line->type, text, TRUE);
3179 return TRUE;
3180 }
3182 static bool
3183 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3184 {
3185 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3186 char refbuf[SIZEOF_STR];
3187 char *ref = NULL;
3189 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3190 ref = chomp_string(refbuf);
3192 if (!ref || !*ref)
3193 return TRUE;
3195 /* This is the only fatal call, since it can "corrupt" the buffer. */
3196 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3197 return FALSE;
3199 return TRUE;
3200 }
3202 static void
3203 add_pager_refs(struct view *view, struct line *line)
3204 {
3205 char buf[SIZEOF_STR];
3206 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3207 struct ref **refs;
3208 size_t bufpos = 0, refpos = 0;
3209 const char *sep = "Refs: ";
3210 bool is_tag = FALSE;
3212 assert(line->type == LINE_COMMIT);
3214 refs = get_refs(commit_id);
3215 if (!refs) {
3216 if (view == VIEW(REQ_VIEW_DIFF))
3217 goto try_add_describe_ref;
3218 return;
3219 }
3221 do {
3222 struct ref *ref = refs[refpos];
3223 const char *fmt = ref->tag ? "%s[%s]" :
3224 ref->remote ? "%s<%s>" : "%s%s";
3226 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3227 return;
3228 sep = ", ";
3229 if (ref->tag)
3230 is_tag = TRUE;
3231 } while (refs[refpos++]->next);
3233 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3234 try_add_describe_ref:
3235 /* Add <tag>-g<commit_id> "fake" reference. */
3236 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3237 return;
3238 }
3240 if (bufpos == 0)
3241 return;
3243 if (!realloc_lines(view, view->line_size + 1))
3244 return;
3246 add_line_text(view, buf, LINE_PP_REFS);
3247 }
3249 static bool
3250 pager_read(struct view *view, char *data)
3251 {
3252 struct line *line;
3254 if (!data)
3255 return TRUE;
3257 line = add_line_text(view, data, get_line_type(data));
3258 if (!line)
3259 return FALSE;
3261 if (line->type == LINE_COMMIT &&
3262 (view == VIEW(REQ_VIEW_DIFF) ||
3263 view == VIEW(REQ_VIEW_LOG)))
3264 add_pager_refs(view, line);
3266 return TRUE;
3267 }
3269 static enum request
3270 pager_request(struct view *view, enum request request, struct line *line)
3271 {
3272 int split = 0;
3274 if (request != REQ_ENTER)
3275 return request;
3277 if (line->type == LINE_COMMIT &&
3278 (view == VIEW(REQ_VIEW_LOG) ||
3279 view == VIEW(REQ_VIEW_PAGER))) {
3280 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3281 split = 1;
3282 }
3284 /* Always scroll the view even if it was split. That way
3285 * you can use Enter to scroll through the log view and
3286 * split open each commit diff. */
3287 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3289 /* FIXME: A minor workaround. Scrolling the view will call report("")
3290 * but if we are scrolling a non-current view this won't properly
3291 * update the view title. */
3292 if (split)
3293 update_view_title(view);
3295 return REQ_NONE;
3296 }
3298 static bool
3299 pager_grep(struct view *view, struct line *line)
3300 {
3301 regmatch_t pmatch;
3302 char *text = line->data;
3304 if (!*text)
3305 return FALSE;
3307 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3308 return FALSE;
3310 return TRUE;
3311 }
3313 static void
3314 pager_select(struct view *view, struct line *line)
3315 {
3316 if (line->type == LINE_COMMIT) {
3317 char *text = (char *)line->data + STRING_SIZE("commit ");
3319 if (view != VIEW(REQ_VIEW_PAGER))
3320 string_copy_rev(view->ref, text);
3321 string_copy_rev(ref_commit, text);
3322 }
3323 }
3325 static struct view_ops pager_ops = {
3326 "line",
3327 NULL,
3328 NULL,
3329 pager_read,
3330 pager_draw,
3331 pager_request,
3332 pager_grep,
3333 pager_select,
3334 };
3336 static const char *log_argv[SIZEOF_ARG] = {
3337 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3338 };
3340 static enum request
3341 log_request(struct view *view, enum request request, struct line *line)
3342 {
3343 switch (request) {
3344 case REQ_REFRESH:
3345 load_refs();
3346 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3347 return REQ_NONE;
3348 default:
3349 return pager_request(view, request, line);
3350 }
3351 }
3353 static struct view_ops log_ops = {
3354 "line",
3355 log_argv,
3356 NULL,
3357 pager_read,
3358 pager_draw,
3359 log_request,
3360 pager_grep,
3361 pager_select,
3362 };
3364 static const char *diff_argv[SIZEOF_ARG] = {
3365 "git", "show", "--pretty=fuller", "--no-color", "--root",
3366 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3367 };
3369 static struct view_ops diff_ops = {
3370 "line",
3371 diff_argv,
3372 NULL,
3373 pager_read,
3374 pager_draw,
3375 pager_request,
3376 pager_grep,
3377 pager_select,
3378 };
3380 /*
3381 * Help backend
3382 */
3384 static bool
3385 help_open(struct view *view)
3386 {
3387 char buf[BUFSIZ];
3388 int lines = ARRAY_SIZE(req_info) + 2;
3389 int i;
3391 if (view->lines > 0)
3392 return TRUE;
3394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3395 if (!req_info[i].request)
3396 lines++;
3398 lines += run_requests + 1;
3400 view->line = calloc(lines, sizeof(*view->line));
3401 if (!view->line)
3402 return FALSE;
3404 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3406 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3407 const char *key;
3409 if (req_info[i].request == REQ_NONE)
3410 continue;
3412 if (!req_info[i].request) {
3413 add_line_text(view, "", LINE_DEFAULT);
3414 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3415 continue;
3416 }
3418 key = get_key(req_info[i].request);
3419 if (!*key)
3420 key = "(no key defined)";
3422 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3423 continue;
3425 add_line_text(view, buf, LINE_DEFAULT);
3426 }
3428 if (run_requests) {
3429 add_line_text(view, "", LINE_DEFAULT);
3430 add_line_text(view, "External commands:", LINE_DEFAULT);
3431 }
3433 for (i = 0; i < run_requests; i++) {
3434 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3435 const char *key;
3436 char cmd[SIZEOF_STR];
3437 size_t bufpos;
3438 int argc;
3440 if (!req)
3441 continue;
3443 key = get_key_name(req->key);
3444 if (!*key)
3445 key = "(no key defined)";
3447 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3448 if (!string_format_from(cmd, &bufpos, "%s%s",
3449 argc ? " " : "", req->argv[argc]))
3450 return REQ_NONE;
3452 if (!string_format(buf, " %-10s %-14s `%s`",
3453 keymap_table[req->keymap].name, key, cmd))
3454 continue;
3456 add_line_text(view, buf, LINE_DEFAULT);
3457 }
3459 return TRUE;
3460 }
3462 static struct view_ops help_ops = {
3463 "line",
3464 NULL,
3465 help_open,
3466 NULL,
3467 pager_draw,
3468 pager_request,
3469 pager_grep,
3470 pager_select,
3471 };
3474 /*
3475 * Tree backend
3476 */
3478 struct tree_stack_entry {
3479 struct tree_stack_entry *prev; /* Entry below this in the stack */
3480 unsigned long lineno; /* Line number to restore */
3481 char *name; /* Position of name in opt_path */
3482 };
3484 /* The top of the path stack. */
3485 static struct tree_stack_entry *tree_stack = NULL;
3486 unsigned long tree_lineno = 0;
3488 static void
3489 pop_tree_stack_entry(void)
3490 {
3491 struct tree_stack_entry *entry = tree_stack;
3493 tree_lineno = entry->lineno;
3494 entry->name[0] = 0;
3495 tree_stack = entry->prev;
3496 free(entry);
3497 }
3499 static void
3500 push_tree_stack_entry(const char *name, unsigned long lineno)
3501 {
3502 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3503 size_t pathlen = strlen(opt_path);
3505 if (!entry)
3506 return;
3508 entry->prev = tree_stack;
3509 entry->name = opt_path + pathlen;
3510 tree_stack = entry;
3512 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3513 pop_tree_stack_entry();
3514 return;
3515 }
3517 /* Move the current line to the first tree entry. */
3518 tree_lineno = 1;
3519 entry->lineno = lineno;
3520 }
3522 /* Parse output from git-ls-tree(1):
3523 *
3524 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3525 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3526 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3527 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3528 */
3530 #define SIZEOF_TREE_ATTR \
3531 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3533 #define TREE_UP_FORMAT "040000 tree %s\t.."
3535 static int
3536 tree_compare_entry(enum line_type type1, const char *name1,
3537 enum line_type type2, const char *name2)
3538 {
3539 if (type1 != type2) {
3540 if (type1 == LINE_TREE_DIR)
3541 return -1;
3542 return 1;
3543 }
3545 return strcmp(name1, name2);
3546 }
3548 static const char *
3549 tree_path(struct line *line)
3550 {
3551 const char *path = line->data;
3553 return path + SIZEOF_TREE_ATTR;
3554 }
3556 static bool
3557 tree_read(struct view *view, char *text)
3558 {
3559 size_t textlen = text ? strlen(text) : 0;
3560 char buf[SIZEOF_STR];
3561 unsigned long pos;
3562 enum line_type type;
3563 bool first_read = view->lines == 0;
3565 if (!text)
3566 return TRUE;
3567 if (textlen <= SIZEOF_TREE_ATTR)
3568 return FALSE;
3570 type = text[STRING_SIZE("100644 ")] == 't'
3571 ? LINE_TREE_DIR : LINE_TREE_FILE;
3573 if (first_read) {
3574 /* Add path info line */
3575 if (!string_format(buf, "Directory path /%s", opt_path) ||
3576 !realloc_lines(view, view->line_size + 1) ||
3577 !add_line_text(view, buf, LINE_DEFAULT))
3578 return FALSE;
3580 /* Insert "link" to parent directory. */
3581 if (*opt_path) {
3582 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3583 !realloc_lines(view, view->line_size + 1) ||
3584 !add_line_text(view, buf, LINE_TREE_DIR))
3585 return FALSE;
3586 }
3587 }
3589 /* Strip the path part ... */
3590 if (*opt_path) {
3591 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3592 size_t striplen = strlen(opt_path);
3593 char *path = text + SIZEOF_TREE_ATTR;
3595 if (pathlen > striplen)
3596 memmove(path, path + striplen,
3597 pathlen - striplen + 1);
3598 }
3600 /* Skip "Directory ..." and ".." line. */
3601 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3602 struct line *line = &view->line[pos];
3603 const char *path1 = tree_path(line);
3604 char *path2 = text + SIZEOF_TREE_ATTR;
3605 int cmp = tree_compare_entry(line->type, path1, type, path2);
3607 if (cmp <= 0)
3608 continue;
3610 text = strdup(text);
3611 if (!text)
3612 return FALSE;
3614 if (view->lines > pos)
3615 memmove(&view->line[pos + 1], &view->line[pos],
3616 (view->lines - pos) * sizeof(*line));
3618 line = &view->line[pos];
3619 line->data = text;
3620 line->type = type;
3621 view->lines++;
3622 return TRUE;
3623 }
3625 if (!add_line_text(view, text, type))
3626 return FALSE;
3628 if (tree_lineno > view->lineno) {
3629 view->lineno = tree_lineno;
3630 tree_lineno = 0;
3631 }
3633 return TRUE;
3634 }
3636 static enum request
3637 tree_request(struct view *view, enum request request, struct line *line)
3638 {
3639 enum open_flags flags;
3641 switch (request) {
3642 case REQ_VIEW_BLAME:
3643 if (line->type != LINE_TREE_FILE) {
3644 report("Blame only supported for files");
3645 return REQ_NONE;
3646 }
3648 string_copy(opt_ref, view->vid);
3649 return request;
3651 case REQ_EDIT:
3652 if (line->type != LINE_TREE_FILE) {
3653 report("Edit only supported for files");
3654 } else if (!is_head_commit(view->vid)) {
3655 report("Edit only supported for files in the current work tree");
3656 } else {
3657 open_editor(TRUE, opt_file);
3658 }
3659 return REQ_NONE;
3661 case REQ_TREE_PARENT:
3662 if (!*opt_path) {
3663 /* quit view if at top of tree */
3664 return REQ_VIEW_CLOSE;
3665 }
3666 /* fake 'cd ..' */
3667 line = &view->line[1];
3668 break;
3670 case REQ_ENTER:
3671 break;
3673 default:
3674 return request;
3675 }
3677 /* Cleanup the stack if the tree view is at a different tree. */
3678 while (!*opt_path && tree_stack)
3679 pop_tree_stack_entry();
3681 switch (line->type) {
3682 case LINE_TREE_DIR:
3683 /* Depending on whether it is a subdir or parent (updir?) link
3684 * mangle the path buffer. */
3685 if (line == &view->line[1] && *opt_path) {
3686 pop_tree_stack_entry();
3688 } else {
3689 const char *basename = tree_path(line);
3691 push_tree_stack_entry(basename, view->lineno);
3692 }
3694 /* Trees and subtrees share the same ID, so they are not not
3695 * unique like blobs. */
3696 flags = OPEN_RELOAD;
3697 request = REQ_VIEW_TREE;
3698 break;
3700 case LINE_TREE_FILE:
3701 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3702 request = REQ_VIEW_BLOB;
3703 break;
3705 default:
3706 return TRUE;
3707 }
3709 open_view(view, request, flags);
3710 if (request == REQ_VIEW_TREE) {
3711 view->lineno = tree_lineno;
3712 }
3714 return REQ_NONE;
3715 }
3717 static void
3718 tree_select(struct view *view, struct line *line)
3719 {
3720 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3722 if (line->type == LINE_TREE_FILE) {
3723 string_copy_rev(ref_blob, text);
3724 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3726 } else if (line->type != LINE_TREE_DIR) {
3727 return;
3728 }
3730 string_copy_rev(view->ref, text);
3731 }
3733 static const char *tree_argv[SIZEOF_ARG] = {
3734 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3735 };
3737 static struct view_ops tree_ops = {
3738 "file",
3739 tree_argv,
3740 NULL,
3741 tree_read,
3742 pager_draw,
3743 tree_request,
3744 pager_grep,
3745 tree_select,
3746 };
3748 static bool
3749 blob_read(struct view *view, char *line)
3750 {
3751 if (!line)
3752 return TRUE;
3753 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3754 }
3756 static const char *blob_argv[SIZEOF_ARG] = {
3757 "git", "cat-file", "blob", "%(blob)", NULL
3758 };
3760 static struct view_ops blob_ops = {
3761 "line",
3762 blob_argv,
3763 NULL,
3764 blob_read,
3765 pager_draw,
3766 pager_request,
3767 pager_grep,
3768 pager_select,
3769 };
3771 /*
3772 * Blame backend
3773 *
3774 * Loading the blame view is a two phase job:
3775 *
3776 * 1. File content is read either using opt_file from the
3777 * filesystem or using git-cat-file.
3778 * 2. Then blame information is incrementally added by
3779 * reading output from git-blame.
3780 */
3782 static const char *blame_head_argv[] = {
3783 "git", "blame", "--incremental", "--", "%(file)", NULL
3784 };
3786 static const char *blame_ref_argv[] = {
3787 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3788 };
3790 static const char *blame_cat_file_argv[] = {
3791 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3792 };
3794 struct blame_commit {
3795 char id[SIZEOF_REV]; /* SHA1 ID. */
3796 char title[128]; /* First line of the commit message. */
3797 char author[75]; /* Author of the commit. */
3798 struct tm time; /* Date from the author ident. */
3799 char filename[128]; /* Name of file. */
3800 };
3802 struct blame {
3803 struct blame_commit *commit;
3804 char text[1];
3805 };
3807 static bool
3808 blame_open(struct view *view)
3809 {
3810 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3811 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3812 return FALSE;
3813 }
3815 setup_update(view, opt_file);
3816 string_format(view->ref, "%s ...", opt_file);
3818 return TRUE;
3819 }
3821 static struct blame_commit *
3822 get_blame_commit(struct view *view, const char *id)
3823 {
3824 size_t i;
3826 for (i = 0; i < view->lines; i++) {
3827 struct blame *blame = view->line[i].data;
3829 if (!blame->commit)
3830 continue;
3832 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3833 return blame->commit;
3834 }
3836 {
3837 struct blame_commit *commit = calloc(1, sizeof(*commit));
3839 if (commit)
3840 string_ncopy(commit->id, id, SIZEOF_REV);
3841 return commit;
3842 }
3843 }
3845 static bool
3846 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3847 {
3848 const char *pos = *posref;
3850 *posref = NULL;
3851 pos = strchr(pos + 1, ' ');
3852 if (!pos || !isdigit(pos[1]))
3853 return FALSE;
3854 *number = atoi(pos + 1);
3855 if (*number < min || *number > max)
3856 return FALSE;
3858 *posref = pos;
3859 return TRUE;
3860 }
3862 static struct blame_commit *
3863 parse_blame_commit(struct view *view, const char *text, int *blamed)
3864 {
3865 struct blame_commit *commit;
3866 struct blame *blame;
3867 const char *pos = text + SIZEOF_REV - 1;
3868 size_t lineno;
3869 size_t group;
3871 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3872 return NULL;
3874 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3875 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3876 return NULL;
3878 commit = get_blame_commit(view, text);
3879 if (!commit)
3880 return NULL;
3882 *blamed += group;
3883 while (group--) {
3884 struct line *line = &view->line[lineno + group - 1];
3886 blame = line->data;
3887 blame->commit = commit;
3888 line->dirty = 1;
3889 }
3891 return commit;
3892 }
3894 static bool
3895 blame_read_file(struct view *view, const char *line, bool *read_file)
3896 {
3897 if (!line) {
3898 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3899 struct io io = {};
3901 if (view->lines == 0 && !view->parent)
3902 die("No blame exist for %s", view->vid);
3904 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3905 report("Failed to load blame data");
3906 return TRUE;
3907 }
3909 done_io(view->pipe);
3910 view->io = io;
3911 *read_file = FALSE;
3912 return FALSE;
3914 } else {
3915 size_t linelen = strlen(line);
3916 struct blame *blame = malloc(sizeof(*blame) + linelen);
3918 blame->commit = NULL;
3919 strncpy(blame->text, line, linelen);
3920 blame->text[linelen] = 0;
3921 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3922 }
3923 }
3925 static bool
3926 match_blame_header(const char *name, char **line)
3927 {
3928 size_t namelen = strlen(name);
3929 bool matched = !strncmp(name, *line, namelen);
3931 if (matched)
3932 *line += namelen;
3934 return matched;
3935 }
3937 static bool
3938 blame_read(struct view *view, char *line)
3939 {
3940 static struct blame_commit *commit = NULL;
3941 static int blamed = 0;
3942 static time_t author_time;
3943 static bool read_file = TRUE;
3945 if (read_file)
3946 return blame_read_file(view, line, &read_file);
3948 if (!line) {
3949 /* Reset all! */
3950 commit = NULL;
3951 blamed = 0;
3952 read_file = TRUE;
3953 string_format(view->ref, "%s", view->vid);
3954 if (view_is_displayed(view)) {
3955 update_view_title(view);
3956 redraw_view_from(view, 0);
3957 }
3958 return TRUE;
3959 }
3961 if (!commit) {
3962 commit = parse_blame_commit(view, line, &blamed);
3963 string_format(view->ref, "%s %2d%%", view->vid,
3964 blamed * 100 / view->lines);
3966 } else if (match_blame_header("author ", &line)) {
3967 string_ncopy(commit->author, line, strlen(line));
3969 } else if (match_blame_header("author-time ", &line)) {
3970 author_time = (time_t) atol(line);
3972 } else if (match_blame_header("author-tz ", &line)) {
3973 long tz;
3975 tz = ('0' - line[1]) * 60 * 60 * 10;
3976 tz += ('0' - line[2]) * 60 * 60;
3977 tz += ('0' - line[3]) * 60;
3978 tz += ('0' - line[4]) * 60;
3980 if (line[0] == '-')
3981 tz = -tz;
3983 author_time -= tz;
3984 gmtime_r(&author_time, &commit->time);
3986 } else if (match_blame_header("summary ", &line)) {
3987 string_ncopy(commit->title, line, strlen(line));
3989 } else if (match_blame_header("filename ", &line)) {
3990 string_ncopy(commit->filename, line, strlen(line));
3991 commit = NULL;
3992 }
3994 return TRUE;
3995 }
3997 static bool
3998 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3999 {
4000 struct blame *blame = line->data;
4001 struct tm *time = NULL;
4002 const char *id = NULL, *author = NULL;
4004 if (blame->commit && *blame->commit->filename) {
4005 id = blame->commit->id;
4006 author = blame->commit->author;
4007 time = &blame->commit->time;
4008 }
4010 if (opt_date && draw_date(view, time))
4011 return TRUE;
4013 if (opt_author &&
4014 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4015 return TRUE;
4017 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4018 return TRUE;
4020 if (draw_lineno(view, lineno))
4021 return TRUE;
4023 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4024 return TRUE;
4025 }
4027 static enum request
4028 blame_request(struct view *view, enum request request, struct line *line)
4029 {
4030 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4031 struct blame *blame = line->data;
4033 switch (request) {
4034 case REQ_VIEW_BLAME:
4035 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4036 report("Commit ID unknown");
4037 break;
4038 }
4039 string_copy(opt_ref, blame->commit->id);
4040 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4041 return request;
4043 case REQ_ENTER:
4044 if (!blame->commit) {
4045 report("No commit loaded yet");
4046 break;
4047 }
4049 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4050 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4051 break;
4053 if (!strcmp(blame->commit->id, NULL_ID)) {
4054 struct view *diff = VIEW(REQ_VIEW_DIFF);
4055 const char *diff_index_argv[] = {
4056 "git", "diff-index", "--root", "--cached",
4057 "--patch-with-stat", "-C", "-M",
4058 "HEAD", "--", view->vid, NULL
4059 };
4061 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4062 report("Failed to allocate diff command");
4063 break;
4064 }
4065 flags |= OPEN_PREPARED;
4066 }
4068 open_view(view, REQ_VIEW_DIFF, flags);
4069 break;
4071 default:
4072 return request;
4073 }
4075 return REQ_NONE;
4076 }
4078 static bool
4079 blame_grep(struct view *view, struct line *line)
4080 {
4081 struct blame *blame = line->data;
4082 struct blame_commit *commit = blame->commit;
4083 regmatch_t pmatch;
4085 #define MATCH(text, on) \
4086 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4088 if (commit) {
4089 char buf[DATE_COLS + 1];
4091 if (MATCH(commit->title, 1) ||
4092 MATCH(commit->author, opt_author) ||
4093 MATCH(commit->id, opt_date))
4094 return TRUE;
4096 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4097 MATCH(buf, 1))
4098 return TRUE;
4099 }
4101 return MATCH(blame->text, 1);
4103 #undef MATCH
4104 }
4106 static void
4107 blame_select(struct view *view, struct line *line)
4108 {
4109 struct blame *blame = line->data;
4110 struct blame_commit *commit = blame->commit;
4112 if (!commit)
4113 return;
4115 if (!strcmp(commit->id, NULL_ID))
4116 string_ncopy(ref_commit, "HEAD", 4);
4117 else
4118 string_copy_rev(ref_commit, commit->id);
4119 }
4121 static struct view_ops blame_ops = {
4122 "line",
4123 NULL,
4124 blame_open,
4125 blame_read,
4126 blame_draw,
4127 blame_request,
4128 blame_grep,
4129 blame_select,
4130 };
4132 /*
4133 * Status backend
4134 */
4136 struct status {
4137 char status;
4138 struct {
4139 mode_t mode;
4140 char rev[SIZEOF_REV];
4141 char name[SIZEOF_STR];
4142 } old;
4143 struct {
4144 mode_t mode;
4145 char rev[SIZEOF_REV];
4146 char name[SIZEOF_STR];
4147 } new;
4148 };
4150 static char status_onbranch[SIZEOF_STR];
4151 static struct status stage_status;
4152 static enum line_type stage_line_type;
4153 static size_t stage_chunks;
4154 static int *stage_chunk;
4156 /* This should work even for the "On branch" line. */
4157 static inline bool
4158 status_has_none(struct view *view, struct line *line)
4159 {
4160 return line < view->line + view->lines && !line[1].data;
4161 }
4163 /* Get fields from the diff line:
4164 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4165 */
4166 static inline bool
4167 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4168 {
4169 const char *old_mode = buf + 1;
4170 const char *new_mode = buf + 8;
4171 const char *old_rev = buf + 15;
4172 const char *new_rev = buf + 56;
4173 const char *status = buf + 97;
4175 if (bufsize < 99 ||
4176 old_mode[-1] != ':' ||
4177 new_mode[-1] != ' ' ||
4178 old_rev[-1] != ' ' ||
4179 new_rev[-1] != ' ' ||
4180 status[-1] != ' ')
4181 return FALSE;
4183 file->status = *status;
4185 string_copy_rev(file->old.rev, old_rev);
4186 string_copy_rev(file->new.rev, new_rev);
4188 file->old.mode = strtoul(old_mode, NULL, 8);
4189 file->new.mode = strtoul(new_mode, NULL, 8);
4191 file->old.name[0] = file->new.name[0] = 0;
4193 return TRUE;
4194 }
4196 static bool
4197 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4198 {
4199 struct status *file = NULL;
4200 struct status *unmerged = NULL;
4201 char buf[SIZEOF_STR * 4];
4202 size_t bufsize = 0;
4203 struct io io = {};
4205 if (!run_io(&io, argv, NULL, IO_RD))
4206 return FALSE;
4208 add_line_data(view, NULL, type);
4210 while (!io_eof(&io)) {
4211 char *sep;
4212 size_t readsize;
4214 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4215 if (io_error(&io))
4216 break;
4217 bufsize += readsize;
4219 /* Process while we have NUL chars. */
4220 while ((sep = memchr(buf, 0, bufsize))) {
4221 size_t sepsize = sep - buf + 1;
4223 if (!file) {
4224 if (!realloc_lines(view, view->line_size + 1))
4225 goto error_out;
4227 file = calloc(1, sizeof(*file));
4228 if (!file)
4229 goto error_out;
4231 add_line_data(view, file, type);
4232 }
4234 /* Parse diff info part. */
4235 if (status) {
4236 file->status = status;
4237 if (status == 'A')
4238 string_copy(file->old.rev, NULL_ID);
4240 } else if (!file->status) {
4241 if (!status_get_diff(file, buf, sepsize))
4242 goto error_out;
4244 bufsize -= sepsize;
4245 memmove(buf, sep + 1, bufsize);
4247 sep = memchr(buf, 0, bufsize);
4248 if (!sep)
4249 break;
4250 sepsize = sep - buf + 1;
4252 /* Collapse all 'M'odified entries that
4253 * follow a associated 'U'nmerged entry.
4254 */
4255 if (file->status == 'U') {
4256 unmerged = file;
4258 } else if (unmerged) {
4259 int collapse = !strcmp(buf, unmerged->new.name);
4261 unmerged = NULL;
4262 if (collapse) {
4263 free(file);
4264 view->lines--;
4265 continue;
4266 }
4267 }
4268 }
4270 /* Grab the old name for rename/copy. */
4271 if (!*file->old.name &&
4272 (file->status == 'R' || file->status == 'C')) {
4273 sepsize = sep - buf + 1;
4274 string_ncopy(file->old.name, buf, sepsize);
4275 bufsize -= sepsize;
4276 memmove(buf, sep + 1, bufsize);
4278 sep = memchr(buf, 0, bufsize);
4279 if (!sep)
4280 break;
4281 sepsize = sep - buf + 1;
4282 }
4284 /* git-ls-files just delivers a NUL separated
4285 * list of file names similar to the second half
4286 * of the git-diff-* output. */
4287 string_ncopy(file->new.name, buf, sepsize);
4288 if (!*file->old.name)
4289 string_copy(file->old.name, file->new.name);
4290 bufsize -= sepsize;
4291 memmove(buf, sep + 1, bufsize);
4292 file = NULL;
4293 }
4294 }
4296 if (io_error(&io)) {
4297 error_out:
4298 done_io(&io);
4299 return FALSE;
4300 }
4302 if (!view->line[view->lines - 1].data)
4303 add_line_data(view, NULL, LINE_STAT_NONE);
4305 done_io(&io);
4306 return TRUE;
4307 }
4309 /* Don't show unmerged entries in the staged section. */
4310 static const char *status_diff_index_argv[] = {
4311 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4312 "--cached", "-M", "HEAD", NULL
4313 };
4315 static const char *status_diff_files_argv[] = {
4316 "git", "diff-files", "-z", NULL
4317 };
4319 static const char *status_list_other_argv[] = {
4320 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4321 };
4323 static const char *status_list_no_head_argv[] = {
4324 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4325 };
4327 static const char *update_index_argv[] = {
4328 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4329 };
4331 /* First parse staged info using git-diff-index(1), then parse unstaged
4332 * info using git-diff-files(1), and finally untracked files using
4333 * git-ls-files(1). */
4334 static bool
4335 status_open(struct view *view)
4336 {
4337 unsigned long prev_lineno = view->lineno;
4339 reset_view(view);
4341 if (!realloc_lines(view, view->line_size + 7))
4342 return FALSE;
4344 add_line_data(view, NULL, LINE_STAT_HEAD);
4345 if (is_initial_commit())
4346 string_copy(status_onbranch, "Initial commit");
4347 else if (!*opt_head)
4348 string_copy(status_onbranch, "Not currently on any branch");
4349 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4350 return FALSE;
4352 run_io_bg(update_index_argv);
4354 if (is_initial_commit()) {
4355 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4356 return FALSE;
4357 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4358 return FALSE;
4359 }
4361 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4362 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4363 return FALSE;
4365 /* If all went well restore the previous line number to stay in
4366 * the context or select a line with something that can be
4367 * updated. */
4368 if (prev_lineno >= view->lines)
4369 prev_lineno = view->lines - 1;
4370 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4371 prev_lineno++;
4372 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4373 prev_lineno--;
4375 /* If the above fails, always skip the "On branch" line. */
4376 if (prev_lineno < view->lines)
4377 view->lineno = prev_lineno;
4378 else
4379 view->lineno = 1;
4381 if (view->lineno < view->offset)
4382 view->offset = view->lineno;
4383 else if (view->offset + view->height <= view->lineno)
4384 view->offset = view->lineno - view->height + 1;
4386 return TRUE;
4387 }
4389 static bool
4390 status_draw(struct view *view, struct line *line, unsigned int lineno)
4391 {
4392 struct status *status = line->data;
4393 enum line_type type;
4394 const char *text;
4396 if (!status) {
4397 switch (line->type) {
4398 case LINE_STAT_STAGED:
4399 type = LINE_STAT_SECTION;
4400 text = "Changes to be committed:";
4401 break;
4403 case LINE_STAT_UNSTAGED:
4404 type = LINE_STAT_SECTION;
4405 text = "Changed but not updated:";
4406 break;
4408 case LINE_STAT_UNTRACKED:
4409 type = LINE_STAT_SECTION;
4410 text = "Untracked files:";
4411 break;
4413 case LINE_STAT_NONE:
4414 type = LINE_DEFAULT;
4415 text = " (no files)";
4416 break;
4418 case LINE_STAT_HEAD:
4419 type = LINE_STAT_HEAD;
4420 text = status_onbranch;
4421 break;
4423 default:
4424 return FALSE;
4425 }
4426 } else {
4427 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4429 buf[0] = status->status;
4430 if (draw_text(view, line->type, buf, TRUE))
4431 return TRUE;
4432 type = LINE_DEFAULT;
4433 text = status->new.name;
4434 }
4436 draw_text(view, type, text, TRUE);
4437 return TRUE;
4438 }
4440 static enum request
4441 status_enter(struct view *view, struct line *line)
4442 {
4443 struct status *status = line->data;
4444 const char *oldpath = status ? status->old.name : NULL;
4445 /* Diffs for unmerged entries are empty when passing the new
4446 * path, so leave it empty. */
4447 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4448 const char *info;
4449 enum open_flags split;
4450 struct view *stage = VIEW(REQ_VIEW_STAGE);
4452 if (line->type == LINE_STAT_NONE ||
4453 (!status && line[1].type == LINE_STAT_NONE)) {
4454 report("No file to diff");
4455 return REQ_NONE;
4456 }
4458 switch (line->type) {
4459 case LINE_STAT_STAGED:
4460 if (is_initial_commit()) {
4461 const char *no_head_diff_argv[] = {
4462 "git", "diff", "--no-color", "--patch-with-stat",
4463 "--", "/dev/null", newpath, NULL
4464 };
4466 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4467 return REQ_QUIT;
4468 } else {
4469 const char *index_show_argv[] = {
4470 "git", "diff-index", "--root", "--patch-with-stat",
4471 "-C", "-M", "--cached", "HEAD", "--",
4472 oldpath, newpath, NULL
4473 };
4475 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4476 return REQ_QUIT;
4477 }
4479 if (status)
4480 info = "Staged changes to %s";
4481 else
4482 info = "Staged changes";
4483 break;
4485 case LINE_STAT_UNSTAGED:
4486 {
4487 const char *files_show_argv[] = {
4488 "git", "diff-files", "--root", "--patch-with-stat",
4489 "-C", "-M", "--", oldpath, newpath, NULL
4490 };
4492 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4493 return REQ_QUIT;
4494 if (status)
4495 info = "Unstaged changes to %s";
4496 else
4497 info = "Unstaged changes";
4498 break;
4499 }
4500 case LINE_STAT_UNTRACKED:
4501 if (!newpath) {
4502 report("No file to show");
4503 return REQ_NONE;
4504 }
4506 if (!suffixcmp(status->new.name, -1, "/")) {
4507 report("Cannot display a directory");
4508 return REQ_NONE;
4509 }
4511 if (!prepare_update_file(stage, newpath))
4512 return REQ_QUIT;
4513 info = "Untracked file %s";
4514 break;
4516 case LINE_STAT_HEAD:
4517 return REQ_NONE;
4519 default:
4520 die("line type %d not handled in switch", line->type);
4521 }
4523 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4524 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4525 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4526 if (status) {
4527 stage_status = *status;
4528 } else {
4529 memset(&stage_status, 0, sizeof(stage_status));
4530 }
4532 stage_line_type = line->type;
4533 stage_chunks = 0;
4534 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4535 }
4537 return REQ_NONE;
4538 }
4540 static bool
4541 status_exists(struct status *status, enum line_type type)
4542 {
4543 struct view *view = VIEW(REQ_VIEW_STATUS);
4544 struct line *line;
4546 for (line = view->line; line < view->line + view->lines; line++) {
4547 struct status *pos = line->data;
4549 if (line->type == type && pos &&
4550 !strcmp(status->new.name, pos->new.name))
4551 return TRUE;
4552 }
4554 return FALSE;
4555 }
4558 static bool
4559 status_update_prepare(struct io *io, enum line_type type)
4560 {
4561 const char *staged_argv[] = {
4562 "git", "update-index", "-z", "--index-info", NULL
4563 };
4564 const char *others_argv[] = {
4565 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4566 };
4568 switch (type) {
4569 case LINE_STAT_STAGED:
4570 return run_io(io, staged_argv, opt_cdup, IO_WR);
4572 case LINE_STAT_UNSTAGED:
4573 return run_io(io, others_argv, opt_cdup, IO_WR);
4575 case LINE_STAT_UNTRACKED:
4576 return run_io(io, others_argv, NULL, IO_WR);
4578 default:
4579 die("line type %d not handled in switch", type);
4580 return FALSE;
4581 }
4582 }
4584 static bool
4585 status_update_write(struct io *io, struct status *status, enum line_type type)
4586 {
4587 char buf[SIZEOF_STR];
4588 size_t bufsize = 0;
4590 switch (type) {
4591 case LINE_STAT_STAGED:
4592 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4593 status->old.mode,
4594 status->old.rev,
4595 status->old.name, 0))
4596 return FALSE;
4597 break;
4599 case LINE_STAT_UNSTAGED:
4600 case LINE_STAT_UNTRACKED:
4601 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4602 return FALSE;
4603 break;
4605 default:
4606 die("line type %d not handled in switch", type);
4607 }
4609 return io_write(io, buf, bufsize);
4610 }
4612 static bool
4613 status_update_file(struct status *status, enum line_type type)
4614 {
4615 struct io io = {};
4616 bool result;
4618 if (!status_update_prepare(&io, type))
4619 return FALSE;
4621 result = status_update_write(&io, status, type);
4622 done_io(&io);
4623 return result;
4624 }
4626 static bool
4627 status_update_files(struct view *view, struct line *line)
4628 {
4629 struct io io = {};
4630 bool result = TRUE;
4631 struct line *pos = view->line + view->lines;
4632 int files = 0;
4633 int file, done;
4635 if (!status_update_prepare(&io, line->type))
4636 return FALSE;
4638 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4639 files++;
4641 for (file = 0, done = 0; result && file < files; line++, file++) {
4642 int almost_done = file * 100 / files;
4644 if (almost_done > done) {
4645 done = almost_done;
4646 string_format(view->ref, "updating file %u of %u (%d%% done)",
4647 file, files, done);
4648 update_view_title(view);
4649 }
4650 result = status_update_write(&io, line->data, line->type);
4651 }
4653 done_io(&io);
4654 return result;
4655 }
4657 static bool
4658 status_update(struct view *view)
4659 {
4660 struct line *line = &view->line[view->lineno];
4662 assert(view->lines);
4664 if (!line->data) {
4665 /* This should work even for the "On branch" line. */
4666 if (line < view->line + view->lines && !line[1].data) {
4667 report("Nothing to update");
4668 return FALSE;
4669 }
4671 if (!status_update_files(view, line + 1)) {
4672 report("Failed to update file status");
4673 return FALSE;
4674 }
4676 } else if (!status_update_file(line->data, line->type)) {
4677 report("Failed to update file status");
4678 return FALSE;
4679 }
4681 return TRUE;
4682 }
4684 static bool
4685 status_revert(struct status *status, enum line_type type, bool has_none)
4686 {
4687 if (!status || type != LINE_STAT_UNSTAGED) {
4688 if (type == LINE_STAT_STAGED) {
4689 report("Cannot revert changes to staged files");
4690 } else if (type == LINE_STAT_UNTRACKED) {
4691 report("Cannot revert changes to untracked files");
4692 } else if (has_none) {
4693 report("Nothing to revert");
4694 } else {
4695 report("Cannot revert changes to multiple files");
4696 }
4697 return FALSE;
4699 } else {
4700 const char *checkout_argv[] = {
4701 "git", "checkout", "--", status->old.name, NULL
4702 };
4704 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4705 return FALSE;
4706 return run_io_fg(checkout_argv, opt_cdup);
4707 }
4708 }
4710 static enum request
4711 status_request(struct view *view, enum request request, struct line *line)
4712 {
4713 struct status *status = line->data;
4715 switch (request) {
4716 case REQ_STATUS_UPDATE:
4717 if (!status_update(view))
4718 return REQ_NONE;
4719 break;
4721 case REQ_STATUS_REVERT:
4722 if (!status_revert(status, line->type, status_has_none(view, line)))
4723 return REQ_NONE;
4724 break;
4726 case REQ_STATUS_MERGE:
4727 if (!status || status->status != 'U') {
4728 report("Merging only possible for files with unmerged status ('U').");
4729 return REQ_NONE;
4730 }
4731 open_mergetool(status->new.name);
4732 break;
4734 case REQ_EDIT:
4735 if (!status)
4736 return request;
4737 if (status->status == 'D') {
4738 report("File has been deleted.");
4739 return REQ_NONE;
4740 }
4742 open_editor(status->status != '?', status->new.name);
4743 break;
4745 case REQ_VIEW_BLAME:
4746 if (status) {
4747 string_copy(opt_file, status->new.name);
4748 opt_ref[0] = 0;
4749 }
4750 return request;
4752 case REQ_ENTER:
4753 /* After returning the status view has been split to
4754 * show the stage view. No further reloading is
4755 * necessary. */
4756 status_enter(view, line);
4757 return REQ_NONE;
4759 case REQ_REFRESH:
4760 /* Simply reload the view. */
4761 break;
4763 default:
4764 return request;
4765 }
4767 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4769 return REQ_NONE;
4770 }
4772 static void
4773 status_select(struct view *view, struct line *line)
4774 {
4775 struct status *status = line->data;
4776 char file[SIZEOF_STR] = "all files";
4777 const char *text;
4778 const char *key;
4780 if (status && !string_format(file, "'%s'", status->new.name))
4781 return;
4783 if (!status && line[1].type == LINE_STAT_NONE)
4784 line++;
4786 switch (line->type) {
4787 case LINE_STAT_STAGED:
4788 text = "Press %s to unstage %s for commit";
4789 break;
4791 case LINE_STAT_UNSTAGED:
4792 text = "Press %s to stage %s for commit";
4793 break;
4795 case LINE_STAT_UNTRACKED:
4796 text = "Press %s to stage %s for addition";
4797 break;
4799 case LINE_STAT_HEAD:
4800 case LINE_STAT_NONE:
4801 text = "Nothing to update";
4802 break;
4804 default:
4805 die("line type %d not handled in switch", line->type);
4806 }
4808 if (status && status->status == 'U') {
4809 text = "Press %s to resolve conflict in %s";
4810 key = get_key(REQ_STATUS_MERGE);
4812 } else {
4813 key = get_key(REQ_STATUS_UPDATE);
4814 }
4816 string_format(view->ref, text, key, file);
4817 }
4819 static bool
4820 status_grep(struct view *view, struct line *line)
4821 {
4822 struct status *status = line->data;
4823 enum { S_STATUS, S_NAME, S_END } state;
4824 char buf[2] = "?";
4825 regmatch_t pmatch;
4827 if (!status)
4828 return FALSE;
4830 for (state = S_STATUS; state < S_END; state++) {
4831 const char *text;
4833 switch (state) {
4834 case S_NAME: text = status->new.name; break;
4835 case S_STATUS:
4836 buf[0] = status->status;
4837 text = buf;
4838 break;
4840 default:
4841 return FALSE;
4842 }
4844 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4845 return TRUE;
4846 }
4848 return FALSE;
4849 }
4851 static struct view_ops status_ops = {
4852 "file",
4853 NULL,
4854 status_open,
4855 NULL,
4856 status_draw,
4857 status_request,
4858 status_grep,
4859 status_select,
4860 };
4863 static bool
4864 stage_diff_write(struct io *io, struct line *line, struct line *end)
4865 {
4866 while (line < end) {
4867 if (!io_write(io, line->data, strlen(line->data)) ||
4868 !io_write(io, "\n", 1))
4869 return FALSE;
4870 line++;
4871 if (line->type == LINE_DIFF_CHUNK ||
4872 line->type == LINE_DIFF_HEADER)
4873 break;
4874 }
4876 return TRUE;
4877 }
4879 static struct line *
4880 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4881 {
4882 for (; view->line < line; line--)
4883 if (line->type == type)
4884 return line;
4886 return NULL;
4887 }
4889 static bool
4890 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4891 {
4892 const char *apply_argv[SIZEOF_ARG] = {
4893 "git", "apply", "--whitespace=nowarn", NULL
4894 };
4895 struct line *diff_hdr;
4896 struct io io = {};
4897 int argc = 3;
4899 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4900 if (!diff_hdr)
4901 return FALSE;
4903 if (!revert)
4904 apply_argv[argc++] = "--cached";
4905 if (revert || stage_line_type == LINE_STAT_STAGED)
4906 apply_argv[argc++] = "-R";
4907 apply_argv[argc++] = "-";
4908 apply_argv[argc++] = NULL;
4909 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4910 return FALSE;
4912 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4913 !stage_diff_write(&io, chunk, view->line + view->lines))
4914 chunk = NULL;
4916 done_io(&io);
4917 run_io_bg(update_index_argv);
4919 return chunk ? TRUE : FALSE;
4920 }
4922 static bool
4923 stage_update(struct view *view, struct line *line)
4924 {
4925 struct line *chunk = NULL;
4927 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4928 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4930 if (chunk) {
4931 if (!stage_apply_chunk(view, chunk, FALSE)) {
4932 report("Failed to apply chunk");
4933 return FALSE;
4934 }
4936 } else if (!stage_status.status) {
4937 view = VIEW(REQ_VIEW_STATUS);
4939 for (line = view->line; line < view->line + view->lines; line++)
4940 if (line->type == stage_line_type)
4941 break;
4943 if (!status_update_files(view, line + 1)) {
4944 report("Failed to update files");
4945 return FALSE;
4946 }
4948 } else if (!status_update_file(&stage_status, stage_line_type)) {
4949 report("Failed to update file");
4950 return FALSE;
4951 }
4953 return TRUE;
4954 }
4956 static bool
4957 stage_revert(struct view *view, struct line *line)
4958 {
4959 struct line *chunk = NULL;
4961 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4962 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4964 if (chunk) {
4965 if (!prompt_yesno("Are you sure you want to revert changes?"))
4966 return FALSE;
4968 if (!stage_apply_chunk(view, chunk, TRUE)) {
4969 report("Failed to revert chunk");
4970 return FALSE;
4971 }
4972 return TRUE;
4974 } else {
4975 return status_revert(stage_status.status ? &stage_status : NULL,
4976 stage_line_type, FALSE);
4977 }
4978 }
4981 static void
4982 stage_next(struct view *view, struct line *line)
4983 {
4984 int i;
4986 if (!stage_chunks) {
4987 static size_t alloc = 0;
4988 int *tmp;
4990 for (line = view->line; line < view->line + view->lines; line++) {
4991 if (line->type != LINE_DIFF_CHUNK)
4992 continue;
4994 tmp = realloc_items(stage_chunk, &alloc,
4995 stage_chunks, sizeof(*tmp));
4996 if (!tmp) {
4997 report("Allocation failure");
4998 return;
4999 }
5001 stage_chunk = tmp;
5002 stage_chunk[stage_chunks++] = line - view->line;
5003 }
5004 }
5006 for (i = 0; i < stage_chunks; i++) {
5007 if (stage_chunk[i] > view->lineno) {
5008 do_scroll_view(view, stage_chunk[i] - view->lineno);
5009 report("Chunk %d of %d", i + 1, stage_chunks);
5010 return;
5011 }
5012 }
5014 report("No next chunk found");
5015 }
5017 static enum request
5018 stage_request(struct view *view, enum request request, struct line *line)
5019 {
5020 switch (request) {
5021 case REQ_STATUS_UPDATE:
5022 if (!stage_update(view, line))
5023 return REQ_NONE;
5024 break;
5026 case REQ_STATUS_REVERT:
5027 if (!stage_revert(view, line))
5028 return REQ_NONE;
5029 break;
5031 case REQ_STAGE_NEXT:
5032 if (stage_line_type == LINE_STAT_UNTRACKED) {
5033 report("File is untracked; press %s to add",
5034 get_key(REQ_STATUS_UPDATE));
5035 return REQ_NONE;
5036 }
5037 stage_next(view, line);
5038 return REQ_NONE;
5040 case REQ_EDIT:
5041 if (!stage_status.new.name[0])
5042 return request;
5043 if (stage_status.status == 'D') {
5044 report("File has been deleted.");
5045 return REQ_NONE;
5046 }
5048 open_editor(stage_status.status != '?', stage_status.new.name);
5049 break;
5051 case REQ_REFRESH:
5052 /* Reload everything ... */
5053 break;
5055 case REQ_VIEW_BLAME:
5056 if (stage_status.new.name[0]) {
5057 string_copy(opt_file, stage_status.new.name);
5058 opt_ref[0] = 0;
5059 }
5060 return request;
5062 case REQ_ENTER:
5063 return pager_request(view, request, line);
5065 default:
5066 return request;
5067 }
5069 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5071 /* Check whether the staged entry still exists, and close the
5072 * stage view if it doesn't. */
5073 if (!status_exists(&stage_status, stage_line_type))
5074 return REQ_VIEW_CLOSE;
5076 if (stage_line_type == LINE_STAT_UNTRACKED) {
5077 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5078 report("Cannot display a directory");
5079 return REQ_NONE;
5080 }
5082 if (!prepare_update_file(view, stage_status.new.name)) {
5083 report("Failed to open file: %s", strerror(errno));
5084 return REQ_NONE;
5085 }
5086 }
5087 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5089 return REQ_NONE;
5090 }
5092 static struct view_ops stage_ops = {
5093 "line",
5094 NULL,
5095 NULL,
5096 pager_read,
5097 pager_draw,
5098 stage_request,
5099 pager_grep,
5100 pager_select,
5101 };
5104 /*
5105 * Revision graph
5106 */
5108 struct commit {
5109 char id[SIZEOF_REV]; /* SHA1 ID. */
5110 char title[128]; /* First line of the commit message. */
5111 char author[75]; /* Author of the commit. */
5112 struct tm time; /* Date from the author ident. */
5113 struct ref **refs; /* Repository references. */
5114 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5115 size_t graph_size; /* The width of the graph array. */
5116 bool has_parents; /* Rewritten --parents seen. */
5117 };
5119 /* Size of rev graph with no "padding" columns */
5120 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5122 struct rev_graph {
5123 struct rev_graph *prev, *next, *parents;
5124 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5125 size_t size;
5126 struct commit *commit;
5127 size_t pos;
5128 unsigned int boundary:1;
5129 };
5131 /* Parents of the commit being visualized. */
5132 static struct rev_graph graph_parents[4];
5134 /* The current stack of revisions on the graph. */
5135 static struct rev_graph graph_stacks[4] = {
5136 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5137 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5138 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5139 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5140 };
5142 static inline bool
5143 graph_parent_is_merge(struct rev_graph *graph)
5144 {
5145 return graph->parents->size > 1;
5146 }
5148 static inline void
5149 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5150 {
5151 struct commit *commit = graph->commit;
5153 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5154 commit->graph[commit->graph_size++] = symbol;
5155 }
5157 static void
5158 clear_rev_graph(struct rev_graph *graph)
5159 {
5160 graph->boundary = 0;
5161 graph->size = graph->pos = 0;
5162 graph->commit = NULL;
5163 memset(graph->parents, 0, sizeof(*graph->parents));
5164 }
5166 static void
5167 done_rev_graph(struct rev_graph *graph)
5168 {
5169 if (graph_parent_is_merge(graph) &&
5170 graph->pos < graph->size - 1 &&
5171 graph->next->size == graph->size + graph->parents->size - 1) {
5172 size_t i = graph->pos + graph->parents->size - 1;
5174 graph->commit->graph_size = i * 2;
5175 while (i < graph->next->size - 1) {
5176 append_to_rev_graph(graph, ' ');
5177 append_to_rev_graph(graph, '\\');
5178 i++;
5179 }
5180 }
5182 clear_rev_graph(graph);
5183 }
5185 static void
5186 push_rev_graph(struct rev_graph *graph, const char *parent)
5187 {
5188 int i;
5190 /* "Collapse" duplicate parents lines.
5191 *
5192 * FIXME: This needs to also update update the drawn graph but
5193 * for now it just serves as a method for pruning graph lines. */
5194 for (i = 0; i < graph->size; i++)
5195 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5196 return;
5198 if (graph->size < SIZEOF_REVITEMS) {
5199 string_copy_rev(graph->rev[graph->size++], parent);
5200 }
5201 }
5203 static chtype
5204 get_rev_graph_symbol(struct rev_graph *graph)
5205 {
5206 chtype symbol;
5208 if (graph->boundary)
5209 symbol = REVGRAPH_BOUND;
5210 else if (graph->parents->size == 0)
5211 symbol = REVGRAPH_INIT;
5212 else if (graph_parent_is_merge(graph))
5213 symbol = REVGRAPH_MERGE;
5214 else if (graph->pos >= graph->size)
5215 symbol = REVGRAPH_BRANCH;
5216 else
5217 symbol = REVGRAPH_COMMIT;
5219 return symbol;
5220 }
5222 static void
5223 draw_rev_graph(struct rev_graph *graph)
5224 {
5225 struct rev_filler {
5226 chtype separator, line;
5227 };
5228 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5229 static struct rev_filler fillers[] = {
5230 { ' ', '|' },
5231 { '`', '.' },
5232 { '\'', ' ' },
5233 { '/', ' ' },
5234 };
5235 chtype symbol = get_rev_graph_symbol(graph);
5236 struct rev_filler *filler;
5237 size_t i;
5239 if (opt_line_graphics)
5240 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5242 filler = &fillers[DEFAULT];
5244 for (i = 0; i < graph->pos; i++) {
5245 append_to_rev_graph(graph, filler->line);
5246 if (graph_parent_is_merge(graph->prev) &&
5247 graph->prev->pos == i)
5248 filler = &fillers[RSHARP];
5250 append_to_rev_graph(graph, filler->separator);
5251 }
5253 /* Place the symbol for this revision. */
5254 append_to_rev_graph(graph, symbol);
5256 if (graph->prev->size > graph->size)
5257 filler = &fillers[RDIAG];
5258 else
5259 filler = &fillers[DEFAULT];
5261 i++;
5263 for (; i < graph->size; i++) {
5264 append_to_rev_graph(graph, filler->separator);
5265 append_to_rev_graph(graph, filler->line);
5266 if (graph_parent_is_merge(graph->prev) &&
5267 i < graph->prev->pos + graph->parents->size)
5268 filler = &fillers[RSHARP];
5269 if (graph->prev->size > graph->size)
5270 filler = &fillers[LDIAG];
5271 }
5273 if (graph->prev->size > graph->size) {
5274 append_to_rev_graph(graph, filler->separator);
5275 if (filler->line != ' ')
5276 append_to_rev_graph(graph, filler->line);
5277 }
5278 }
5280 /* Prepare the next rev graph */
5281 static void
5282 prepare_rev_graph(struct rev_graph *graph)
5283 {
5284 size_t i;
5286 /* First, traverse all lines of revisions up to the active one. */
5287 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5288 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5289 break;
5291 push_rev_graph(graph->next, graph->rev[graph->pos]);
5292 }
5294 /* Interleave the new revision parent(s). */
5295 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5296 push_rev_graph(graph->next, graph->parents->rev[i]);
5298 /* Lastly, put any remaining revisions. */
5299 for (i = graph->pos + 1; i < graph->size; i++)
5300 push_rev_graph(graph->next, graph->rev[i]);
5301 }
5303 static void
5304 update_rev_graph(struct rev_graph *graph)
5305 {
5306 /* If this is the finalizing update ... */
5307 if (graph->commit)
5308 prepare_rev_graph(graph);
5310 /* Graph visualization needs a one rev look-ahead,
5311 * so the first update doesn't visualize anything. */
5312 if (!graph->prev->commit)
5313 return;
5315 draw_rev_graph(graph->prev);
5316 done_rev_graph(graph->prev->prev);
5317 }
5320 /*
5321 * Main view backend
5322 */
5324 static const char *main_argv[SIZEOF_ARG] = {
5325 "git", "log", "--no-color", "--pretty=raw", "--parents",
5326 "--topo-order", "%(head)", NULL
5327 };
5329 static bool
5330 main_draw(struct view *view, struct line *line, unsigned int lineno)
5331 {
5332 struct commit *commit = line->data;
5334 if (!*commit->author)
5335 return FALSE;
5337 if (opt_date && draw_date(view, &commit->time))
5338 return TRUE;
5340 if (opt_author &&
5341 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5342 return TRUE;
5344 if (opt_rev_graph && commit->graph_size &&
5345 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5346 return TRUE;
5348 if (opt_show_refs && commit->refs) {
5349 size_t i = 0;
5351 do {
5352 enum line_type type;
5354 if (commit->refs[i]->head)
5355 type = LINE_MAIN_HEAD;
5356 else if (commit->refs[i]->ltag)
5357 type = LINE_MAIN_LOCAL_TAG;
5358 else if (commit->refs[i]->tag)
5359 type = LINE_MAIN_TAG;
5360 else if (commit->refs[i]->tracked)
5361 type = LINE_MAIN_TRACKED;
5362 else if (commit->refs[i]->remote)
5363 type = LINE_MAIN_REMOTE;
5364 else
5365 type = LINE_MAIN_REF;
5367 if (draw_text(view, type, "[", TRUE) ||
5368 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5369 draw_text(view, type, "]", TRUE))
5370 return TRUE;
5372 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5373 return TRUE;
5374 } while (commit->refs[i++]->next);
5375 }
5377 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5378 return TRUE;
5379 }
5381 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5382 static bool
5383 main_read(struct view *view, char *line)
5384 {
5385 static struct rev_graph *graph = graph_stacks;
5386 enum line_type type;
5387 struct commit *commit;
5389 if (!line) {
5390 int i;
5392 if (!view->lines && !view->parent)
5393 die("No revisions match the given arguments.");
5394 if (view->lines > 0) {
5395 commit = view->line[view->lines - 1].data;
5396 if (!*commit->author) {
5397 view->lines--;
5398 free(commit);
5399 graph->commit = NULL;
5400 }
5401 }
5402 update_rev_graph(graph);
5404 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5405 clear_rev_graph(&graph_stacks[i]);
5406 return TRUE;
5407 }
5409 type = get_line_type(line);
5410 if (type == LINE_COMMIT) {
5411 commit = calloc(1, sizeof(struct commit));
5412 if (!commit)
5413 return FALSE;
5415 line += STRING_SIZE("commit ");
5416 if (*line == '-') {
5417 graph->boundary = 1;
5418 line++;
5419 }
5421 string_copy_rev(commit->id, line);
5422 commit->refs = get_refs(commit->id);
5423 graph->commit = commit;
5424 add_line_data(view, commit, LINE_MAIN_COMMIT);
5426 while ((line = strchr(line, ' '))) {
5427 line++;
5428 push_rev_graph(graph->parents, line);
5429 commit->has_parents = TRUE;
5430 }
5431 return TRUE;
5432 }
5434 if (!view->lines)
5435 return TRUE;
5436 commit = view->line[view->lines - 1].data;
5438 switch (type) {
5439 case LINE_PARENT:
5440 if (commit->has_parents)
5441 break;
5442 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5443 break;
5445 case LINE_AUTHOR:
5446 {
5447 /* Parse author lines where the name may be empty:
5448 * author <email@address.tld> 1138474660 +0100
5449 */
5450 char *ident = line + STRING_SIZE("author ");
5451 char *nameend = strchr(ident, '<');
5452 char *emailend = strchr(ident, '>');
5454 if (!nameend || !emailend)
5455 break;
5457 update_rev_graph(graph);
5458 graph = graph->next;
5460 *nameend = *emailend = 0;
5461 ident = chomp_string(ident);
5462 if (!*ident) {
5463 ident = chomp_string(nameend + 1);
5464 if (!*ident)
5465 ident = "Unknown";
5466 }
5468 string_ncopy(commit->author, ident, strlen(ident));
5470 /* Parse epoch and timezone */
5471 if (emailend[1] == ' ') {
5472 char *secs = emailend + 2;
5473 char *zone = strchr(secs, ' ');
5474 time_t time = (time_t) atol(secs);
5476 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5477 long tz;
5479 zone++;
5480 tz = ('0' - zone[1]) * 60 * 60 * 10;
5481 tz += ('0' - zone[2]) * 60 * 60;
5482 tz += ('0' - zone[3]) * 60;
5483 tz += ('0' - zone[4]) * 60;
5485 if (zone[0] == '-')
5486 tz = -tz;
5488 time -= tz;
5489 }
5491 gmtime_r(&time, &commit->time);
5492 }
5493 break;
5494 }
5495 default:
5496 /* Fill in the commit title if it has not already been set. */
5497 if (commit->title[0])
5498 break;
5500 /* Require titles to start with a non-space character at the
5501 * offset used by git log. */
5502 if (strncmp(line, " ", 4))
5503 break;
5504 line += 4;
5505 /* Well, if the title starts with a whitespace character,
5506 * try to be forgiving. Otherwise we end up with no title. */
5507 while (isspace(*line))
5508 line++;
5509 if (*line == '\0')
5510 break;
5511 /* FIXME: More graceful handling of titles; append "..." to
5512 * shortened titles, etc. */
5514 string_ncopy(commit->title, line, strlen(line));
5515 }
5517 return TRUE;
5518 }
5520 static enum request
5521 main_request(struct view *view, enum request request, struct line *line)
5522 {
5523 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5525 switch (request) {
5526 case REQ_ENTER:
5527 open_view(view, REQ_VIEW_DIFF, flags);
5528 break;
5529 case REQ_REFRESH:
5530 load_refs();
5531 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5532 break;
5533 default:
5534 return request;
5535 }
5537 return REQ_NONE;
5538 }
5540 static bool
5541 grep_refs(struct ref **refs, regex_t *regex)
5542 {
5543 regmatch_t pmatch;
5544 size_t i = 0;
5546 if (!refs)
5547 return FALSE;
5548 do {
5549 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5550 return TRUE;
5551 } while (refs[i++]->next);
5553 return FALSE;
5554 }
5556 static bool
5557 main_grep(struct view *view, struct line *line)
5558 {
5559 struct commit *commit = line->data;
5560 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5561 char buf[DATE_COLS + 1];
5562 regmatch_t pmatch;
5564 for (state = S_TITLE; state < S_END; state++) {
5565 char *text;
5567 switch (state) {
5568 case S_TITLE: text = commit->title; break;
5569 case S_AUTHOR:
5570 if (!opt_author)
5571 continue;
5572 text = commit->author;
5573 break;
5574 case S_DATE:
5575 if (!opt_date)
5576 continue;
5577 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5578 continue;
5579 text = buf;
5580 break;
5581 case S_REFS:
5582 if (!opt_show_refs)
5583 continue;
5584 if (grep_refs(commit->refs, view->regex) == TRUE)
5585 return TRUE;
5586 continue;
5587 default:
5588 return FALSE;
5589 }
5591 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5592 return TRUE;
5593 }
5595 return FALSE;
5596 }
5598 static void
5599 main_select(struct view *view, struct line *line)
5600 {
5601 struct commit *commit = line->data;
5603 string_copy_rev(view->ref, commit->id);
5604 string_copy_rev(ref_commit, view->ref);
5605 }
5607 static struct view_ops main_ops = {
5608 "commit",
5609 main_argv,
5610 NULL,
5611 main_read,
5612 main_draw,
5613 main_request,
5614 main_grep,
5615 main_select,
5616 };
5619 /*
5620 * Unicode / UTF-8 handling
5621 *
5622 * NOTE: Much of the following code for dealing with unicode is derived from
5623 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5624 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5625 */
5627 /* I've (over)annotated a lot of code snippets because I am not entirely
5628 * confident that the approach taken by this small UTF-8 interface is correct.
5629 * --jonas */
5631 static inline int
5632 unicode_width(unsigned long c)
5633 {
5634 if (c >= 0x1100 &&
5635 (c <= 0x115f /* Hangul Jamo */
5636 || c == 0x2329
5637 || c == 0x232a
5638 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5639 /* CJK ... Yi */
5640 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5641 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5642 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5643 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5644 || (c >= 0xffe0 && c <= 0xffe6)
5645 || (c >= 0x20000 && c <= 0x2fffd)
5646 || (c >= 0x30000 && c <= 0x3fffd)))
5647 return 2;
5649 if (c == '\t')
5650 return opt_tab_size;
5652 return 1;
5653 }
5655 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5656 * Illegal bytes are set one. */
5657 static const unsigned char utf8_bytes[256] = {
5658 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,
5659 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,
5660 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,
5661 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,
5662 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,
5663 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,
5664 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,
5665 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,
5666 };
5668 /* Decode UTF-8 multi-byte representation into a unicode character. */
5669 static inline unsigned long
5670 utf8_to_unicode(const char *string, size_t length)
5671 {
5672 unsigned long unicode;
5674 switch (length) {
5675 case 1:
5676 unicode = string[0];
5677 break;
5678 case 2:
5679 unicode = (string[0] & 0x1f) << 6;
5680 unicode += (string[1] & 0x3f);
5681 break;
5682 case 3:
5683 unicode = (string[0] & 0x0f) << 12;
5684 unicode += ((string[1] & 0x3f) << 6);
5685 unicode += (string[2] & 0x3f);
5686 break;
5687 case 4:
5688 unicode = (string[0] & 0x0f) << 18;
5689 unicode += ((string[1] & 0x3f) << 12);
5690 unicode += ((string[2] & 0x3f) << 6);
5691 unicode += (string[3] & 0x3f);
5692 break;
5693 case 5:
5694 unicode = (string[0] & 0x0f) << 24;
5695 unicode += ((string[1] & 0x3f) << 18);
5696 unicode += ((string[2] & 0x3f) << 12);
5697 unicode += ((string[3] & 0x3f) << 6);
5698 unicode += (string[4] & 0x3f);
5699 break;
5700 case 6:
5701 unicode = (string[0] & 0x01) << 30;
5702 unicode += ((string[1] & 0x3f) << 24);
5703 unicode += ((string[2] & 0x3f) << 18);
5704 unicode += ((string[3] & 0x3f) << 12);
5705 unicode += ((string[4] & 0x3f) << 6);
5706 unicode += (string[5] & 0x3f);
5707 break;
5708 default:
5709 die("Invalid unicode length");
5710 }
5712 /* Invalid characters could return the special 0xfffd value but NUL
5713 * should be just as good. */
5714 return unicode > 0xffff ? 0 : unicode;
5715 }
5717 /* Calculates how much of string can be shown within the given maximum width
5718 * and sets trimmed parameter to non-zero value if all of string could not be
5719 * shown. If the reserve flag is TRUE, it will reserve at least one
5720 * trailing character, which can be useful when drawing a delimiter.
5721 *
5722 * Returns the number of bytes to output from string to satisfy max_width. */
5723 static size_t
5724 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5725 {
5726 const char *start = string;
5727 const char *end = strchr(string, '\0');
5728 unsigned char last_bytes = 0;
5729 size_t last_ucwidth = 0;
5731 *width = 0;
5732 *trimmed = 0;
5734 while (string < end) {
5735 int c = *(unsigned char *) string;
5736 unsigned char bytes = utf8_bytes[c];
5737 size_t ucwidth;
5738 unsigned long unicode;
5740 if (string + bytes > end)
5741 break;
5743 /* Change representation to figure out whether
5744 * it is a single- or double-width character. */
5746 unicode = utf8_to_unicode(string, bytes);
5747 /* FIXME: Graceful handling of invalid unicode character. */
5748 if (!unicode)
5749 break;
5751 ucwidth = unicode_width(unicode);
5752 *width += ucwidth;
5753 if (*width > max_width) {
5754 *trimmed = 1;
5755 *width -= ucwidth;
5756 if (reserve && *width == max_width) {
5757 string -= last_bytes;
5758 *width -= last_ucwidth;
5759 }
5760 break;
5761 }
5763 string += bytes;
5764 last_bytes = bytes;
5765 last_ucwidth = ucwidth;
5766 }
5768 return string - start;
5769 }
5772 /*
5773 * Status management
5774 */
5776 /* Whether or not the curses interface has been initialized. */
5777 static bool cursed = FALSE;
5779 /* The status window is used for polling keystrokes. */
5780 static WINDOW *status_win;
5782 static bool status_empty = TRUE;
5784 /* Update status and title window. */
5785 static void
5786 report(const char *msg, ...)
5787 {
5788 struct view *view = display[current_view];
5790 if (input_mode)
5791 return;
5793 if (!view) {
5794 char buf[SIZEOF_STR];
5795 va_list args;
5797 va_start(args, msg);
5798 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5799 buf[sizeof(buf) - 1] = 0;
5800 buf[sizeof(buf) - 2] = '.';
5801 buf[sizeof(buf) - 3] = '.';
5802 buf[sizeof(buf) - 4] = '.';
5803 }
5804 va_end(args);
5805 die("%s", buf);
5806 }
5808 if (!status_empty || *msg) {
5809 va_list args;
5811 va_start(args, msg);
5813 wmove(status_win, 0, 0);
5814 if (*msg) {
5815 vwprintw(status_win, msg, args);
5816 status_empty = FALSE;
5817 } else {
5818 status_empty = TRUE;
5819 }
5820 wclrtoeol(status_win);
5821 wrefresh(status_win);
5823 va_end(args);
5824 }
5826 update_view_title(view);
5827 update_display_cursor(view);
5828 }
5830 /* Controls when nodelay should be in effect when polling user input. */
5831 static void
5832 set_nonblocking_input(bool loading)
5833 {
5834 static unsigned int loading_views;
5836 if ((loading == FALSE && loading_views-- == 1) ||
5837 (loading == TRUE && loading_views++ == 0))
5838 nodelay(status_win, loading);
5839 }
5841 static void
5842 init_display(void)
5843 {
5844 int x, y;
5846 /* Initialize the curses library */
5847 if (isatty(STDIN_FILENO)) {
5848 cursed = !!initscr();
5849 opt_tty = stdin;
5850 } else {
5851 /* Leave stdin and stdout alone when acting as a pager. */
5852 opt_tty = fopen("/dev/tty", "r+");
5853 if (!opt_tty)
5854 die("Failed to open /dev/tty");
5855 cursed = !!newterm(NULL, opt_tty, opt_tty);
5856 }
5858 if (!cursed)
5859 die("Failed to initialize curses");
5861 nonl(); /* Tell curses not to do NL->CR/NL on output */
5862 cbreak(); /* Take input chars one at a time, no wait for \n */
5863 noecho(); /* Don't echo input */
5864 leaveok(stdscr, TRUE);
5866 if (has_colors())
5867 init_colors();
5869 getmaxyx(stdscr, y, x);
5870 status_win = newwin(1, 0, y - 1, 0);
5871 if (!status_win)
5872 die("Failed to create status window");
5874 /* Enable keyboard mapping */
5875 keypad(status_win, TRUE);
5876 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5878 TABSIZE = opt_tab_size;
5879 if (opt_line_graphics) {
5880 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5881 }
5882 }
5884 static bool
5885 prompt_yesno(const char *prompt)
5886 {
5887 enum { WAIT, STOP, CANCEL } status = WAIT;
5888 bool answer = FALSE;
5890 while (status == WAIT) {
5891 struct view *view;
5892 int i, key;
5894 input_mode = TRUE;
5896 foreach_view (view, i)
5897 update_view(view);
5899 input_mode = FALSE;
5901 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5902 wclrtoeol(status_win);
5904 /* Refresh, accept single keystroke of input */
5905 key = wgetch(status_win);
5906 switch (key) {
5907 case ERR:
5908 break;
5910 case 'y':
5911 case 'Y':
5912 answer = TRUE;
5913 status = STOP;
5914 break;
5916 case KEY_ESC:
5917 case KEY_RETURN:
5918 case KEY_ENTER:
5919 case KEY_BACKSPACE:
5920 case 'n':
5921 case 'N':
5922 case '\n':
5923 default:
5924 answer = FALSE;
5925 status = CANCEL;
5926 }
5927 }
5929 /* Clear the status window */
5930 status_empty = FALSE;
5931 report("");
5933 return answer;
5934 }
5936 static char *
5937 read_prompt(const char *prompt)
5938 {
5939 enum { READING, STOP, CANCEL } status = READING;
5940 static char buf[SIZEOF_STR];
5941 int pos = 0;
5943 while (status == READING) {
5944 struct view *view;
5945 int i, key;
5947 input_mode = TRUE;
5949 foreach_view (view, i)
5950 update_view(view);
5952 input_mode = FALSE;
5954 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5955 wclrtoeol(status_win);
5957 /* Refresh, accept single keystroke of input */
5958 key = wgetch(status_win);
5959 switch (key) {
5960 case KEY_RETURN:
5961 case KEY_ENTER:
5962 case '\n':
5963 status = pos ? STOP : CANCEL;
5964 break;
5966 case KEY_BACKSPACE:
5967 if (pos > 0)
5968 pos--;
5969 else
5970 status = CANCEL;
5971 break;
5973 case KEY_ESC:
5974 status = CANCEL;
5975 break;
5977 case ERR:
5978 break;
5980 default:
5981 if (pos >= sizeof(buf)) {
5982 report("Input string too long");
5983 return NULL;
5984 }
5986 if (isprint(key))
5987 buf[pos++] = (char) key;
5988 }
5989 }
5991 /* Clear the status window */
5992 status_empty = FALSE;
5993 report("");
5995 if (status == CANCEL)
5996 return NULL;
5998 buf[pos++] = 0;
6000 return buf;
6001 }
6003 /*
6004 * Repository properties
6005 */
6007 static int
6008 git_properties(const char **argv, const char *separators,
6009 int (*read_property)(char *, size_t, char *, size_t))
6010 {
6011 struct io io = {};
6013 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6014 return read_properties(&io, separators, read_property);
6015 return ERR;
6016 }
6018 static struct ref *refs = NULL;
6019 static size_t refs_alloc = 0;
6020 static size_t refs_size = 0;
6022 /* Id <-> ref store */
6023 static struct ref ***id_refs = NULL;
6024 static size_t id_refs_alloc = 0;
6025 static size_t id_refs_size = 0;
6027 static int
6028 compare_refs(const void *ref1_, const void *ref2_)
6029 {
6030 const struct ref *ref1 = *(const struct ref **)ref1_;
6031 const struct ref *ref2 = *(const struct ref **)ref2_;
6033 if (ref1->tag != ref2->tag)
6034 return ref2->tag - ref1->tag;
6035 if (ref1->ltag != ref2->ltag)
6036 return ref2->ltag - ref2->ltag;
6037 if (ref1->head != ref2->head)
6038 return ref2->head - ref1->head;
6039 if (ref1->tracked != ref2->tracked)
6040 return ref2->tracked - ref1->tracked;
6041 if (ref1->remote != ref2->remote)
6042 return ref2->remote - ref1->remote;
6043 return strcmp(ref1->name, ref2->name);
6044 }
6046 static struct ref **
6047 get_refs(const char *id)
6048 {
6049 struct ref ***tmp_id_refs;
6050 struct ref **ref_list = NULL;
6051 size_t ref_list_alloc = 0;
6052 size_t ref_list_size = 0;
6053 size_t i;
6055 for (i = 0; i < id_refs_size; i++)
6056 if (!strcmp(id, id_refs[i][0]->id))
6057 return id_refs[i];
6059 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6060 sizeof(*id_refs));
6061 if (!tmp_id_refs)
6062 return NULL;
6064 id_refs = tmp_id_refs;
6066 for (i = 0; i < refs_size; i++) {
6067 struct ref **tmp;
6069 if (strcmp(id, refs[i].id))
6070 continue;
6072 tmp = realloc_items(ref_list, &ref_list_alloc,
6073 ref_list_size + 1, sizeof(*ref_list));
6074 if (!tmp) {
6075 if (ref_list)
6076 free(ref_list);
6077 return NULL;
6078 }
6080 ref_list = tmp;
6081 ref_list[ref_list_size] = &refs[i];
6082 /* XXX: The properties of the commit chains ensures that we can
6083 * safely modify the shared ref. The repo references will
6084 * always be similar for the same id. */
6085 ref_list[ref_list_size]->next = 1;
6087 ref_list_size++;
6088 }
6090 if (ref_list) {
6091 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6092 ref_list[ref_list_size - 1]->next = 0;
6093 id_refs[id_refs_size++] = ref_list;
6094 }
6096 return ref_list;
6097 }
6099 static int
6100 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6101 {
6102 struct ref *ref;
6103 bool tag = FALSE;
6104 bool ltag = FALSE;
6105 bool remote = FALSE;
6106 bool tracked = FALSE;
6107 bool check_replace = FALSE;
6108 bool head = FALSE;
6110 if (!prefixcmp(name, "refs/tags/")) {
6111 if (!suffixcmp(name, namelen, "^{}")) {
6112 namelen -= 3;
6113 name[namelen] = 0;
6114 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6115 check_replace = TRUE;
6116 } else {
6117 ltag = TRUE;
6118 }
6120 tag = TRUE;
6121 namelen -= STRING_SIZE("refs/tags/");
6122 name += STRING_SIZE("refs/tags/");
6124 } else if (!prefixcmp(name, "refs/remotes/")) {
6125 remote = TRUE;
6126 namelen -= STRING_SIZE("refs/remotes/");
6127 name += STRING_SIZE("refs/remotes/");
6128 tracked = !strcmp(opt_remote, name);
6130 } else if (!prefixcmp(name, "refs/heads/")) {
6131 namelen -= STRING_SIZE("refs/heads/");
6132 name += STRING_SIZE("refs/heads/");
6133 head = !strncmp(opt_head, name, namelen);
6135 } else if (!strcmp(name, "HEAD")) {
6136 string_ncopy(opt_head_rev, id, idlen);
6137 return OK;
6138 }
6140 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6141 /* it's an annotated tag, replace the previous sha1 with the
6142 * resolved commit id; relies on the fact git-ls-remote lists
6143 * the commit id of an annotated tag right before the commit id
6144 * it points to. */
6145 refs[refs_size - 1].ltag = ltag;
6146 string_copy_rev(refs[refs_size - 1].id, id);
6148 return OK;
6149 }
6150 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6151 if (!refs)
6152 return ERR;
6154 ref = &refs[refs_size++];
6155 ref->name = malloc(namelen + 1);
6156 if (!ref->name)
6157 return ERR;
6159 strncpy(ref->name, name, namelen);
6160 ref->name[namelen] = 0;
6161 ref->head = head;
6162 ref->tag = tag;
6163 ref->ltag = ltag;
6164 ref->remote = remote;
6165 ref->tracked = tracked;
6166 string_copy_rev(ref->id, id);
6168 return OK;
6169 }
6171 static int
6172 load_refs(void)
6173 {
6174 static const char *ls_remote_argv[SIZEOF_ARG] = {
6175 "git", "ls-remote", ".", NULL
6176 };
6177 static bool init = FALSE;
6179 if (!init) {
6180 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6181 init = TRUE;
6182 }
6184 if (!*opt_git_dir)
6185 return OK;
6187 while (refs_size > 0)
6188 free(refs[--refs_size].name);
6189 while (id_refs_size > 0)
6190 free(id_refs[--id_refs_size]);
6192 return git_properties(ls_remote_argv, "\t", read_ref);
6193 }
6195 static int
6196 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6197 {
6198 if (!strcmp(name, "i18n.commitencoding"))
6199 string_ncopy(opt_encoding, value, valuelen);
6201 if (!strcmp(name, "core.editor"))
6202 string_ncopy(opt_editor, value, valuelen);
6204 /* branch.<head>.remote */
6205 if (*opt_head &&
6206 !strncmp(name, "branch.", 7) &&
6207 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6208 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6209 string_ncopy(opt_remote, value, valuelen);
6211 if (*opt_head && *opt_remote &&
6212 !strncmp(name, "branch.", 7) &&
6213 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6214 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6215 size_t from = strlen(opt_remote);
6217 if (!prefixcmp(value, "refs/heads/")) {
6218 value += STRING_SIZE("refs/heads/");
6219 valuelen -= STRING_SIZE("refs/heads/");
6220 }
6222 if (!string_format_from(opt_remote, &from, "/%s", value))
6223 opt_remote[0] = 0;
6224 }
6226 return OK;
6227 }
6229 static int
6230 load_git_config(void)
6231 {
6232 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6234 return git_properties(config_list_argv, "=", read_repo_config_option);
6235 }
6237 static int
6238 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6239 {
6240 if (!opt_git_dir[0]) {
6241 string_ncopy(opt_git_dir, name, namelen);
6243 } else if (opt_is_inside_work_tree == -1) {
6244 /* This can be 3 different values depending on the
6245 * version of git being used. If git-rev-parse does not
6246 * understand --is-inside-work-tree it will simply echo
6247 * the option else either "true" or "false" is printed.
6248 * Default to true for the unknown case. */
6249 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6250 } else {
6251 string_ncopy(opt_cdup, name, namelen);
6252 }
6254 return OK;
6255 }
6257 static int
6258 load_repo_info(void)
6259 {
6260 const char *head_argv[] = {
6261 "git", "symbolic-ref", "HEAD", NULL
6262 };
6263 const char *rev_parse_argv[] = {
6264 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6265 "--show-cdup", NULL
6266 };
6268 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6269 chomp_string(opt_head);
6270 if (!prefixcmp(opt_head, "refs/heads/")) {
6271 char *offset = opt_head + STRING_SIZE("refs/heads/");
6273 memmove(opt_head, offset, strlen(offset) + 1);
6274 }
6275 }
6277 return git_properties(rev_parse_argv, "=", read_repo_info);
6278 }
6280 static int
6281 read_properties(struct io *io, const char *separators,
6282 int (*read_property)(char *, size_t, char *, size_t))
6283 {
6284 char *name;
6285 int state = OK;
6287 if (!start_io(io))
6288 return ERR;
6290 while (state == OK && (name = io_gets(io))) {
6291 char *value;
6292 size_t namelen;
6293 size_t valuelen;
6295 name = chomp_string(name);
6296 namelen = strcspn(name, separators);
6298 if (name[namelen]) {
6299 name[namelen] = 0;
6300 value = chomp_string(name + namelen + 1);
6301 valuelen = strlen(value);
6303 } else {
6304 value = "";
6305 valuelen = 0;
6306 }
6308 state = read_property(name, namelen, value, valuelen);
6309 }
6311 if (state != ERR && io_error(io))
6312 state = ERR;
6313 done_io(io);
6315 return state;
6316 }
6319 /*
6320 * Main
6321 */
6323 static void __NORETURN
6324 quit(int sig)
6325 {
6326 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6327 if (cursed)
6328 endwin();
6329 exit(0);
6330 }
6332 static void __NORETURN
6333 die(const char *err, ...)
6334 {
6335 va_list args;
6337 endwin();
6339 va_start(args, err);
6340 fputs("tig: ", stderr);
6341 vfprintf(stderr, err, args);
6342 fputs("\n", stderr);
6343 va_end(args);
6345 exit(1);
6346 }
6348 static void
6349 warn(const char *msg, ...)
6350 {
6351 va_list args;
6353 va_start(args, msg);
6354 fputs("tig warning: ", stderr);
6355 vfprintf(stderr, msg, args);
6356 fputs("\n", stderr);
6357 va_end(args);
6358 }
6360 int
6361 main(int argc, const char *argv[])
6362 {
6363 const char **run_argv = NULL;
6364 struct view *view;
6365 enum request request;
6366 size_t i;
6368 signal(SIGINT, quit);
6370 if (setlocale(LC_ALL, "")) {
6371 char *codeset = nl_langinfo(CODESET);
6373 string_ncopy(opt_codeset, codeset, strlen(codeset));
6374 }
6376 if (load_repo_info() == ERR)
6377 die("Failed to load repo info.");
6379 if (load_options() == ERR)
6380 die("Failed to load user config.");
6382 if (load_git_config() == ERR)
6383 die("Failed to load repo config.");
6385 request = parse_options(argc, argv, &run_argv);
6386 if (request == REQ_NONE)
6387 return 0;
6389 /* Require a git repository unless when running in pager mode. */
6390 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6391 die("Not a git repository");
6393 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6394 opt_utf8 = FALSE;
6396 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6397 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6398 if (opt_iconv == ICONV_NONE)
6399 die("Failed to initialize character set conversion");
6400 }
6402 if (load_refs() == ERR)
6403 die("Failed to load refs.");
6405 foreach_view (view, i)
6406 argv_from_env(view->ops->argv, view->cmd_env);
6408 init_display();
6410 if (request == REQ_VIEW_PAGER || run_argv) {
6411 if (request == REQ_VIEW_PAGER)
6412 init_io_fd(&VIEW(request)->io, stdin);
6413 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6414 die("Failed to format arguments");
6415 open_view(NULL, request, OPEN_PREPARED);
6416 request = REQ_NONE;
6417 }
6419 while (view_driver(display[current_view], request)) {
6420 int key;
6421 int i;
6423 foreach_view (view, i)
6424 update_view(view);
6425 view = display[current_view];
6427 /* Refresh, accept single keystroke of input */
6428 key = wgetch(status_win);
6430 /* wgetch() with nodelay() enabled returns ERR when there's no
6431 * input. */
6432 if (key == ERR) {
6433 request = REQ_NONE;
6434 continue;
6435 }
6437 request = get_keybinding(view->keymap, key);
6439 /* Some low-level request handling. This keeps access to
6440 * status_win restricted. */
6441 switch (request) {
6442 case REQ_PROMPT:
6443 {
6444 char *cmd = read_prompt(":");
6446 if (cmd) {
6447 struct view *next = VIEW(REQ_VIEW_PAGER);
6448 const char *argv[SIZEOF_ARG] = { "git" };
6449 int argc = 1;
6451 /* When running random commands, initially show the
6452 * command in the title. However, it maybe later be
6453 * overwritten if a commit line is selected. */
6454 string_ncopy(next->ref, cmd, strlen(cmd));
6456 if (!argv_from_string(argv, &argc, cmd)) {
6457 report("Too many arguments");
6458 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6459 report("Failed to format command");
6460 } else {
6461 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6462 }
6463 }
6465 request = REQ_NONE;
6466 break;
6467 }
6468 case REQ_SEARCH:
6469 case REQ_SEARCH_BACK:
6470 {
6471 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6472 char *search = read_prompt(prompt);
6474 if (search)
6475 string_ncopy(opt_search, search, strlen(search));
6476 else
6477 request = REQ_NONE;
6478 break;
6479 }
6480 case REQ_SCREEN_RESIZE:
6481 {
6482 int height, width;
6484 getmaxyx(stdscr, height, width);
6486 /* Resize the status view and let the view driver take
6487 * care of resizing the displayed views. */
6488 wresize(status_win, 1, width);
6489 mvwin(status_win, height - 1, 0);
6490 wrefresh(status_win);
6491 break;
6492 }
6493 default:
6494 break;
6495 }
6496 }
6498 quit(0);
6500 return 0;
6501 }