1 /* Copyright (c) 2006-2009 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/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS 20
109 #define ID_COLS 8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152 const char *name;
153 int namelen;
154 int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
160 {
162 int i;
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
168 return OK;
169 }
171 return ERR;
172 }
175 /*
176 * String helpers
177 */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181 {
182 if (srclen > dstlen - 1)
183 srclen = dstlen - 1;
185 strncpy(dst, src, srclen);
186 dst[srclen] = 0;
187 }
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195 string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
205 {
206 int namelen;
208 while (isspace(*name))
209 name++;
211 namelen = strlen(name) - 1;
212 while (namelen > 0 && isspace(name[namelen]))
213 name[namelen--] = 0;
215 return name;
216 }
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220 {
221 va_list args;
222 size_t pos = bufpos ? *bufpos : 0;
224 va_start(args, fmt);
225 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226 va_end(args);
228 if (bufpos)
229 *bufpos = pos;
231 return pos >= bufsize ? FALSE : TRUE;
232 }
234 #define string_format(buf, fmt, args...) \
235 string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238 string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
242 {
243 size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247 /* Diff-Header == DIFF_HEADER */
248 for (i = 0; i < len; i++) {
249 if (toupper(str1[i]) == toupper(str2[i]))
250 continue;
252 if (string_enum_sep(str1[i]) &&
253 string_enum_sep(str2[i]))
254 continue;
256 return str1[i] - str2[i];
257 }
259 return 0;
260 }
262 #define prefixcmp(str1, str2) \
263 strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
267 {
268 size_t len = slen >= 0 ? slen : strlen(str);
269 size_t suffixlen = strlen(suffix);
271 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
272 }
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
277 {
278 int valuelen;
280 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281 bool advance = cmd[valuelen] != 0;
283 cmd[valuelen] = 0;
284 argv[(*argc)++] = chomp_string(cmd);
285 cmd += valuelen + advance;
286 }
288 if (*argc < SIZEOF_ARG)
289 argv[*argc] = NULL;
290 return *argc < SIZEOF_ARG;
291 }
293 static void
294 argv_from_env(const char **argv, const char *name)
295 {
296 char *env = argv ? getenv(name) : NULL;
297 int argc = 0;
299 if (env && *env)
300 env = strdup(env);
301 if (env && !argv_from_string(argv, &argc, env))
302 die("Too many arguments in the `%s` environment variable", name);
303 }
306 /*
307 * Executing external commands.
308 */
310 enum io_type {
311 IO_FD, /* File descriptor based IO. */
312 IO_BG, /* Execute command in the background. */
313 IO_FG, /* Execute command with same std{in,out,err}. */
314 IO_RD, /* Read only fork+exec IO. */
315 IO_WR, /* Write only fork+exec IO. */
316 IO_AP, /* Append fork+exec output to file. */
317 };
319 struct io {
320 enum io_type type; /* The requested type of pipe. */
321 const char *dir; /* Directory from which to execute. */
322 pid_t pid; /* Pipe for reading or writing. */
323 int pipe; /* Pipe end for reading or writing. */
324 int error; /* Error status. */
325 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
326 char *buf; /* Read buffer. */
327 size_t bufalloc; /* Allocated buffer size. */
328 size_t bufsize; /* Buffer content size. */
329 char *bufpos; /* Current buffer position. */
330 unsigned int eof:1; /* Has end of file been reached. */
331 };
333 static void
334 reset_io(struct io *io)
335 {
336 io->pipe = -1;
337 io->pid = 0;
338 io->buf = io->bufpos = NULL;
339 io->bufalloc = io->bufsize = 0;
340 io->error = 0;
341 io->eof = 0;
342 }
344 static void
345 init_io(struct io *io, const char *dir, enum io_type type)
346 {
347 reset_io(io);
348 io->type = type;
349 io->dir = dir;
350 }
352 static bool
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354 enum format_flags flags)
355 {
356 init_io(io, dir, IO_RD);
357 return format_argv(io->argv, argv, flags);
358 }
360 static bool
361 io_open(struct io *io, const char *name)
362 {
363 init_io(io, NULL, IO_FD);
364 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365 return io->pipe != -1;
366 }
368 static bool
369 kill_io(struct io *io)
370 {
371 return kill(io->pid, SIGKILL) != -1;
372 }
374 static bool
375 done_io(struct io *io)
376 {
377 pid_t pid = io->pid;
379 if (io->pipe != -1)
380 close(io->pipe);
381 free(io->buf);
382 reset_io(io);
384 while (pid > 0) {
385 int status;
386 pid_t waiting = waitpid(pid, &status, 0);
388 if (waiting < 0) {
389 if (errno == EINTR)
390 continue;
391 report("waitpid failed (%s)", strerror(errno));
392 return FALSE;
393 }
395 return waiting == pid &&
396 !WIFSIGNALED(status) &&
397 WIFEXITED(status) &&
398 !WEXITSTATUS(status);
399 }
401 return TRUE;
402 }
404 static bool
405 start_io(struct io *io)
406 {
407 int pipefds[2] = { -1, -1 };
409 if (io->type == IO_FD)
410 return TRUE;
412 if ((io->type == IO_RD || io->type == IO_WR) &&
413 pipe(pipefds) < 0)
414 return FALSE;
415 else if (io->type == IO_AP)
416 pipefds[1] = io->pipe;
418 if ((io->pid = fork())) {
419 if (pipefds[!(io->type == IO_WR)] != -1)
420 close(pipefds[!(io->type == IO_WR)]);
421 if (io->pid != -1) {
422 io->pipe = pipefds[!!(io->type == IO_WR)];
423 return TRUE;
424 }
426 } else {
427 if (io->type != IO_FG) {
428 int devnull = open("/dev/null", O_RDWR);
429 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
430 int writefd = (io->type == IO_RD || io->type == IO_AP)
431 ? pipefds[1] : devnull;
433 dup2(readfd, STDIN_FILENO);
434 dup2(writefd, STDOUT_FILENO);
435 dup2(devnull, STDERR_FILENO);
437 close(devnull);
438 if (pipefds[0] != -1)
439 close(pipefds[0]);
440 if (pipefds[1] != -1)
441 close(pipefds[1]);
442 }
444 if (io->dir && *io->dir && chdir(io->dir) == -1)
445 die("Failed to change directory: %s", strerror(errno));
447 execvp(io->argv[0], (char *const*) io->argv);
448 die("Failed to execute program: %s", strerror(errno));
449 }
451 if (pipefds[!!(io->type == IO_WR)] != -1)
452 close(pipefds[!!(io->type == IO_WR)]);
453 return FALSE;
454 }
456 static bool
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
458 {
459 init_io(io, dir, type);
460 if (!format_argv(io->argv, argv, FORMAT_NONE))
461 return FALSE;
462 return start_io(io);
463 }
465 static int
466 run_io_do(struct io *io)
467 {
468 return start_io(io) && done_io(io);
469 }
471 static int
472 run_io_bg(const char **argv)
473 {
474 struct io io = {};
476 init_io(&io, NULL, IO_BG);
477 if (!format_argv(io.argv, argv, FORMAT_NONE))
478 return FALSE;
479 return run_io_do(&io);
480 }
482 static bool
483 run_io_fg(const char **argv, const char *dir)
484 {
485 struct io io = {};
487 init_io(&io, dir, IO_FG);
488 if (!format_argv(io.argv, argv, FORMAT_NONE))
489 return FALSE;
490 return run_io_do(&io);
491 }
493 static bool
494 run_io_append(const char **argv, enum format_flags flags, int fd)
495 {
496 struct io io = {};
498 init_io(&io, NULL, IO_AP);
499 io.pipe = fd;
500 if (format_argv(io.argv, argv, flags))
501 return run_io_do(&io);
502 close(fd);
503 return FALSE;
504 }
506 static bool
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
508 {
509 return init_io_rd(io, argv, NULL, flags) && start_io(io);
510 }
512 static bool
513 io_eof(struct io *io)
514 {
515 return io->eof;
516 }
518 static int
519 io_error(struct io *io)
520 {
521 return io->error;
522 }
524 static bool
525 io_strerror(struct io *io)
526 {
527 return strerror(io->error);
528 }
530 static bool
531 io_can_read(struct io *io)
532 {
533 struct timeval tv = { 0, 500 };
534 fd_set fds;
536 FD_ZERO(&fds);
537 FD_SET(io->pipe, &fds);
539 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
540 }
542 static ssize_t
543 io_read(struct io *io, void *buf, size_t bufsize)
544 {
545 do {
546 ssize_t readsize = read(io->pipe, buf, bufsize);
548 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
549 continue;
550 else if (readsize == -1)
551 io->error = errno;
552 else if (readsize == 0)
553 io->eof = 1;
554 return readsize;
555 } while (1);
556 }
558 static char *
559 io_get(struct io *io, int c, bool can_read)
560 {
561 char *eol;
562 ssize_t readsize;
564 if (!io->buf) {
565 io->buf = io->bufpos = malloc(BUFSIZ);
566 if (!io->buf)
567 return NULL;
568 io->bufalloc = BUFSIZ;
569 io->bufsize = 0;
570 }
572 while (TRUE) {
573 if (io->bufsize > 0) {
574 eol = memchr(io->bufpos, c, io->bufsize);
575 if (eol) {
576 char *line = io->bufpos;
578 *eol = 0;
579 io->bufpos = eol + 1;
580 io->bufsize -= io->bufpos - line;
581 return line;
582 }
583 }
585 if (io_eof(io)) {
586 if (io->bufsize) {
587 io->bufpos[io->bufsize] = 0;
588 io->bufsize = 0;
589 return io->bufpos;
590 }
591 return NULL;
592 }
594 if (!can_read)
595 return NULL;
597 if (io->bufsize > 0 && io->bufpos > io->buf)
598 memmove(io->buf, io->bufpos, io->bufsize);
600 io->bufpos = io->buf;
601 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
602 if (io_error(io))
603 return NULL;
604 io->bufsize += readsize;
605 }
606 }
608 static bool
609 io_write(struct io *io, const void *buf, size_t bufsize)
610 {
611 size_t written = 0;
613 while (!io_error(io) && written < bufsize) {
614 ssize_t size;
616 size = write(io->pipe, buf + written, bufsize - written);
617 if (size < 0 && (errno == EAGAIN || errno == EINTR))
618 continue;
619 else if (size == -1)
620 io->error = errno;
621 else
622 written += size;
623 }
625 return written == bufsize;
626 }
628 static bool
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
630 {
631 struct io io = {};
632 bool error;
634 if (!run_io_rd(&io, argv, FORMAT_NONE))
635 return FALSE;
637 io.buf = io.bufpos = buf;
638 io.bufalloc = bufsize;
639 error = !io_get(&io, '\n', TRUE) && io_error(&io);
640 io.buf = NULL;
642 return done_io(&io) || error;
643 }
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
647 /*
648 * User requests
649 */
651 #define REQ_INFO \
652 /* XXX: Keep the view request first and in sync with views[]. */ \
653 REQ_GROUP("View switching") \
654 REQ_(VIEW_MAIN, "Show main view"), \
655 REQ_(VIEW_DIFF, "Show diff view"), \
656 REQ_(VIEW_LOG, "Show log view"), \
657 REQ_(VIEW_TREE, "Show tree view"), \
658 REQ_(VIEW_BLOB, "Show blob view"), \
659 REQ_(VIEW_BLAME, "Show blame view"), \
660 REQ_(VIEW_HELP, "Show help page"), \
661 REQ_(VIEW_PAGER, "Show pager view"), \
662 REQ_(VIEW_STATUS, "Show status view"), \
663 REQ_(VIEW_STAGE, "Show stage view"), \
664 \
665 REQ_GROUP("View manipulation") \
666 REQ_(ENTER, "Enter current line and scroll"), \
667 REQ_(NEXT, "Move to next"), \
668 REQ_(PREVIOUS, "Move to previous"), \
669 REQ_(VIEW_NEXT, "Move focus to next view"), \
670 REQ_(REFRESH, "Reload and refresh"), \
671 REQ_(MAXIMIZE, "Maximize the current view"), \
672 REQ_(VIEW_CLOSE, "Close the current view"), \
673 REQ_(QUIT, "Close all views and quit"), \
674 \
675 REQ_GROUP("View specific requests") \
676 REQ_(STATUS_UPDATE, "Update file status"), \
677 REQ_(STATUS_REVERT, "Revert file changes"), \
678 REQ_(STATUS_MERGE, "Merge file using external tool"), \
679 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
680 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
681 \
682 REQ_GROUP("Cursor navigation") \
683 REQ_(MOVE_UP, "Move cursor one line up"), \
684 REQ_(MOVE_DOWN, "Move cursor one line down"), \
685 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
686 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
687 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
688 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
689 \
690 REQ_GROUP("Scrolling") \
691 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
692 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
693 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
694 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
695 \
696 REQ_GROUP("Searching") \
697 REQ_(SEARCH, "Search the view"), \
698 REQ_(SEARCH_BACK, "Search backwards in the view"), \
699 REQ_(FIND_NEXT, "Find next search match"), \
700 REQ_(FIND_PREV, "Find previous search match"), \
701 \
702 REQ_GROUP("Option manipulation") \
703 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
704 REQ_(TOGGLE_DATE, "Toggle date display"), \
705 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
706 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
707 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
708 \
709 REQ_GROUP("Misc") \
710 REQ_(PROMPT, "Bring up the prompt"), \
711 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
712 REQ_(SCREEN_RESIZE, "Resize the screen"), \
713 REQ_(SHOW_VERSION, "Show version information"), \
714 REQ_(STOP_LOADING, "Stop all loading views"), \
715 REQ_(EDIT, "Open in editor"), \
716 REQ_(NONE, "Do nothing")
719 /* User action requests. */
720 enum request {
721 #define REQ_GROUP(help)
722 #define REQ_(req, help) REQ_##req
724 /* Offset all requests to avoid conflicts with ncurses getch values. */
725 REQ_OFFSET = KEY_MAX + 1,
726 REQ_INFO
728 #undef REQ_GROUP
729 #undef REQ_
730 };
732 struct request_info {
733 enum request request;
734 const char *name;
735 int namelen;
736 const char *help;
737 };
739 static struct request_info req_info[] = {
740 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
741 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
742 REQ_INFO
743 #undef REQ_GROUP
744 #undef REQ_
745 };
747 static enum request
748 get_request(const char *name)
749 {
750 int namelen = strlen(name);
751 int i;
753 for (i = 0; i < ARRAY_SIZE(req_info); i++)
754 if (req_info[i].namelen == namelen &&
755 !string_enum_compare(req_info[i].name, name, namelen))
756 return req_info[i].request;
758 return REQ_NONE;
759 }
762 /*
763 * Options
764 */
766 static const char usage[] =
767 "tig " TIG_VERSION " (" __DATE__ ")\n"
768 "\n"
769 "Usage: tig [options] [revs] [--] [paths]\n"
770 " or: tig show [options] [revs] [--] [paths]\n"
771 " or: tig blame [rev] path\n"
772 " or: tig status\n"
773 " or: tig < [git command output]\n"
774 "\n"
775 "Options:\n"
776 " -v, --version Show version and exit\n"
777 " -h, --help Show help message and exit";
779 /* Option and state variables. */
780 static bool opt_date = TRUE;
781 static bool opt_author = TRUE;
782 static bool opt_line_number = FALSE;
783 static bool opt_line_graphics = TRUE;
784 static bool opt_rev_graph = FALSE;
785 static bool opt_show_refs = TRUE;
786 static int opt_num_interval = NUMBER_INTERVAL;
787 static int opt_tab_size = TAB_SIZE;
788 static int opt_author_cols = AUTHOR_COLS-1;
789 static char opt_path[SIZEOF_STR] = "";
790 static char opt_file[SIZEOF_STR] = "";
791 static char opt_ref[SIZEOF_REF] = "";
792 static char opt_head[SIZEOF_REF] = "";
793 static char opt_head_rev[SIZEOF_REV] = "";
794 static char opt_remote[SIZEOF_REF] = "";
795 static char opt_encoding[20] = "UTF-8";
796 static bool opt_utf8 = TRUE;
797 static char opt_codeset[20] = "UTF-8";
798 static iconv_t opt_iconv = ICONV_NONE;
799 static char opt_search[SIZEOF_STR] = "";
800 static char opt_cdup[SIZEOF_STR] = "";
801 static char opt_git_dir[SIZEOF_STR] = "";
802 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
803 static char opt_editor[SIZEOF_STR] = "";
804 static FILE *opt_tty = NULL;
806 #define is_initial_commit() (!*opt_head_rev)
807 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
809 static enum request
810 parse_options(int argc, const char *argv[], const char ***run_argv)
811 {
812 enum request request = REQ_VIEW_MAIN;
813 const char *subcommand;
814 bool seen_dashdash = FALSE;
815 /* XXX: This is vulnerable to the user overriding options
816 * required for the main view parser. */
817 static const char *custom_argv[SIZEOF_ARG] = {
818 "git", "log", "--no-color", "--pretty=raw", "--parents",
819 "--topo-order", NULL
820 };
821 int i, j = 6;
823 if (!isatty(STDIN_FILENO))
824 return REQ_VIEW_PAGER;
826 if (argc <= 1)
827 return REQ_VIEW_MAIN;
829 subcommand = argv[1];
830 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
831 if (!strcmp(subcommand, "-S"))
832 warn("`-S' has been deprecated; use `tig status' instead");
833 if (argc > 2)
834 warn("ignoring arguments after `%s'", subcommand);
835 return REQ_VIEW_STATUS;
837 } else if (!strcmp(subcommand, "blame")) {
838 if (argc <= 2 || argc > 4)
839 die("invalid number of options to blame\n\n%s", usage);
841 i = 2;
842 if (argc == 4) {
843 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
844 i++;
845 }
847 string_ncopy(opt_file, argv[i], strlen(argv[i]));
848 return REQ_VIEW_BLAME;
850 } else if (!strcmp(subcommand, "show")) {
851 request = REQ_VIEW_DIFF;
853 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
854 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
855 warn("`tig %s' has been deprecated", subcommand);
857 } else {
858 subcommand = NULL;
859 }
861 if (subcommand) {
862 custom_argv[1] = subcommand;
863 j = 2;
864 }
866 for (i = 1 + !!subcommand; i < argc; i++) {
867 const char *opt = argv[i];
869 if (seen_dashdash || !strcmp(opt, "--")) {
870 seen_dashdash = TRUE;
872 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
873 printf("tig version %s\n", TIG_VERSION);
874 return REQ_NONE;
876 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
877 printf("%s\n", usage);
878 return REQ_NONE;
879 }
881 custom_argv[j++] = opt;
882 if (j >= ARRAY_SIZE(custom_argv))
883 die("command too long");
884 }
886 custom_argv[j] = NULL;
887 *run_argv = custom_argv;
889 return request;
890 }
893 /*
894 * Line-oriented content detection.
895 */
897 #define LINE_INFO \
898 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
900 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
901 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
902 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
903 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
912 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
913 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
914 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
919 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
920 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
927 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
928 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
929 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
930 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
932 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
933 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
934 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
935 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
936 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
941 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
942 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
944 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
945 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
947 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
953 enum line_type {
954 #define LINE(type, line, fg, bg, attr) \
955 LINE_##type
956 LINE_INFO,
957 LINE_NONE
958 #undef LINE
959 };
961 struct line_info {
962 const char *name; /* Option name. */
963 int namelen; /* Size of option name. */
964 const char *line; /* The start of line to match. */
965 int linelen; /* Size of string to match. */
966 int fg, bg, attr; /* Color and text attributes for the lines. */
967 };
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
972 LINE_INFO
973 #undef LINE
974 };
976 static enum line_type
977 get_line_type(const char *line)
978 {
979 int linelen = strlen(line);
980 enum line_type type;
982 for (type = 0; type < ARRAY_SIZE(line_info); type++)
983 /* Case insensitive search matches Signed-off-by lines better. */
984 if (linelen >= line_info[type].linelen &&
985 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986 return type;
988 return LINE_DEFAULT;
989 }
991 static inline int
992 get_line_attr(enum line_type type)
993 {
994 assert(type < ARRAY_SIZE(line_info));
995 return COLOR_PAIR(type) | line_info[type].attr;
996 }
998 static struct line_info *
999 get_line_info(const char *name)
1000 {
1001 size_t namelen = strlen(name);
1002 enum line_type type;
1004 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005 if (namelen == line_info[type].namelen &&
1006 !string_enum_compare(line_info[type].name, name, namelen))
1007 return &line_info[type];
1009 return NULL;
1010 }
1012 static void
1013 init_colors(void)
1014 {
1015 int default_bg = line_info[LINE_DEFAULT].bg;
1016 int default_fg = line_info[LINE_DEFAULT].fg;
1017 enum line_type type;
1019 start_color();
1021 if (assume_default_colors(default_fg, default_bg) == ERR) {
1022 default_bg = COLOR_BLACK;
1023 default_fg = COLOR_WHITE;
1024 }
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027 struct line_info *info = &line_info[type];
1028 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031 init_pair(type, fg, bg);
1032 }
1033 }
1035 struct line {
1036 enum line_type type;
1038 /* State flags */
1039 unsigned int selected:1;
1040 unsigned int dirty:1;
1041 unsigned int cleareol:1;
1043 void *data; /* User data */
1044 };
1047 /*
1048 * Keys
1049 */
1051 struct keybinding {
1052 int alias;
1053 enum request request;
1054 };
1056 static struct keybinding default_keybindings[] = {
1057 /* View switching */
1058 { 'm', REQ_VIEW_MAIN },
1059 { 'd', REQ_VIEW_DIFF },
1060 { 'l', REQ_VIEW_LOG },
1061 { 't', REQ_VIEW_TREE },
1062 { 'f', REQ_VIEW_BLOB },
1063 { 'B', REQ_VIEW_BLAME },
1064 { 'p', REQ_VIEW_PAGER },
1065 { 'h', REQ_VIEW_HELP },
1066 { 'S', REQ_VIEW_STATUS },
1067 { 'c', REQ_VIEW_STAGE },
1069 /* View manipulation */
1070 { 'q', REQ_VIEW_CLOSE },
1071 { KEY_TAB, REQ_VIEW_NEXT },
1072 { KEY_RETURN, REQ_ENTER },
1073 { KEY_UP, REQ_PREVIOUS },
1074 { KEY_DOWN, REQ_NEXT },
1075 { 'R', REQ_REFRESH },
1076 { KEY_F(5), REQ_REFRESH },
1077 { 'O', REQ_MAXIMIZE },
1079 /* Cursor navigation */
1080 { 'k', REQ_MOVE_UP },
1081 { 'j', REQ_MOVE_DOWN },
1082 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1083 { KEY_END, REQ_MOVE_LAST_LINE },
1084 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1085 { ' ', REQ_MOVE_PAGE_DOWN },
1086 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1087 { 'b', REQ_MOVE_PAGE_UP },
1088 { '-', REQ_MOVE_PAGE_UP },
1090 /* Scrolling */
1091 { KEY_IC, REQ_SCROLL_LINE_UP },
1092 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1093 { 'w', REQ_SCROLL_PAGE_UP },
1094 { 's', REQ_SCROLL_PAGE_DOWN },
1096 /* Searching */
1097 { '/', REQ_SEARCH },
1098 { '?', REQ_SEARCH_BACK },
1099 { 'n', REQ_FIND_NEXT },
1100 { 'N', REQ_FIND_PREV },
1102 /* Misc */
1103 { 'Q', REQ_QUIT },
1104 { 'z', REQ_STOP_LOADING },
1105 { 'v', REQ_SHOW_VERSION },
1106 { 'r', REQ_SCREEN_REDRAW },
1107 { '.', REQ_TOGGLE_LINENO },
1108 { 'D', REQ_TOGGLE_DATE },
1109 { 'A', REQ_TOGGLE_AUTHOR },
1110 { 'g', REQ_TOGGLE_REV_GRAPH },
1111 { 'F', REQ_TOGGLE_REFS },
1112 { ':', REQ_PROMPT },
1113 { 'u', REQ_STATUS_UPDATE },
1114 { '!', REQ_STATUS_REVERT },
1115 { 'M', REQ_STATUS_MERGE },
1116 { '@', REQ_STAGE_NEXT },
1117 { ',', REQ_TREE_PARENT },
1118 { 'e', REQ_EDIT },
1120 /* Using the ncurses SIGWINCH handler. */
1121 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1122 };
1124 #define KEYMAP_INFO \
1125 KEYMAP_(GENERIC), \
1126 KEYMAP_(MAIN), \
1127 KEYMAP_(DIFF), \
1128 KEYMAP_(LOG), \
1129 KEYMAP_(TREE), \
1130 KEYMAP_(BLOB), \
1131 KEYMAP_(BLAME), \
1132 KEYMAP_(PAGER), \
1133 KEYMAP_(HELP), \
1134 KEYMAP_(STATUS), \
1135 KEYMAP_(STAGE)
1137 enum keymap {
1138 #define KEYMAP_(name) KEYMAP_##name
1139 KEYMAP_INFO
1140 #undef KEYMAP_
1141 };
1143 static struct int_map keymap_table[] = {
1144 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1145 KEYMAP_INFO
1146 #undef KEYMAP_
1147 };
1149 #define set_keymap(map, name) \
1150 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1152 struct keybinding_table {
1153 struct keybinding *data;
1154 size_t size;
1155 };
1157 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1159 static void
1160 add_keybinding(enum keymap keymap, enum request request, int key)
1161 {
1162 struct keybinding_table *table = &keybindings[keymap];
1164 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1165 if (!table->data)
1166 die("Failed to allocate keybinding");
1167 table->data[table->size].alias = key;
1168 table->data[table->size++].request = request;
1169 }
1171 /* Looks for a key binding first in the given map, then in the generic map, and
1172 * lastly in the default keybindings. */
1173 static enum request
1174 get_keybinding(enum keymap keymap, int key)
1175 {
1176 size_t i;
1178 for (i = 0; i < keybindings[keymap].size; i++)
1179 if (keybindings[keymap].data[i].alias == key)
1180 return keybindings[keymap].data[i].request;
1182 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1183 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1184 return keybindings[KEYMAP_GENERIC].data[i].request;
1186 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1187 if (default_keybindings[i].alias == key)
1188 return default_keybindings[i].request;
1190 return (enum request) key;
1191 }
1194 struct key {
1195 const char *name;
1196 int value;
1197 };
1199 static struct key key_table[] = {
1200 { "Enter", KEY_RETURN },
1201 { "Space", ' ' },
1202 { "Backspace", KEY_BACKSPACE },
1203 { "Tab", KEY_TAB },
1204 { "Escape", KEY_ESC },
1205 { "Left", KEY_LEFT },
1206 { "Right", KEY_RIGHT },
1207 { "Up", KEY_UP },
1208 { "Down", KEY_DOWN },
1209 { "Insert", KEY_IC },
1210 { "Delete", KEY_DC },
1211 { "Hash", '#' },
1212 { "Home", KEY_HOME },
1213 { "End", KEY_END },
1214 { "PageUp", KEY_PPAGE },
1215 { "PageDown", KEY_NPAGE },
1216 { "F1", KEY_F(1) },
1217 { "F2", KEY_F(2) },
1218 { "F3", KEY_F(3) },
1219 { "F4", KEY_F(4) },
1220 { "F5", KEY_F(5) },
1221 { "F6", KEY_F(6) },
1222 { "F7", KEY_F(7) },
1223 { "F8", KEY_F(8) },
1224 { "F9", KEY_F(9) },
1225 { "F10", KEY_F(10) },
1226 { "F11", KEY_F(11) },
1227 { "F12", KEY_F(12) },
1228 };
1230 static int
1231 get_key_value(const char *name)
1232 {
1233 int i;
1235 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1236 if (!strcasecmp(key_table[i].name, name))
1237 return key_table[i].value;
1239 if (strlen(name) == 1 && isprint(*name))
1240 return (int) *name;
1242 return ERR;
1243 }
1245 static const char *
1246 get_key_name(int key_value)
1247 {
1248 static char key_char[] = "'X'";
1249 const char *seq = NULL;
1250 int key;
1252 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1253 if (key_table[key].value == key_value)
1254 seq = key_table[key].name;
1256 if (seq == NULL &&
1257 key_value < 127 &&
1258 isprint(key_value)) {
1259 key_char[1] = (char) key_value;
1260 seq = key_char;
1261 }
1263 return seq ? seq : "(no key)";
1264 }
1266 static const char *
1267 get_key(enum request request)
1268 {
1269 static char buf[BUFSIZ];
1270 size_t pos = 0;
1271 char *sep = "";
1272 int i;
1274 buf[pos] = 0;
1276 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1277 struct keybinding *keybinding = &default_keybindings[i];
1279 if (keybinding->request != request)
1280 continue;
1282 if (!string_format_from(buf, &pos, "%s%s", sep,
1283 get_key_name(keybinding->alias)))
1284 return "Too many keybindings!";
1285 sep = ", ";
1286 }
1288 return buf;
1289 }
1291 struct run_request {
1292 enum keymap keymap;
1293 int key;
1294 const char *argv[SIZEOF_ARG];
1295 };
1297 static struct run_request *run_request;
1298 static size_t run_requests;
1300 static enum request
1301 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1302 {
1303 struct run_request *req;
1305 if (argc >= ARRAY_SIZE(req->argv) - 1)
1306 return REQ_NONE;
1308 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1309 if (!req)
1310 return REQ_NONE;
1312 run_request = req;
1313 req = &run_request[run_requests];
1314 req->keymap = keymap;
1315 req->key = key;
1316 req->argv[0] = NULL;
1318 if (!format_argv(req->argv, argv, FORMAT_NONE))
1319 return REQ_NONE;
1321 return REQ_NONE + ++run_requests;
1322 }
1324 static struct run_request *
1325 get_run_request(enum request request)
1326 {
1327 if (request <= REQ_NONE)
1328 return NULL;
1329 return &run_request[request - REQ_NONE - 1];
1330 }
1332 static void
1333 add_builtin_run_requests(void)
1334 {
1335 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1336 const char *gc[] = { "git", "gc", NULL };
1337 struct {
1338 enum keymap keymap;
1339 int key;
1340 int argc;
1341 const char **argv;
1342 } reqs[] = {
1343 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1344 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1345 };
1346 int i;
1348 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1349 enum request req;
1351 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1352 if (req != REQ_NONE)
1353 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1354 }
1355 }
1357 /*
1358 * User config file handling.
1359 */
1361 static struct int_map color_map[] = {
1362 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1363 COLOR_MAP(DEFAULT),
1364 COLOR_MAP(BLACK),
1365 COLOR_MAP(BLUE),
1366 COLOR_MAP(CYAN),
1367 COLOR_MAP(GREEN),
1368 COLOR_MAP(MAGENTA),
1369 COLOR_MAP(RED),
1370 COLOR_MAP(WHITE),
1371 COLOR_MAP(YELLOW),
1372 };
1374 #define set_color(color, name) \
1375 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1377 static struct int_map attr_map[] = {
1378 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1379 ATTR_MAP(NORMAL),
1380 ATTR_MAP(BLINK),
1381 ATTR_MAP(BOLD),
1382 ATTR_MAP(DIM),
1383 ATTR_MAP(REVERSE),
1384 ATTR_MAP(STANDOUT),
1385 ATTR_MAP(UNDERLINE),
1386 };
1388 #define set_attribute(attr, name) \
1389 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1391 static int config_lineno;
1392 static bool config_errors;
1393 static const char *config_msg;
1395 /* Wants: object fgcolor bgcolor [attr] */
1396 static int
1397 option_color_command(int argc, const char *argv[])
1398 {
1399 struct line_info *info;
1401 if (argc != 3 && argc != 4) {
1402 config_msg = "Wrong number of arguments given to color command";
1403 return ERR;
1404 }
1406 info = get_line_info(argv[0]);
1407 if (!info) {
1408 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1409 info = get_line_info("delimiter");
1411 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1412 info = get_line_info("date");
1414 } else {
1415 config_msg = "Unknown color name";
1416 return ERR;
1417 }
1418 }
1420 if (set_color(&info->fg, argv[1]) == ERR ||
1421 set_color(&info->bg, argv[2]) == ERR) {
1422 config_msg = "Unknown color";
1423 return ERR;
1424 }
1426 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1427 config_msg = "Unknown attribute";
1428 return ERR;
1429 }
1431 return OK;
1432 }
1434 static bool parse_bool(const char *s)
1435 {
1436 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1437 !strcmp(s, "yes")) ? TRUE : FALSE;
1438 }
1440 static int
1441 parse_int(const char *s, int default_value, int min, int max)
1442 {
1443 int value = atoi(s);
1445 return (value < min || value > max) ? default_value : value;
1446 }
1448 /* Wants: name = value */
1449 static int
1450 option_set_command(int argc, const char *argv[])
1451 {
1452 if (argc != 3) {
1453 config_msg = "Wrong number of arguments given to set command";
1454 return ERR;
1455 }
1457 if (strcmp(argv[1], "=")) {
1458 config_msg = "No value assigned";
1459 return ERR;
1460 }
1462 if (!strcmp(argv[0], "show-author")) {
1463 opt_author = parse_bool(argv[2]);
1464 return OK;
1465 }
1467 if (!strcmp(argv[0], "show-date")) {
1468 opt_date = parse_bool(argv[2]);
1469 return OK;
1470 }
1472 if (!strcmp(argv[0], "show-rev-graph")) {
1473 opt_rev_graph = parse_bool(argv[2]);
1474 return OK;
1475 }
1477 if (!strcmp(argv[0], "show-refs")) {
1478 opt_show_refs = parse_bool(argv[2]);
1479 return OK;
1480 }
1482 if (!strcmp(argv[0], "show-line-numbers")) {
1483 opt_line_number = parse_bool(argv[2]);
1484 return OK;
1485 }
1487 if (!strcmp(argv[0], "line-graphics")) {
1488 opt_line_graphics = parse_bool(argv[2]);
1489 return OK;
1490 }
1492 if (!strcmp(argv[0], "line-number-interval")) {
1493 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1494 return OK;
1495 }
1497 if (!strcmp(argv[0], "author-width")) {
1498 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1499 return OK;
1500 }
1502 if (!strcmp(argv[0], "tab-size")) {
1503 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1504 return OK;
1505 }
1507 if (!strcmp(argv[0], "commit-encoding")) {
1508 const char *arg = argv[2];
1509 int arglen = strlen(arg);
1511 switch (arg[0]) {
1512 case '"':
1513 case '\'':
1514 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1515 config_msg = "Unmatched quotation";
1516 return ERR;
1517 }
1518 arg += 1; arglen -= 2;
1519 default:
1520 string_ncopy(opt_encoding, arg, strlen(arg));
1521 return OK;
1522 }
1523 }
1525 config_msg = "Unknown variable name";
1526 return ERR;
1527 }
1529 /* Wants: mode request key */
1530 static int
1531 option_bind_command(int argc, const char *argv[])
1532 {
1533 enum request request;
1534 int keymap;
1535 int key;
1537 if (argc < 3) {
1538 config_msg = "Wrong number of arguments given to bind command";
1539 return ERR;
1540 }
1542 if (set_keymap(&keymap, argv[0]) == ERR) {
1543 config_msg = "Unknown key map";
1544 return ERR;
1545 }
1547 key = get_key_value(argv[1]);
1548 if (key == ERR) {
1549 config_msg = "Unknown key";
1550 return ERR;
1551 }
1553 request = get_request(argv[2]);
1554 if (request == REQ_NONE) {
1555 const char *obsolete[] = { "cherry-pick" };
1556 size_t namelen = strlen(argv[2]);
1557 int i;
1559 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1560 if (namelen == strlen(obsolete[i]) &&
1561 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1562 config_msg = "Obsolete request name";
1563 return ERR;
1564 }
1565 }
1566 }
1567 if (request == REQ_NONE && *argv[2]++ == '!')
1568 request = add_run_request(keymap, key, argc - 2, argv + 2);
1569 if (request == REQ_NONE) {
1570 config_msg = "Unknown request name";
1571 return ERR;
1572 }
1574 add_keybinding(keymap, request, key);
1576 return OK;
1577 }
1579 static int
1580 set_option(const char *opt, char *value)
1581 {
1582 const char *argv[SIZEOF_ARG];
1583 int argc = 0;
1585 if (!argv_from_string(argv, &argc, value)) {
1586 config_msg = "Too many option arguments";
1587 return ERR;
1588 }
1590 if (!strcmp(opt, "color"))
1591 return option_color_command(argc, argv);
1593 if (!strcmp(opt, "set"))
1594 return option_set_command(argc, argv);
1596 if (!strcmp(opt, "bind"))
1597 return option_bind_command(argc, argv);
1599 config_msg = "Unknown option command";
1600 return ERR;
1601 }
1603 static int
1604 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1605 {
1606 int status = OK;
1608 config_lineno++;
1609 config_msg = "Internal error";
1611 /* Check for comment markers, since read_properties() will
1612 * only ensure opt and value are split at first " \t". */
1613 optlen = strcspn(opt, "#");
1614 if (optlen == 0)
1615 return OK;
1617 if (opt[optlen] != 0) {
1618 config_msg = "No option value";
1619 status = ERR;
1621 } else {
1622 /* Look for comment endings in the value. */
1623 size_t len = strcspn(value, "#");
1625 if (len < valuelen) {
1626 valuelen = len;
1627 value[valuelen] = 0;
1628 }
1630 status = set_option(opt, value);
1631 }
1633 if (status == ERR) {
1634 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1635 config_lineno, (int) optlen, opt, config_msg);
1636 config_errors = TRUE;
1637 }
1639 /* Always keep going if errors are encountered. */
1640 return OK;
1641 }
1643 static void
1644 load_option_file(const char *path)
1645 {
1646 struct io io = {};
1648 /* It's ok that the file doesn't exist. */
1649 if (!io_open(&io, path))
1650 return;
1652 config_lineno = 0;
1653 config_errors = FALSE;
1655 if (read_properties(&io, " \t", read_option) == ERR ||
1656 config_errors == TRUE)
1657 fprintf(stderr, "Errors while loading %s.\n", path);
1658 }
1660 static int
1661 load_options(void)
1662 {
1663 const char *home = getenv("HOME");
1664 const char *tigrc_user = getenv("TIGRC_USER");
1665 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1666 char buf[SIZEOF_STR];
1668 add_builtin_run_requests();
1670 if (!tigrc_system) {
1671 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1672 return ERR;
1673 tigrc_system = buf;
1674 }
1675 load_option_file(tigrc_system);
1677 if (!tigrc_user) {
1678 if (!home || !string_format(buf, "%s/.tigrc", home))
1679 return ERR;
1680 tigrc_user = buf;
1681 }
1682 load_option_file(tigrc_user);
1684 return OK;
1685 }
1688 /*
1689 * The viewer
1690 */
1692 struct view;
1693 struct view_ops;
1695 /* The display array of active views and the index of the current view. */
1696 static struct view *display[2];
1697 static unsigned int current_view;
1699 /* Reading from the prompt? */
1700 static bool input_mode = FALSE;
1702 #define foreach_displayed_view(view, i) \
1703 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1705 #define displayed_views() (display[1] != NULL ? 2 : 1)
1707 /* Current head and commit ID */
1708 static char ref_blob[SIZEOF_REF] = "";
1709 static char ref_commit[SIZEOF_REF] = "HEAD";
1710 static char ref_head[SIZEOF_REF] = "HEAD";
1712 struct view {
1713 const char *name; /* View name */
1714 const char *cmd_env; /* Command line set via environment */
1715 const char *id; /* Points to either of ref_{head,commit,blob} */
1717 struct view_ops *ops; /* View operations */
1719 enum keymap keymap; /* What keymap does this view have */
1720 bool git_dir; /* Whether the view requires a git directory. */
1722 char ref[SIZEOF_REF]; /* Hovered commit reference */
1723 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1725 int height, width; /* The width and height of the main window */
1726 WINDOW *win; /* The main window */
1727 WINDOW *title; /* The title window living below the main window */
1729 /* Navigation */
1730 unsigned long offset; /* Offset of the window top */
1731 unsigned long lineno; /* Current line number */
1733 /* Searching */
1734 char grep[SIZEOF_STR]; /* Search string */
1735 regex_t *regex; /* Pre-compiled regex */
1737 /* If non-NULL, points to the view that opened this view. If this view
1738 * is closed tig will switch back to the parent view. */
1739 struct view *parent;
1741 /* Buffering */
1742 size_t lines; /* Total number of lines */
1743 struct line *line; /* Line index */
1744 size_t line_alloc; /* Total number of allocated lines */
1745 unsigned int digits; /* Number of digits in the lines member. */
1747 /* Drawing */
1748 struct line *curline; /* Line currently being drawn. */
1749 enum line_type curtype; /* Attribute currently used for drawing. */
1750 unsigned long col; /* Column when drawing. */
1752 /* Loading */
1753 struct io io;
1754 struct io *pipe;
1755 time_t start_time;
1756 time_t update_secs;
1757 };
1759 struct view_ops {
1760 /* What type of content being displayed. Used in the title bar. */
1761 const char *type;
1762 /* Default command arguments. */
1763 const char **argv;
1764 /* Open and reads in all view content. */
1765 bool (*open)(struct view *view);
1766 /* Read one line; updates view->line. */
1767 bool (*read)(struct view *view, char *data);
1768 /* Draw one line; @lineno must be < view->height. */
1769 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1770 /* Depending on view handle a special requests. */
1771 enum request (*request)(struct view *view, enum request request, struct line *line);
1772 /* Search for regex in a line. */
1773 bool (*grep)(struct view *view, struct line *line);
1774 /* Select line */
1775 void (*select)(struct view *view, struct line *line);
1776 };
1778 static struct view_ops blame_ops;
1779 static struct view_ops blob_ops;
1780 static struct view_ops diff_ops;
1781 static struct view_ops help_ops;
1782 static struct view_ops log_ops;
1783 static struct view_ops main_ops;
1784 static struct view_ops pager_ops;
1785 static struct view_ops stage_ops;
1786 static struct view_ops status_ops;
1787 static struct view_ops tree_ops;
1789 #define VIEW_STR(name, env, ref, ops, map, git) \
1790 { name, #env, ref, ops, map, git }
1792 #define VIEW_(id, name, ops, git, ref) \
1793 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1796 static struct view views[] = {
1797 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1798 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1799 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1800 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1801 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1802 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1803 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1804 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1805 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1806 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1807 };
1809 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1810 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1812 #define foreach_view(view, i) \
1813 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1815 #define view_is_displayed(view) \
1816 (view == display[0] || view == display[1])
1819 enum line_graphic {
1820 LINE_GRAPHIC_VLINE
1821 };
1823 static int line_graphics[] = {
1824 /* LINE_GRAPHIC_VLINE: */ '|'
1825 };
1827 static inline void
1828 set_view_attr(struct view *view, enum line_type type)
1829 {
1830 if (!view->curline->selected && view->curtype != type) {
1831 wattrset(view->win, get_line_attr(type));
1832 wchgat(view->win, -1, 0, type, NULL);
1833 view->curtype = type;
1834 }
1835 }
1837 static int
1838 draw_chars(struct view *view, enum line_type type, const char *string,
1839 int max_len, bool use_tilde)
1840 {
1841 int len = 0;
1842 int col = 0;
1843 int trimmed = FALSE;
1845 if (max_len <= 0)
1846 return 0;
1848 if (opt_utf8) {
1849 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1850 } else {
1851 col = len = strlen(string);
1852 if (len > max_len) {
1853 if (use_tilde) {
1854 max_len -= 1;
1855 }
1856 col = len = max_len;
1857 trimmed = TRUE;
1858 }
1859 }
1861 set_view_attr(view, type);
1862 waddnstr(view->win, string, len);
1863 if (trimmed && use_tilde) {
1864 set_view_attr(view, LINE_DELIMITER);
1865 waddch(view->win, '~');
1866 col++;
1867 }
1869 return col;
1870 }
1872 static int
1873 draw_space(struct view *view, enum line_type type, int max, int spaces)
1874 {
1875 static char space[] = " ";
1876 int col = 0;
1878 spaces = MIN(max, spaces);
1880 while (spaces > 0) {
1881 int len = MIN(spaces, sizeof(space) - 1);
1883 col += draw_chars(view, type, space, spaces, FALSE);
1884 spaces -= len;
1885 }
1887 return col;
1888 }
1890 static bool
1891 draw_lineno(struct view *view, unsigned int lineno)
1892 {
1893 char number[10];
1894 int digits3 = view->digits < 3 ? 3 : view->digits;
1895 int max_number = MIN(digits3, STRING_SIZE(number));
1896 int max = view->width - view->col;
1897 int col;
1899 if (max < max_number)
1900 max_number = max;
1902 lineno += view->offset + 1;
1903 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1904 static char fmt[] = "%1ld";
1906 if (view->digits <= 9)
1907 fmt[1] = '0' + digits3;
1909 if (!string_format(number, fmt, lineno))
1910 number[0] = 0;
1911 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1912 } else {
1913 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1914 }
1916 if (col < max) {
1917 set_view_attr(view, LINE_DEFAULT);
1918 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1919 col++;
1920 }
1922 if (col < max)
1923 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1924 view->col += col;
1926 return view->width - view->col <= 0;
1927 }
1929 static bool
1930 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1931 {
1932 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1933 return view->width - view->col <= 0;
1934 }
1936 static bool
1937 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1938 {
1939 int max = view->width - view->col;
1940 int i;
1942 if (max < size)
1943 size = max;
1945 set_view_attr(view, type);
1946 /* Using waddch() instead of waddnstr() ensures that
1947 * they'll be rendered correctly for the cursor line. */
1948 for (i = 0; i < size; i++)
1949 waddch(view->win, graphic[i]);
1951 view->col += size;
1952 if (size < max) {
1953 waddch(view->win, ' ');
1954 view->col++;
1955 }
1957 return view->width - view->col <= 0;
1958 }
1960 static bool
1961 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1962 {
1963 int max = MIN(view->width - view->col, len);
1964 int col;
1966 if (text)
1967 col = draw_chars(view, type, text, max - 1, trim);
1968 else
1969 col = draw_space(view, type, max - 1, max - 1);
1971 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1972 return view->width - view->col <= 0;
1973 }
1975 static bool
1976 draw_date(struct view *view, struct tm *time)
1977 {
1978 char buf[DATE_COLS];
1979 char *date;
1980 int timelen = 0;
1982 if (time)
1983 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1984 date = timelen ? buf : NULL;
1986 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1987 }
1989 static bool
1990 draw_view_line(struct view *view, unsigned int lineno)
1991 {
1992 struct line *line;
1993 bool selected = (view->offset + lineno == view->lineno);
1994 bool draw_ok;
1996 assert(view_is_displayed(view));
1998 if (view->offset + lineno >= view->lines)
1999 return FALSE;
2001 line = &view->line[view->offset + lineno];
2003 wmove(view->win, lineno, 0);
2004 if (line->cleareol)
2005 wclrtoeol(view->win);
2006 view->col = 0;
2007 view->curline = line;
2008 view->curtype = LINE_NONE;
2009 line->selected = FALSE;
2010 line->dirty = line->cleareol = 0;
2012 if (selected) {
2013 set_view_attr(view, LINE_CURSOR);
2014 line->selected = TRUE;
2015 view->ops->select(view, line);
2016 }
2018 scrollok(view->win, FALSE);
2019 draw_ok = view->ops->draw(view, line, lineno);
2020 scrollok(view->win, TRUE);
2022 return draw_ok;
2023 }
2025 static void
2026 redraw_view_dirty(struct view *view)
2027 {
2028 bool dirty = FALSE;
2029 int lineno;
2031 for (lineno = 0; lineno < view->height; lineno++) {
2032 if (view->offset + lineno >= view->lines)
2033 break;
2034 if (!view->line[view->offset + lineno].dirty)
2035 continue;
2036 dirty = TRUE;
2037 if (!draw_view_line(view, lineno))
2038 break;
2039 }
2041 if (!dirty)
2042 return;
2043 redrawwin(view->win);
2044 if (input_mode)
2045 wnoutrefresh(view->win);
2046 else
2047 wrefresh(view->win);
2048 }
2050 static void
2051 redraw_view_from(struct view *view, int lineno)
2052 {
2053 assert(0 <= lineno && lineno < view->height);
2055 for (; lineno < view->height; lineno++) {
2056 if (!draw_view_line(view, lineno))
2057 break;
2058 }
2060 redrawwin(view->win);
2061 if (input_mode)
2062 wnoutrefresh(view->win);
2063 else
2064 wrefresh(view->win);
2065 }
2067 static void
2068 redraw_view(struct view *view)
2069 {
2070 werase(view->win);
2071 redraw_view_from(view, 0);
2072 }
2075 static void
2076 update_view_title(struct view *view)
2077 {
2078 char buf[SIZEOF_STR];
2079 char state[SIZEOF_STR];
2080 size_t bufpos = 0, statelen = 0;
2082 assert(view_is_displayed(view));
2084 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2085 unsigned int view_lines = view->offset + view->height;
2086 unsigned int lines = view->lines
2087 ? MIN(view_lines, view->lines) * 100 / view->lines
2088 : 0;
2090 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2091 view->ops->type,
2092 view->lineno + 1,
2093 view->lines,
2094 lines);
2096 }
2098 if (view->pipe) {
2099 time_t secs = time(NULL) - view->start_time;
2101 /* Three git seconds are a long time ... */
2102 if (secs > 2)
2103 string_format_from(state, &statelen, " loading %lds", secs);
2104 }
2106 string_format_from(buf, &bufpos, "[%s]", view->name);
2107 if (*view->ref && bufpos < view->width) {
2108 size_t refsize = strlen(view->ref);
2109 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2111 if (minsize < view->width)
2112 refsize = view->width - minsize + 7;
2113 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2114 }
2116 if (statelen && bufpos < view->width) {
2117 string_format_from(buf, &bufpos, "%s", state);
2118 }
2120 if (view == display[current_view])
2121 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2122 else
2123 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2125 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2126 wclrtoeol(view->title);
2127 wmove(view->title, 0, view->width - 1);
2129 if (input_mode)
2130 wnoutrefresh(view->title);
2131 else
2132 wrefresh(view->title);
2133 }
2135 static void
2136 resize_display(void)
2137 {
2138 int offset, i;
2139 struct view *base = display[0];
2140 struct view *view = display[1] ? display[1] : display[0];
2142 /* Setup window dimensions */
2144 getmaxyx(stdscr, base->height, base->width);
2146 /* Make room for the status window. */
2147 base->height -= 1;
2149 if (view != base) {
2150 /* Horizontal split. */
2151 view->width = base->width;
2152 view->height = SCALE_SPLIT_VIEW(base->height);
2153 base->height -= view->height;
2155 /* Make room for the title bar. */
2156 view->height -= 1;
2157 }
2159 /* Make room for the title bar. */
2160 base->height -= 1;
2162 offset = 0;
2164 foreach_displayed_view (view, i) {
2165 if (!view->win) {
2166 view->win = newwin(view->height, 0, offset, 0);
2167 if (!view->win)
2168 die("Failed to create %s view", view->name);
2170 scrollok(view->win, TRUE);
2172 view->title = newwin(1, 0, offset + view->height, 0);
2173 if (!view->title)
2174 die("Failed to create title window");
2176 } else {
2177 wresize(view->win, view->height, view->width);
2178 mvwin(view->win, offset, 0);
2179 mvwin(view->title, offset + view->height, 0);
2180 }
2182 offset += view->height + 1;
2183 }
2184 }
2186 static void
2187 redraw_display(bool clear)
2188 {
2189 struct view *view;
2190 int i;
2192 foreach_displayed_view (view, i) {
2193 if (clear)
2194 wclear(view->win);
2195 redraw_view(view);
2196 update_view_title(view);
2197 }
2198 }
2200 static void
2201 update_display_cursor(struct view *view)
2202 {
2203 /* Move the cursor to the right-most column of the cursor line.
2204 *
2205 * XXX: This could turn out to be a bit expensive, but it ensures that
2206 * the cursor does not jump around. */
2207 if (view->lines) {
2208 wmove(view->win, view->lineno - view->offset, view->width - 1);
2209 wrefresh(view->win);
2210 }
2211 }
2213 static void
2214 toggle_view_option(bool *option, const char *help)
2215 {
2216 *option = !*option;
2217 redraw_display(FALSE);
2218 report("%sabling %s", *option ? "En" : "Dis", help);
2219 }
2221 /*
2222 * Navigation
2223 */
2225 /* Scrolling backend */
2226 static void
2227 do_scroll_view(struct view *view, int lines)
2228 {
2229 bool redraw_current_line = FALSE;
2231 /* The rendering expects the new offset. */
2232 view->offset += lines;
2234 assert(0 <= view->offset && view->offset < view->lines);
2235 assert(lines);
2237 /* Move current line into the view. */
2238 if (view->lineno < view->offset) {
2239 view->lineno = view->offset;
2240 redraw_current_line = TRUE;
2241 } else if (view->lineno >= view->offset + view->height) {
2242 view->lineno = view->offset + view->height - 1;
2243 redraw_current_line = TRUE;
2244 }
2246 assert(view->offset <= view->lineno && view->lineno < view->lines);
2248 /* Redraw the whole screen if scrolling is pointless. */
2249 if (view->height < ABS(lines)) {
2250 redraw_view(view);
2252 } else {
2253 int line = lines > 0 ? view->height - lines : 0;
2254 int end = line + ABS(lines);
2256 wscrl(view->win, lines);
2258 for (; line < end; line++) {
2259 if (!draw_view_line(view, line))
2260 break;
2261 }
2263 if (redraw_current_line)
2264 draw_view_line(view, view->lineno - view->offset);
2265 }
2267 redrawwin(view->win);
2268 wrefresh(view->win);
2269 report("");
2270 }
2272 /* Scroll frontend */
2273 static void
2274 scroll_view(struct view *view, enum request request)
2275 {
2276 int lines = 1;
2278 assert(view_is_displayed(view));
2280 switch (request) {
2281 case REQ_SCROLL_PAGE_DOWN:
2282 lines = view->height;
2283 case REQ_SCROLL_LINE_DOWN:
2284 if (view->offset + lines > view->lines)
2285 lines = view->lines - view->offset;
2287 if (lines == 0 || view->offset + view->height >= view->lines) {
2288 report("Cannot scroll beyond the last line");
2289 return;
2290 }
2291 break;
2293 case REQ_SCROLL_PAGE_UP:
2294 lines = view->height;
2295 case REQ_SCROLL_LINE_UP:
2296 if (lines > view->offset)
2297 lines = view->offset;
2299 if (lines == 0) {
2300 report("Cannot scroll beyond the first line");
2301 return;
2302 }
2304 lines = -lines;
2305 break;
2307 default:
2308 die("request %d not handled in switch", request);
2309 }
2311 do_scroll_view(view, lines);
2312 }
2314 /* Cursor moving */
2315 static void
2316 move_view(struct view *view, enum request request)
2317 {
2318 int scroll_steps = 0;
2319 int steps;
2321 switch (request) {
2322 case REQ_MOVE_FIRST_LINE:
2323 steps = -view->lineno;
2324 break;
2326 case REQ_MOVE_LAST_LINE:
2327 steps = view->lines - view->lineno - 1;
2328 break;
2330 case REQ_MOVE_PAGE_UP:
2331 steps = view->height > view->lineno
2332 ? -view->lineno : -view->height;
2333 break;
2335 case REQ_MOVE_PAGE_DOWN:
2336 steps = view->lineno + view->height >= view->lines
2337 ? view->lines - view->lineno - 1 : view->height;
2338 break;
2340 case REQ_MOVE_UP:
2341 steps = -1;
2342 break;
2344 case REQ_MOVE_DOWN:
2345 steps = 1;
2346 break;
2348 default:
2349 die("request %d not handled in switch", request);
2350 }
2352 if (steps <= 0 && view->lineno == 0) {
2353 report("Cannot move beyond the first line");
2354 return;
2356 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2357 report("Cannot move beyond the last line");
2358 return;
2359 }
2361 /* Move the current line */
2362 view->lineno += steps;
2363 assert(0 <= view->lineno && view->lineno < view->lines);
2365 /* Check whether the view needs to be scrolled */
2366 if (view->lineno < view->offset ||
2367 view->lineno >= view->offset + view->height) {
2368 scroll_steps = steps;
2369 if (steps < 0 && -steps > view->offset) {
2370 scroll_steps = -view->offset;
2372 } else if (steps > 0) {
2373 if (view->lineno == view->lines - 1 &&
2374 view->lines > view->height) {
2375 scroll_steps = view->lines - view->offset - 1;
2376 if (scroll_steps >= view->height)
2377 scroll_steps -= view->height - 1;
2378 }
2379 }
2380 }
2382 if (!view_is_displayed(view)) {
2383 view->offset += scroll_steps;
2384 assert(0 <= view->offset && view->offset < view->lines);
2385 view->ops->select(view, &view->line[view->lineno]);
2386 return;
2387 }
2389 /* Repaint the old "current" line if we be scrolling */
2390 if (ABS(steps) < view->height)
2391 draw_view_line(view, view->lineno - steps - view->offset);
2393 if (scroll_steps) {
2394 do_scroll_view(view, scroll_steps);
2395 return;
2396 }
2398 /* Draw the current line */
2399 draw_view_line(view, view->lineno - view->offset);
2401 redrawwin(view->win);
2402 wrefresh(view->win);
2403 report("");
2404 }
2407 /*
2408 * Searching
2409 */
2411 static void search_view(struct view *view, enum request request);
2413 static bool
2414 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2415 {
2416 assert(view_is_displayed(view));
2418 if (!view->ops->grep(view, line))
2419 return FALSE;
2421 if (lineno - view->offset >= view->height) {
2422 view->offset = lineno;
2423 view->lineno = lineno;
2424 redraw_view(view);
2426 } else {
2427 unsigned long old_lineno = view->lineno - view->offset;
2429 view->lineno = lineno;
2430 draw_view_line(view, old_lineno);
2432 draw_view_line(view, view->lineno - view->offset);
2433 redrawwin(view->win);
2434 wrefresh(view->win);
2435 }
2437 report("Line %ld matches '%s'", lineno + 1, view->grep);
2438 return TRUE;
2439 }
2441 static void
2442 find_next(struct view *view, enum request request)
2443 {
2444 unsigned long lineno = view->lineno;
2445 int direction;
2447 if (!*view->grep) {
2448 if (!*opt_search)
2449 report("No previous search");
2450 else
2451 search_view(view, request);
2452 return;
2453 }
2455 switch (request) {
2456 case REQ_SEARCH:
2457 case REQ_FIND_NEXT:
2458 direction = 1;
2459 break;
2461 case REQ_SEARCH_BACK:
2462 case REQ_FIND_PREV:
2463 direction = -1;
2464 break;
2466 default:
2467 return;
2468 }
2470 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2471 lineno += direction;
2473 /* Note, lineno is unsigned long so will wrap around in which case it
2474 * will become bigger than view->lines. */
2475 for (; lineno < view->lines; lineno += direction) {
2476 struct line *line = &view->line[lineno];
2478 if (find_next_line(view, lineno, line))
2479 return;
2480 }
2482 report("No match found for '%s'", view->grep);
2483 }
2485 static void
2486 search_view(struct view *view, enum request request)
2487 {
2488 int regex_err;
2490 if (view->regex) {
2491 regfree(view->regex);
2492 *view->grep = 0;
2493 } else {
2494 view->regex = calloc(1, sizeof(*view->regex));
2495 if (!view->regex)
2496 return;
2497 }
2499 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2500 if (regex_err != 0) {
2501 char buf[SIZEOF_STR] = "unknown error";
2503 regerror(regex_err, view->regex, buf, sizeof(buf));
2504 report("Search failed: %s", buf);
2505 return;
2506 }
2508 string_copy(view->grep, opt_search);
2510 find_next(view, request);
2511 }
2513 /*
2514 * Incremental updating
2515 */
2517 static void
2518 reset_view(struct view *view)
2519 {
2520 int i;
2522 for (i = 0; i < view->lines; i++)
2523 free(view->line[i].data);
2524 free(view->line);
2526 view->line = NULL;
2527 view->offset = 0;
2528 view->lines = 0;
2529 view->lineno = 0;
2530 view->line_alloc = 0;
2531 view->vid[0] = 0;
2532 view->update_secs = 0;
2533 }
2535 static void
2536 free_argv(const char *argv[])
2537 {
2538 int argc;
2540 for (argc = 0; argv[argc]; argc++)
2541 free((void *) argv[argc]);
2542 }
2544 static bool
2545 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2546 {
2547 char buf[SIZEOF_STR];
2548 int argc;
2549 bool noreplace = flags == FORMAT_NONE;
2551 free_argv(dst_argv);
2553 for (argc = 0; src_argv[argc]; argc++) {
2554 const char *arg = src_argv[argc];
2555 size_t bufpos = 0;
2557 while (arg) {
2558 char *next = strstr(arg, "%(");
2559 int len = next - arg;
2560 const char *value;
2562 if (!next || noreplace) {
2563 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2564 noreplace = TRUE;
2565 len = strlen(arg);
2566 value = "";
2568 } else if (!prefixcmp(next, "%(directory)")) {
2569 value = opt_path;
2571 } else if (!prefixcmp(next, "%(file)")) {
2572 value = opt_file;
2574 } else if (!prefixcmp(next, "%(ref)")) {
2575 value = *opt_ref ? opt_ref : "HEAD";
2577 } else if (!prefixcmp(next, "%(head)")) {
2578 value = ref_head;
2580 } else if (!prefixcmp(next, "%(commit)")) {
2581 value = ref_commit;
2583 } else if (!prefixcmp(next, "%(blob)")) {
2584 value = ref_blob;
2586 } else {
2587 report("Unknown replacement: `%s`", next);
2588 return FALSE;
2589 }
2591 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2592 return FALSE;
2594 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2595 }
2597 dst_argv[argc] = strdup(buf);
2598 if (!dst_argv[argc])
2599 break;
2600 }
2602 dst_argv[argc] = NULL;
2604 return src_argv[argc] == NULL;
2605 }
2607 static void
2608 end_update(struct view *view, bool force)
2609 {
2610 if (!view->pipe)
2611 return;
2612 while (!view->ops->read(view, NULL))
2613 if (!force)
2614 return;
2615 set_nonblocking_input(FALSE);
2616 if (force)
2617 kill_io(view->pipe);
2618 done_io(view->pipe);
2619 view->pipe = NULL;
2620 }
2622 static void
2623 setup_update(struct view *view, const char *vid)
2624 {
2625 set_nonblocking_input(TRUE);
2626 reset_view(view);
2627 string_copy_rev(view->vid, vid);
2628 view->pipe = &view->io;
2629 view->start_time = time(NULL);
2630 }
2632 static bool
2633 prepare_update(struct view *view, const char *argv[], const char *dir,
2634 enum format_flags flags)
2635 {
2636 if (view->pipe)
2637 end_update(view, TRUE);
2638 return init_io_rd(&view->io, argv, dir, flags);
2639 }
2641 static bool
2642 prepare_update_file(struct view *view, const char *name)
2643 {
2644 if (view->pipe)
2645 end_update(view, TRUE);
2646 return io_open(&view->io, name);
2647 }
2649 static bool
2650 begin_update(struct view *view, bool refresh)
2651 {
2652 if (view->pipe)
2653 end_update(view, TRUE);
2655 if (refresh) {
2656 if (!start_io(&view->io))
2657 return FALSE;
2659 } else {
2660 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2661 opt_path[0] = 0;
2663 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2664 return FALSE;
2666 /* Put the current ref_* value to the view title ref
2667 * member. This is needed by the blob view. Most other
2668 * views sets it automatically after loading because the
2669 * first line is a commit line. */
2670 string_copy_rev(view->ref, view->id);
2671 }
2673 setup_update(view, view->id);
2675 return TRUE;
2676 }
2678 #define ITEM_CHUNK_SIZE 256
2679 static void *
2680 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2681 {
2682 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2683 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2685 if (mem == NULL || num_chunks != num_chunks_new) {
2686 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2687 mem = realloc(mem, *size * item_size);
2688 }
2690 return mem;
2691 }
2693 static struct line *
2694 realloc_lines(struct view *view, size_t line_size)
2695 {
2696 size_t alloc = view->line_alloc;
2697 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2698 sizeof(*view->line));
2700 if (!tmp)
2701 return NULL;
2703 view->line = tmp;
2704 view->line_alloc = alloc;
2705 return view->line;
2706 }
2708 static bool
2709 update_view(struct view *view)
2710 {
2711 char out_buffer[BUFSIZ * 2];
2712 char *line;
2713 /* Clear the view and redraw everything since the tree sorting
2714 * might have rearranged things. */
2715 bool redraw = view->lines == 0;
2716 bool can_read = TRUE;
2718 if (!view->pipe)
2719 return TRUE;
2721 if (!io_can_read(view->pipe)) {
2722 if (view->lines == 0) {
2723 time_t secs = time(NULL) - view->start_time;
2725 if (secs > view->update_secs) {
2726 if (view->update_secs == 0)
2727 redraw_view(view);
2728 update_view_title(view);
2729 view->update_secs = secs;
2730 }
2731 }
2732 return TRUE;
2733 }
2735 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2736 if (opt_iconv != ICONV_NONE) {
2737 ICONV_CONST char *inbuf = line;
2738 size_t inlen = strlen(line) + 1;
2740 char *outbuf = out_buffer;
2741 size_t outlen = sizeof(out_buffer);
2743 size_t ret;
2745 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2746 if (ret != (size_t) -1)
2747 line = out_buffer;
2748 }
2750 if (!view->ops->read(view, line))
2751 goto alloc_error;
2752 }
2754 {
2755 unsigned long lines = view->lines;
2756 int digits;
2758 for (digits = 0; lines; digits++)
2759 lines /= 10;
2761 /* Keep the displayed view in sync with line number scaling. */
2762 if (digits != view->digits) {
2763 view->digits = digits;
2764 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2765 redraw = TRUE;
2766 }
2767 }
2769 if (io_error(view->pipe)) {
2770 report("Failed to read: %s", io_strerror(view->pipe));
2771 end_update(view, TRUE);
2773 } else if (io_eof(view->pipe)) {
2774 report("");
2775 end_update(view, FALSE);
2776 }
2778 if (!view_is_displayed(view))
2779 return TRUE;
2781 if (redraw)
2782 redraw_view_from(view, 0);
2783 else
2784 redraw_view_dirty(view);
2786 /* Update the title _after_ the redraw so that if the redraw picks up a
2787 * commit reference in view->ref it'll be available here. */
2788 update_view_title(view);
2789 return TRUE;
2791 alloc_error:
2792 report("Allocation failure");
2793 end_update(view, TRUE);
2794 return FALSE;
2795 }
2797 static struct line *
2798 add_line_data(struct view *view, void *data, enum line_type type)
2799 {
2800 struct line *line;
2802 if (!realloc_lines(view, view->lines + 1))
2803 return NULL;
2805 line = &view->line[view->lines++];
2806 memset(line, 0, sizeof(*line));
2807 line->type = type;
2808 line->data = data;
2809 line->dirty = 1;
2811 return line;
2812 }
2814 static struct line *
2815 add_line_text(struct view *view, const char *text, enum line_type type)
2816 {
2817 char *data = text ? strdup(text) : NULL;
2819 return data ? add_line_data(view, data, type) : NULL;
2820 }
2822 static struct line *
2823 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2824 {
2825 char buf[SIZEOF_STR];
2826 va_list args;
2828 va_start(args, fmt);
2829 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2830 buf[0] = 0;
2831 va_end(args);
2833 return buf[0] ? add_line_text(view, buf, type) : NULL;
2834 }
2836 /*
2837 * View opening
2838 */
2840 enum open_flags {
2841 OPEN_DEFAULT = 0, /* Use default view switching. */
2842 OPEN_SPLIT = 1, /* Split current view. */
2843 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2844 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2845 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2846 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2847 OPEN_PREPARED = 32, /* Open already prepared command. */
2848 };
2850 static void
2851 open_view(struct view *prev, enum request request, enum open_flags flags)
2852 {
2853 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2854 bool split = !!(flags & OPEN_SPLIT);
2855 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2856 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2857 struct view *view = VIEW(request);
2858 int nviews = displayed_views();
2859 struct view *base_view = display[0];
2861 if (view == prev && nviews == 1 && !reload) {
2862 report("Already in %s view", view->name);
2863 return;
2864 }
2866 if (view->git_dir && !opt_git_dir[0]) {
2867 report("The %s view is disabled in pager view", view->name);
2868 return;
2869 }
2871 if (split) {
2872 display[1] = view;
2873 if (!backgrounded)
2874 current_view = 1;
2875 } else if (!nomaximize) {
2876 /* Maximize the current view. */
2877 memset(display, 0, sizeof(display));
2878 current_view = 0;
2879 display[current_view] = view;
2880 }
2882 /* Resize the view when switching between split- and full-screen,
2883 * or when switching between two different full-screen views. */
2884 if (nviews != displayed_views() ||
2885 (nviews == 1 && base_view != display[0]))
2886 resize_display();
2888 if (view->ops->open) {
2889 if (!view->ops->open(view)) {
2890 report("Failed to load %s view", view->name);
2891 return;
2892 }
2894 } else if ((reload || strcmp(view->vid, view->id)) &&
2895 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2896 report("Failed to load %s view", view->name);
2897 return;
2898 }
2900 if (split && prev->lineno - prev->offset >= prev->height) {
2901 /* Take the title line into account. */
2902 int lines = prev->lineno - prev->offset - prev->height + 1;
2904 /* Scroll the view that was split if the current line is
2905 * outside the new limited view. */
2906 do_scroll_view(prev, lines);
2907 }
2909 if (prev && view != prev) {
2910 if (split && !backgrounded) {
2911 /* "Blur" the previous view. */
2912 update_view_title(prev);
2913 }
2915 view->parent = prev;
2916 }
2918 if (view->pipe && view->lines == 0) {
2919 /* Clear the old view and let the incremental updating refill
2920 * the screen. */
2921 werase(view->win);
2922 report("");
2923 } else if (view_is_displayed(view)) {
2924 redraw_view(view);
2925 report("");
2926 }
2928 /* If the view is backgrounded the above calls to report()
2929 * won't redraw the view title. */
2930 if (backgrounded)
2931 update_view_title(view);
2932 }
2934 static void
2935 open_external_viewer(const char *argv[], const char *dir)
2936 {
2937 def_prog_mode(); /* save current tty modes */
2938 endwin(); /* restore original tty modes */
2939 run_io_fg(argv, dir);
2940 fprintf(stderr, "Press Enter to continue");
2941 getc(opt_tty);
2942 reset_prog_mode();
2943 redraw_display(TRUE);
2944 }
2946 static void
2947 open_mergetool(const char *file)
2948 {
2949 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2951 open_external_viewer(mergetool_argv, opt_cdup);
2952 }
2954 static void
2955 open_editor(bool from_root, const char *file)
2956 {
2957 const char *editor_argv[] = { "vi", file, NULL };
2958 const char *editor;
2960 editor = getenv("GIT_EDITOR");
2961 if (!editor && *opt_editor)
2962 editor = opt_editor;
2963 if (!editor)
2964 editor = getenv("VISUAL");
2965 if (!editor)
2966 editor = getenv("EDITOR");
2967 if (!editor)
2968 editor = "vi";
2970 editor_argv[0] = editor;
2971 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2972 }
2974 static void
2975 open_run_request(enum request request)
2976 {
2977 struct run_request *req = get_run_request(request);
2978 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2980 if (!req) {
2981 report("Unknown run request");
2982 return;
2983 }
2985 if (format_argv(argv, req->argv, FORMAT_ALL))
2986 open_external_viewer(argv, NULL);
2987 free_argv(argv);
2988 }
2990 /*
2991 * User request switch noodle
2992 */
2994 static int
2995 view_driver(struct view *view, enum request request)
2996 {
2997 int i;
2999 if (request == REQ_NONE) {
3000 doupdate();
3001 return TRUE;
3002 }
3004 if (request > REQ_NONE) {
3005 open_run_request(request);
3006 /* FIXME: When all views can refresh always do this. */
3007 if (view == VIEW(REQ_VIEW_STATUS) ||
3008 view == VIEW(REQ_VIEW_MAIN) ||
3009 view == VIEW(REQ_VIEW_LOG) ||
3010 view == VIEW(REQ_VIEW_STAGE))
3011 request = REQ_REFRESH;
3012 else
3013 return TRUE;
3014 }
3016 if (view && view->lines) {
3017 request = view->ops->request(view, request, &view->line[view->lineno]);
3018 if (request == REQ_NONE)
3019 return TRUE;
3020 }
3022 switch (request) {
3023 case REQ_MOVE_UP:
3024 case REQ_MOVE_DOWN:
3025 case REQ_MOVE_PAGE_UP:
3026 case REQ_MOVE_PAGE_DOWN:
3027 case REQ_MOVE_FIRST_LINE:
3028 case REQ_MOVE_LAST_LINE:
3029 move_view(view, request);
3030 break;
3032 case REQ_SCROLL_LINE_DOWN:
3033 case REQ_SCROLL_LINE_UP:
3034 case REQ_SCROLL_PAGE_DOWN:
3035 case REQ_SCROLL_PAGE_UP:
3036 scroll_view(view, request);
3037 break;
3039 case REQ_VIEW_BLAME:
3040 if (!opt_file[0]) {
3041 report("No file chosen, press %s to open tree view",
3042 get_key(REQ_VIEW_TREE));
3043 break;
3044 }
3045 open_view(view, request, OPEN_DEFAULT);
3046 break;
3048 case REQ_VIEW_BLOB:
3049 if (!ref_blob[0]) {
3050 report("No file chosen, press %s to open tree view",
3051 get_key(REQ_VIEW_TREE));
3052 break;
3053 }
3054 open_view(view, request, OPEN_DEFAULT);
3055 break;
3057 case REQ_VIEW_PAGER:
3058 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3059 report("No pager content, press %s to run command from prompt",
3060 get_key(REQ_PROMPT));
3061 break;
3062 }
3063 open_view(view, request, OPEN_DEFAULT);
3064 break;
3066 case REQ_VIEW_STAGE:
3067 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3068 report("No stage content, press %s to open the status view and choose file",
3069 get_key(REQ_VIEW_STATUS));
3070 break;
3071 }
3072 open_view(view, request, OPEN_DEFAULT);
3073 break;
3075 case REQ_VIEW_STATUS:
3076 if (opt_is_inside_work_tree == FALSE) {
3077 report("The status view requires a working tree");
3078 break;
3079 }
3080 open_view(view, request, OPEN_DEFAULT);
3081 break;
3083 case REQ_VIEW_MAIN:
3084 case REQ_VIEW_DIFF:
3085 case REQ_VIEW_LOG:
3086 case REQ_VIEW_TREE:
3087 case REQ_VIEW_HELP:
3088 open_view(view, request, OPEN_DEFAULT);
3089 break;
3091 case REQ_NEXT:
3092 case REQ_PREVIOUS:
3093 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3095 if ((view == VIEW(REQ_VIEW_DIFF) &&
3096 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3097 (view == VIEW(REQ_VIEW_DIFF) &&
3098 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3099 (view == VIEW(REQ_VIEW_STAGE) &&
3100 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3101 (view == VIEW(REQ_VIEW_BLOB) &&
3102 view->parent == VIEW(REQ_VIEW_TREE))) {
3103 int line;
3105 view = view->parent;
3106 line = view->lineno;
3107 move_view(view, request);
3108 if (view_is_displayed(view))
3109 update_view_title(view);
3110 if (line != view->lineno)
3111 view->ops->request(view, REQ_ENTER,
3112 &view->line[view->lineno]);
3114 } else {
3115 move_view(view, request);
3116 }
3117 break;
3119 case REQ_VIEW_NEXT:
3120 {
3121 int nviews = displayed_views();
3122 int next_view = (current_view + 1) % nviews;
3124 if (next_view == current_view) {
3125 report("Only one view is displayed");
3126 break;
3127 }
3129 current_view = next_view;
3130 /* Blur out the title of the previous view. */
3131 update_view_title(view);
3132 report("");
3133 break;
3134 }
3135 case REQ_REFRESH:
3136 report("Refreshing is not yet supported for the %s view", view->name);
3137 break;
3139 case REQ_MAXIMIZE:
3140 if (displayed_views() == 2)
3141 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3142 break;
3144 case REQ_TOGGLE_LINENO:
3145 toggle_view_option(&opt_line_number, "line numbers");
3146 break;
3148 case REQ_TOGGLE_DATE:
3149 toggle_view_option(&opt_date, "date display");
3150 break;
3152 case REQ_TOGGLE_AUTHOR:
3153 toggle_view_option(&opt_author, "author display");
3154 break;
3156 case REQ_TOGGLE_REV_GRAPH:
3157 toggle_view_option(&opt_rev_graph, "revision graph display");
3158 break;
3160 case REQ_TOGGLE_REFS:
3161 toggle_view_option(&opt_show_refs, "reference display");
3162 break;
3164 case REQ_SEARCH:
3165 case REQ_SEARCH_BACK:
3166 search_view(view, request);
3167 break;
3169 case REQ_FIND_NEXT:
3170 case REQ_FIND_PREV:
3171 find_next(view, request);
3172 break;
3174 case REQ_STOP_LOADING:
3175 for (i = 0; i < ARRAY_SIZE(views); i++) {
3176 view = &views[i];
3177 if (view->pipe)
3178 report("Stopped loading the %s view", view->name),
3179 end_update(view, TRUE);
3180 }
3181 break;
3183 case REQ_SHOW_VERSION:
3184 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3185 return TRUE;
3187 case REQ_SCREEN_RESIZE:
3188 resize_display();
3189 /* Fall-through */
3190 case REQ_SCREEN_REDRAW:
3191 redraw_display(TRUE);
3192 break;
3194 case REQ_EDIT:
3195 report("Nothing to edit");
3196 break;
3198 case REQ_ENTER:
3199 report("Nothing to enter");
3200 break;
3202 case REQ_VIEW_CLOSE:
3203 /* XXX: Mark closed views by letting view->parent point to the
3204 * view itself. Parents to closed view should never be
3205 * followed. */
3206 if (view->parent &&
3207 view->parent->parent != view->parent) {
3208 memset(display, 0, sizeof(display));
3209 current_view = 0;
3210 display[current_view] = view->parent;
3211 view->parent = view;
3212 resize_display();
3213 redraw_display(FALSE);
3214 report("");
3215 break;
3216 }
3217 /* Fall-through */
3218 case REQ_QUIT:
3219 return FALSE;
3221 default:
3222 report("Unknown key, press 'h' for help");
3223 return TRUE;
3224 }
3226 return TRUE;
3227 }
3230 /*
3231 * Pager backend
3232 */
3234 static bool
3235 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3236 {
3237 char *text = line->data;
3239 if (opt_line_number && draw_lineno(view, lineno))
3240 return TRUE;
3242 draw_text(view, line->type, text, TRUE);
3243 return TRUE;
3244 }
3246 static bool
3247 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3248 {
3249 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3250 char refbuf[SIZEOF_STR];
3251 char *ref = NULL;
3253 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3254 ref = chomp_string(refbuf);
3256 if (!ref || !*ref)
3257 return TRUE;
3259 /* This is the only fatal call, since it can "corrupt" the buffer. */
3260 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3261 return FALSE;
3263 return TRUE;
3264 }
3266 static void
3267 add_pager_refs(struct view *view, struct line *line)
3268 {
3269 char buf[SIZEOF_STR];
3270 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3271 struct ref **refs;
3272 size_t bufpos = 0, refpos = 0;
3273 const char *sep = "Refs: ";
3274 bool is_tag = FALSE;
3276 assert(line->type == LINE_COMMIT);
3278 refs = get_refs(commit_id);
3279 if (!refs) {
3280 if (view == VIEW(REQ_VIEW_DIFF))
3281 goto try_add_describe_ref;
3282 return;
3283 }
3285 do {
3286 struct ref *ref = refs[refpos];
3287 const char *fmt = ref->tag ? "%s[%s]" :
3288 ref->remote ? "%s<%s>" : "%s%s";
3290 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3291 return;
3292 sep = ", ";
3293 if (ref->tag)
3294 is_tag = TRUE;
3295 } while (refs[refpos++]->next);
3297 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3298 try_add_describe_ref:
3299 /* Add <tag>-g<commit_id> "fake" reference. */
3300 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3301 return;
3302 }
3304 if (bufpos == 0)
3305 return;
3307 add_line_text(view, buf, LINE_PP_REFS);
3308 }
3310 static bool
3311 pager_read(struct view *view, char *data)
3312 {
3313 struct line *line;
3315 if (!data)
3316 return TRUE;
3318 line = add_line_text(view, data, get_line_type(data));
3319 if (!line)
3320 return FALSE;
3322 if (line->type == LINE_COMMIT &&
3323 (view == VIEW(REQ_VIEW_DIFF) ||
3324 view == VIEW(REQ_VIEW_LOG)))
3325 add_pager_refs(view, line);
3327 return TRUE;
3328 }
3330 static enum request
3331 pager_request(struct view *view, enum request request, struct line *line)
3332 {
3333 int split = 0;
3335 if (request != REQ_ENTER)
3336 return request;
3338 if (line->type == LINE_COMMIT &&
3339 (view == VIEW(REQ_VIEW_LOG) ||
3340 view == VIEW(REQ_VIEW_PAGER))) {
3341 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3342 split = 1;
3343 }
3345 /* Always scroll the view even if it was split. That way
3346 * you can use Enter to scroll through the log view and
3347 * split open each commit diff. */
3348 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3350 /* FIXME: A minor workaround. Scrolling the view will call report("")
3351 * but if we are scrolling a non-current view this won't properly
3352 * update the view title. */
3353 if (split)
3354 update_view_title(view);
3356 return REQ_NONE;
3357 }
3359 static bool
3360 pager_grep(struct view *view, struct line *line)
3361 {
3362 regmatch_t pmatch;
3363 char *text = line->data;
3365 if (!*text)
3366 return FALSE;
3368 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3369 return FALSE;
3371 return TRUE;
3372 }
3374 static void
3375 pager_select(struct view *view, struct line *line)
3376 {
3377 if (line->type == LINE_COMMIT) {
3378 char *text = (char *)line->data + STRING_SIZE("commit ");
3380 if (view != VIEW(REQ_VIEW_PAGER))
3381 string_copy_rev(view->ref, text);
3382 string_copy_rev(ref_commit, text);
3383 }
3384 }
3386 static struct view_ops pager_ops = {
3387 "line",
3388 NULL,
3389 NULL,
3390 pager_read,
3391 pager_draw,
3392 pager_request,
3393 pager_grep,
3394 pager_select,
3395 };
3397 static const char *log_argv[SIZEOF_ARG] = {
3398 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3399 };
3401 static enum request
3402 log_request(struct view *view, enum request request, struct line *line)
3403 {
3404 switch (request) {
3405 case REQ_REFRESH:
3406 load_refs();
3407 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3408 return REQ_NONE;
3409 default:
3410 return pager_request(view, request, line);
3411 }
3412 }
3414 static struct view_ops log_ops = {
3415 "line",
3416 log_argv,
3417 NULL,
3418 pager_read,
3419 pager_draw,
3420 log_request,
3421 pager_grep,
3422 pager_select,
3423 };
3425 static const char *diff_argv[SIZEOF_ARG] = {
3426 "git", "show", "--pretty=fuller", "--no-color", "--root",
3427 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3428 };
3430 static struct view_ops diff_ops = {
3431 "line",
3432 diff_argv,
3433 NULL,
3434 pager_read,
3435 pager_draw,
3436 pager_request,
3437 pager_grep,
3438 pager_select,
3439 };
3441 /*
3442 * Help backend
3443 */
3445 static bool
3446 help_open(struct view *view)
3447 {
3448 int lines = ARRAY_SIZE(req_info) + 2;
3449 int i;
3451 if (view->lines > 0)
3452 return TRUE;
3454 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3455 if (!req_info[i].request)
3456 lines++;
3458 lines += run_requests + 1;
3460 view->line = calloc(lines, sizeof(*view->line));
3461 if (!view->line)
3462 return FALSE;
3464 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3466 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3467 const char *key;
3469 if (req_info[i].request == REQ_NONE)
3470 continue;
3472 if (!req_info[i].request) {
3473 add_line_text(view, "", LINE_DEFAULT);
3474 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3475 continue;
3476 }
3478 key = get_key(req_info[i].request);
3479 if (!*key)
3480 key = "(no key defined)";
3482 add_line_format(view, LINE_DEFAULT, " %-25s %s",
3483 key, req_info[i].help);
3484 }
3486 if (run_requests) {
3487 add_line_text(view, "", LINE_DEFAULT);
3488 add_line_text(view, "External commands:", LINE_DEFAULT);
3489 }
3491 for (i = 0; i < run_requests; i++) {
3492 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3493 const char *key;
3494 char cmd[SIZEOF_STR];
3495 size_t bufpos;
3496 int argc;
3498 if (!req)
3499 continue;
3501 key = get_key_name(req->key);
3502 if (!*key)
3503 key = "(no key defined)";
3505 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3506 if (!string_format_from(cmd, &bufpos, "%s%s",
3507 argc ? " " : "", req->argv[argc]))
3508 return REQ_NONE;
3510 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3511 keymap_table[req->keymap].name, key, cmd);
3512 }
3514 return TRUE;
3515 }
3517 static struct view_ops help_ops = {
3518 "line",
3519 NULL,
3520 help_open,
3521 NULL,
3522 pager_draw,
3523 pager_request,
3524 pager_grep,
3525 pager_select,
3526 };
3529 /*
3530 * Tree backend
3531 */
3533 struct tree_stack_entry {
3534 struct tree_stack_entry *prev; /* Entry below this in the stack */
3535 unsigned long lineno; /* Line number to restore */
3536 char *name; /* Position of name in opt_path */
3537 };
3539 /* The top of the path stack. */
3540 static struct tree_stack_entry *tree_stack = NULL;
3541 unsigned long tree_lineno = 0;
3543 static void
3544 pop_tree_stack_entry(void)
3545 {
3546 struct tree_stack_entry *entry = tree_stack;
3548 tree_lineno = entry->lineno;
3549 entry->name[0] = 0;
3550 tree_stack = entry->prev;
3551 free(entry);
3552 }
3554 static void
3555 push_tree_stack_entry(const char *name, unsigned long lineno)
3556 {
3557 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3558 size_t pathlen = strlen(opt_path);
3560 if (!entry)
3561 return;
3563 entry->prev = tree_stack;
3564 entry->name = opt_path + pathlen;
3565 tree_stack = entry;
3567 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3568 pop_tree_stack_entry();
3569 return;
3570 }
3572 /* Move the current line to the first tree entry. */
3573 tree_lineno = 1;
3574 entry->lineno = lineno;
3575 }
3577 /* Parse output from git-ls-tree(1):
3578 *
3579 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3580 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3581 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3582 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3583 */
3585 #define SIZEOF_TREE_ATTR \
3586 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3588 #define TREE_UP_FORMAT "040000 tree %s\t.."
3590 static const char *
3591 tree_path(struct line *line)
3592 {
3593 const char *path = line->data;
3595 return path + SIZEOF_TREE_ATTR;
3596 }
3598 static int
3599 tree_compare_entry(struct line *line1, struct line *line2)
3600 {
3601 if (line1->type != line2->type)
3602 return line1->type == LINE_TREE_DIR ? -1 : 1;
3603 return strcmp(tree_path(line1), tree_path(line2));
3604 }
3606 static bool
3607 tree_read(struct view *view, char *text)
3608 {
3609 size_t textlen = text ? strlen(text) : 0;
3610 struct line *entry, *line;
3611 enum line_type type;
3613 if (!text)
3614 return TRUE;
3615 if (textlen <= SIZEOF_TREE_ATTR)
3616 return FALSE;
3618 type = text[STRING_SIZE("100644 ")] == 't'
3619 ? LINE_TREE_DIR : LINE_TREE_FILE;
3621 if (view->lines == 0 &&
3622 !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3623 return FALSE;
3625 /* Strip the path part ... */
3626 if (*opt_path) {
3627 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3628 size_t striplen = strlen(opt_path);
3629 char *path = text + SIZEOF_TREE_ATTR;
3631 if (pathlen > striplen)
3632 memmove(path, path + striplen,
3633 pathlen - striplen + 1);
3635 /* Insert "link" to parent directory. */
3636 if (view->lines == 1 &&
3637 !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3638 return FALSE;
3639 }
3641 entry = add_line_text(view, text, type);
3642 if (!entry)
3643 return FALSE;
3644 text = entry->data;
3646 /* Skip "Directory ..." and ".." line. */
3647 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3648 if (tree_compare_entry(line, entry) <= 0)
3649 continue;
3651 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3653 line->data = text;
3654 line->type = type;
3655 for (; line <= entry; line++)
3656 line->dirty = line->cleareol = 1;
3657 return TRUE;
3658 }
3660 if (tree_lineno > view->lineno) {
3661 view->lineno = tree_lineno;
3662 tree_lineno = 0;
3663 }
3665 return TRUE;
3666 }
3668 static void
3669 open_blob_editor()
3670 {
3671 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3672 int fd = mkstemp(file);
3674 if (fd == -1)
3675 report("Failed to create temporary file");
3676 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3677 report("Failed to save blob data to file");
3678 else
3679 open_editor(FALSE, file);
3680 if (fd != -1)
3681 unlink(file);
3682 }
3684 static enum request
3685 tree_request(struct view *view, enum request request, struct line *line)
3686 {
3687 enum open_flags flags;
3689 switch (request) {
3690 case REQ_VIEW_BLAME:
3691 if (line->type != LINE_TREE_FILE) {
3692 report("Blame only supported for files");
3693 return REQ_NONE;
3694 }
3696 string_copy(opt_ref, view->vid);
3697 return request;
3699 case REQ_EDIT:
3700 if (line->type != LINE_TREE_FILE) {
3701 report("Edit only supported for files");
3702 } else if (!is_head_commit(view->vid)) {
3703 open_blob_editor();
3704 } else {
3705 open_editor(TRUE, opt_file);
3706 }
3707 return REQ_NONE;
3709 case REQ_TREE_PARENT:
3710 if (!*opt_path) {
3711 /* quit view if at top of tree */
3712 return REQ_VIEW_CLOSE;
3713 }
3714 /* fake 'cd ..' */
3715 line = &view->line[1];
3716 break;
3718 case REQ_ENTER:
3719 break;
3721 default:
3722 return request;
3723 }
3725 /* Cleanup the stack if the tree view is at a different tree. */
3726 while (!*opt_path && tree_stack)
3727 pop_tree_stack_entry();
3729 switch (line->type) {
3730 case LINE_TREE_DIR:
3731 /* Depending on whether it is a subdir or parent (updir?) link
3732 * mangle the path buffer. */
3733 if (line == &view->line[1] && *opt_path) {
3734 pop_tree_stack_entry();
3736 } else {
3737 const char *basename = tree_path(line);
3739 push_tree_stack_entry(basename, view->lineno);
3740 }
3742 /* Trees and subtrees share the same ID, so they are not not
3743 * unique like blobs. */
3744 flags = OPEN_RELOAD;
3745 request = REQ_VIEW_TREE;
3746 break;
3748 case LINE_TREE_FILE:
3749 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3750 request = REQ_VIEW_BLOB;
3751 break;
3753 default:
3754 return TRUE;
3755 }
3757 open_view(view, request, flags);
3758 if (request == REQ_VIEW_TREE) {
3759 view->lineno = tree_lineno;
3760 }
3762 return REQ_NONE;
3763 }
3765 static void
3766 tree_select(struct view *view, struct line *line)
3767 {
3768 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3770 if (line->type == LINE_TREE_FILE) {
3771 string_copy_rev(ref_blob, text);
3772 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3774 } else if (line->type != LINE_TREE_DIR) {
3775 return;
3776 }
3778 string_copy_rev(view->ref, text);
3779 }
3781 static const char *tree_argv[SIZEOF_ARG] = {
3782 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3783 };
3785 static struct view_ops tree_ops = {
3786 "file",
3787 tree_argv,
3788 NULL,
3789 tree_read,
3790 pager_draw,
3791 tree_request,
3792 pager_grep,
3793 tree_select,
3794 };
3796 static bool
3797 blob_read(struct view *view, char *line)
3798 {
3799 if (!line)
3800 return TRUE;
3801 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3802 }
3804 static enum request
3805 blob_request(struct view *view, enum request request, struct line *line)
3806 {
3807 switch (request) {
3808 case REQ_EDIT:
3809 open_blob_editor();
3810 return REQ_NONE;
3811 default:
3812 return pager_request(view, request, line);
3813 }
3814 }
3816 static const char *blob_argv[SIZEOF_ARG] = {
3817 "git", "cat-file", "blob", "%(blob)", NULL
3818 };
3820 static struct view_ops blob_ops = {
3821 "line",
3822 blob_argv,
3823 NULL,
3824 blob_read,
3825 pager_draw,
3826 blob_request,
3827 pager_grep,
3828 pager_select,
3829 };
3831 /*
3832 * Blame backend
3833 *
3834 * Loading the blame view is a two phase job:
3835 *
3836 * 1. File content is read either using opt_file from the
3837 * filesystem or using git-cat-file.
3838 * 2. Then blame information is incrementally added by
3839 * reading output from git-blame.
3840 */
3842 static const char *blame_head_argv[] = {
3843 "git", "blame", "--incremental", "--", "%(file)", NULL
3844 };
3846 static const char *blame_ref_argv[] = {
3847 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3848 };
3850 static const char *blame_cat_file_argv[] = {
3851 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3852 };
3854 struct blame_commit {
3855 char id[SIZEOF_REV]; /* SHA1 ID. */
3856 char title[128]; /* First line of the commit message. */
3857 char author[75]; /* Author of the commit. */
3858 struct tm time; /* Date from the author ident. */
3859 char filename[128]; /* Name of file. */
3860 };
3862 struct blame {
3863 struct blame_commit *commit;
3864 char text[1];
3865 };
3867 static bool
3868 blame_open(struct view *view)
3869 {
3870 if (*opt_ref || !io_open(&view->io, opt_file)) {
3871 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3872 return FALSE;
3873 }
3875 setup_update(view, opt_file);
3876 string_format(view->ref, "%s ...", opt_file);
3878 return TRUE;
3879 }
3881 static struct blame_commit *
3882 get_blame_commit(struct view *view, const char *id)
3883 {
3884 size_t i;
3886 for (i = 0; i < view->lines; i++) {
3887 struct blame *blame = view->line[i].data;
3889 if (!blame->commit)
3890 continue;
3892 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3893 return blame->commit;
3894 }
3896 {
3897 struct blame_commit *commit = calloc(1, sizeof(*commit));
3899 if (commit)
3900 string_ncopy(commit->id, id, SIZEOF_REV);
3901 return commit;
3902 }
3903 }
3905 static bool
3906 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3907 {
3908 const char *pos = *posref;
3910 *posref = NULL;
3911 pos = strchr(pos + 1, ' ');
3912 if (!pos || !isdigit(pos[1]))
3913 return FALSE;
3914 *number = atoi(pos + 1);
3915 if (*number < min || *number > max)
3916 return FALSE;
3918 *posref = pos;
3919 return TRUE;
3920 }
3922 static struct blame_commit *
3923 parse_blame_commit(struct view *view, const char *text, int *blamed)
3924 {
3925 struct blame_commit *commit;
3926 struct blame *blame;
3927 const char *pos = text + SIZEOF_REV - 1;
3928 size_t lineno;
3929 size_t group;
3931 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3932 return NULL;
3934 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3935 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3936 return NULL;
3938 commit = get_blame_commit(view, text);
3939 if (!commit)
3940 return NULL;
3942 *blamed += group;
3943 while (group--) {
3944 struct line *line = &view->line[lineno + group - 1];
3946 blame = line->data;
3947 blame->commit = commit;
3948 line->dirty = 1;
3949 }
3951 return commit;
3952 }
3954 static bool
3955 blame_read_file(struct view *view, const char *line, bool *read_file)
3956 {
3957 if (!line) {
3958 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3959 struct io io = {};
3961 if (view->lines == 0 && !view->parent)
3962 die("No blame exist for %s", view->vid);
3964 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3965 report("Failed to load blame data");
3966 return TRUE;
3967 }
3969 done_io(view->pipe);
3970 view->io = io;
3971 *read_file = FALSE;
3972 return FALSE;
3974 } else {
3975 size_t linelen = strlen(line);
3976 struct blame *blame = malloc(sizeof(*blame) + linelen);
3978 blame->commit = NULL;
3979 strncpy(blame->text, line, linelen);
3980 blame->text[linelen] = 0;
3981 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3982 }
3983 }
3985 static bool
3986 match_blame_header(const char *name, char **line)
3987 {
3988 size_t namelen = strlen(name);
3989 bool matched = !strncmp(name, *line, namelen);
3991 if (matched)
3992 *line += namelen;
3994 return matched;
3995 }
3997 static bool
3998 blame_read(struct view *view, char *line)
3999 {
4000 static struct blame_commit *commit = NULL;
4001 static int blamed = 0;
4002 static time_t author_time;
4003 static bool read_file = TRUE;
4005 if (read_file)
4006 return blame_read_file(view, line, &read_file);
4008 if (!line) {
4009 /* Reset all! */
4010 commit = NULL;
4011 blamed = 0;
4012 read_file = TRUE;
4013 string_format(view->ref, "%s", view->vid);
4014 if (view_is_displayed(view)) {
4015 update_view_title(view);
4016 redraw_view_from(view, 0);
4017 }
4018 return TRUE;
4019 }
4021 if (!commit) {
4022 commit = parse_blame_commit(view, line, &blamed);
4023 string_format(view->ref, "%s %2d%%", view->vid,
4024 blamed * 100 / view->lines);
4026 } else if (match_blame_header("author ", &line)) {
4027 string_ncopy(commit->author, line, strlen(line));
4029 } else if (match_blame_header("author-time ", &line)) {
4030 author_time = (time_t) atol(line);
4032 } else if (match_blame_header("author-tz ", &line)) {
4033 long tz;
4035 tz = ('0' - line[1]) * 60 * 60 * 10;
4036 tz += ('0' - line[2]) * 60 * 60;
4037 tz += ('0' - line[3]) * 60;
4038 tz += ('0' - line[4]) * 60;
4040 if (line[0] == '-')
4041 tz = -tz;
4043 author_time -= tz;
4044 gmtime_r(&author_time, &commit->time);
4046 } else if (match_blame_header("summary ", &line)) {
4047 string_ncopy(commit->title, line, strlen(line));
4049 } else if (match_blame_header("filename ", &line)) {
4050 string_ncopy(commit->filename, line, strlen(line));
4051 commit = NULL;
4052 }
4054 return TRUE;
4055 }
4057 static bool
4058 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4059 {
4060 struct blame *blame = line->data;
4061 struct tm *time = NULL;
4062 const char *id = NULL, *author = NULL;
4064 if (blame->commit && *blame->commit->filename) {
4065 id = blame->commit->id;
4066 author = blame->commit->author;
4067 time = &blame->commit->time;
4068 }
4070 if (opt_date && draw_date(view, time))
4071 return TRUE;
4073 if (opt_author &&
4074 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4075 return TRUE;
4077 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4078 return TRUE;
4080 if (draw_lineno(view, lineno))
4081 return TRUE;
4083 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4084 return TRUE;
4085 }
4087 static bool
4088 check_blame_commit(struct blame *blame)
4089 {
4090 if (!blame->commit)
4091 report("Commit data not loaded yet");
4092 else if (!strcmp(blame->commit->id, NULL_ID))
4093 report("No commit exist for the selected line");
4094 else
4095 return TRUE;
4096 return FALSE;
4097 }
4099 static enum request
4100 blame_request(struct view *view, enum request request, struct line *line)
4101 {
4102 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4103 struct blame *blame = line->data;
4105 switch (request) {
4106 case REQ_VIEW_BLAME:
4107 if (check_blame_commit(blame)) {
4108 string_copy(opt_ref, blame->commit->id);
4109 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4110 }
4111 break;
4113 case REQ_ENTER:
4114 if (!blame->commit) {
4115 report("No commit loaded yet");
4116 break;
4117 }
4119 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4120 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4121 break;
4123 if (!strcmp(blame->commit->id, NULL_ID)) {
4124 struct view *diff = VIEW(REQ_VIEW_DIFF);
4125 const char *diff_index_argv[] = {
4126 "git", "diff-index", "--root", "--cached",
4127 "--patch-with-stat", "-C", "-M",
4128 "HEAD", "--", view->vid, NULL
4129 };
4131 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4132 report("Failed to allocate diff command");
4133 break;
4134 }
4135 flags |= OPEN_PREPARED;
4136 }
4138 open_view(view, REQ_VIEW_DIFF, flags);
4139 break;
4141 default:
4142 return request;
4143 }
4145 return REQ_NONE;
4146 }
4148 static bool
4149 blame_grep(struct view *view, struct line *line)
4150 {
4151 struct blame *blame = line->data;
4152 struct blame_commit *commit = blame->commit;
4153 regmatch_t pmatch;
4155 #define MATCH(text, on) \
4156 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4158 if (commit) {
4159 char buf[DATE_COLS + 1];
4161 if (MATCH(commit->title, 1) ||
4162 MATCH(commit->author, opt_author) ||
4163 MATCH(commit->id, opt_date))
4164 return TRUE;
4166 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4167 MATCH(buf, 1))
4168 return TRUE;
4169 }
4171 return MATCH(blame->text, 1);
4173 #undef MATCH
4174 }
4176 static void
4177 blame_select(struct view *view, struct line *line)
4178 {
4179 struct blame *blame = line->data;
4180 struct blame_commit *commit = blame->commit;
4182 if (!commit)
4183 return;
4185 if (!strcmp(commit->id, NULL_ID))
4186 string_ncopy(ref_commit, "HEAD", 4);
4187 else
4188 string_copy_rev(ref_commit, commit->id);
4189 }
4191 static struct view_ops blame_ops = {
4192 "line",
4193 NULL,
4194 blame_open,
4195 blame_read,
4196 blame_draw,
4197 blame_request,
4198 blame_grep,
4199 blame_select,
4200 };
4202 /*
4203 * Status backend
4204 */
4206 struct status {
4207 char status;
4208 struct {
4209 mode_t mode;
4210 char rev[SIZEOF_REV];
4211 char name[SIZEOF_STR];
4212 } old;
4213 struct {
4214 mode_t mode;
4215 char rev[SIZEOF_REV];
4216 char name[SIZEOF_STR];
4217 } new;
4218 };
4220 static char status_onbranch[SIZEOF_STR];
4221 static struct status stage_status;
4222 static enum line_type stage_line_type;
4223 static size_t stage_chunks;
4224 static int *stage_chunk;
4226 /* This should work even for the "On branch" line. */
4227 static inline bool
4228 status_has_none(struct view *view, struct line *line)
4229 {
4230 return line < view->line + view->lines && !line[1].data;
4231 }
4233 /* Get fields from the diff line:
4234 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4235 */
4236 static inline bool
4237 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4238 {
4239 const char *old_mode = buf + 1;
4240 const char *new_mode = buf + 8;
4241 const char *old_rev = buf + 15;
4242 const char *new_rev = buf + 56;
4243 const char *status = buf + 97;
4245 if (bufsize < 98 ||
4246 old_mode[-1] != ':' ||
4247 new_mode[-1] != ' ' ||
4248 old_rev[-1] != ' ' ||
4249 new_rev[-1] != ' ' ||
4250 status[-1] != ' ')
4251 return FALSE;
4253 file->status = *status;
4255 string_copy_rev(file->old.rev, old_rev);
4256 string_copy_rev(file->new.rev, new_rev);
4258 file->old.mode = strtoul(old_mode, NULL, 8);
4259 file->new.mode = strtoul(new_mode, NULL, 8);
4261 file->old.name[0] = file->new.name[0] = 0;
4263 return TRUE;
4264 }
4266 static bool
4267 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4268 {
4269 struct status *file = NULL;
4270 struct status *unmerged = NULL;
4271 char *buf;
4272 struct io io = {};
4274 if (!run_io(&io, argv, NULL, IO_RD))
4275 return FALSE;
4277 add_line_data(view, NULL, type);
4279 while ((buf = io_get(&io, 0, TRUE))) {
4280 if (!file) {
4281 file = calloc(1, sizeof(*file));
4282 if (!file || !add_line_data(view, file, type))
4283 goto error_out;
4284 }
4286 /* Parse diff info part. */
4287 if (status) {
4288 file->status = status;
4289 if (status == 'A')
4290 string_copy(file->old.rev, NULL_ID);
4292 } else if (!file->status) {
4293 if (!status_get_diff(file, buf, strlen(buf)))
4294 goto error_out;
4296 buf = io_get(&io, 0, TRUE);
4297 if (!buf)
4298 break;
4300 /* Collapse all 'M'odified entries that follow a
4301 * associated 'U'nmerged entry. */
4302 if (file->status == 'U') {
4303 unmerged = file;
4305 } else if (unmerged) {
4306 int collapse = !strcmp(buf, unmerged->new.name);
4308 unmerged = NULL;
4309 if (collapse) {
4310 free(file);
4311 view->lines--;
4312 continue;
4313 }
4314 }
4315 }
4317 /* Grab the old name for rename/copy. */
4318 if (!*file->old.name &&
4319 (file->status == 'R' || file->status == 'C')) {
4320 string_ncopy(file->old.name, buf, strlen(buf));
4322 buf = io_get(&io, 0, TRUE);
4323 if (!buf)
4324 break;
4325 }
4327 /* git-ls-files just delivers a NUL separated list of
4328 * file names similar to the second half of the
4329 * git-diff-* output. */
4330 string_ncopy(file->new.name, buf, strlen(buf));
4331 if (!*file->old.name)
4332 string_copy(file->old.name, file->new.name);
4333 file = NULL;
4334 }
4336 if (io_error(&io)) {
4337 error_out:
4338 done_io(&io);
4339 return FALSE;
4340 }
4342 if (!view->line[view->lines - 1].data)
4343 add_line_data(view, NULL, LINE_STAT_NONE);
4345 done_io(&io);
4346 return TRUE;
4347 }
4349 /* Don't show unmerged entries in the staged section. */
4350 static const char *status_diff_index_argv[] = {
4351 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4352 "--cached", "-M", "HEAD", NULL
4353 };
4355 static const char *status_diff_files_argv[] = {
4356 "git", "diff-files", "-z", NULL
4357 };
4359 static const char *status_list_other_argv[] = {
4360 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4361 };
4363 static const char *status_list_no_head_argv[] = {
4364 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4365 };
4367 static const char *update_index_argv[] = {
4368 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4369 };
4371 /* First parse staged info using git-diff-index(1), then parse unstaged
4372 * info using git-diff-files(1), and finally untracked files using
4373 * git-ls-files(1). */
4374 static bool
4375 status_open(struct view *view)
4376 {
4377 unsigned long prev_lineno = view->lineno;
4379 reset_view(view);
4381 add_line_data(view, NULL, LINE_STAT_HEAD);
4382 if (is_initial_commit())
4383 string_copy(status_onbranch, "Initial commit");
4384 else if (!*opt_head)
4385 string_copy(status_onbranch, "Not currently on any branch");
4386 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4387 return FALSE;
4389 run_io_bg(update_index_argv);
4391 if (is_initial_commit()) {
4392 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4393 return FALSE;
4394 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4395 return FALSE;
4396 }
4398 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4399 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4400 return FALSE;
4402 /* If all went well restore the previous line number to stay in
4403 * the context or select a line with something that can be
4404 * updated. */
4405 if (prev_lineno >= view->lines)
4406 prev_lineno = view->lines - 1;
4407 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4408 prev_lineno++;
4409 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4410 prev_lineno--;
4412 /* If the above fails, always skip the "On branch" line. */
4413 if (prev_lineno < view->lines)
4414 view->lineno = prev_lineno;
4415 else
4416 view->lineno = 1;
4418 if (view->lineno < view->offset)
4419 view->offset = view->lineno;
4420 else if (view->offset + view->height <= view->lineno)
4421 view->offset = view->lineno - view->height + 1;
4423 return TRUE;
4424 }
4426 static bool
4427 status_draw(struct view *view, struct line *line, unsigned int lineno)
4428 {
4429 struct status *status = line->data;
4430 enum line_type type;
4431 const char *text;
4433 if (!status) {
4434 switch (line->type) {
4435 case LINE_STAT_STAGED:
4436 type = LINE_STAT_SECTION;
4437 text = "Changes to be committed:";
4438 break;
4440 case LINE_STAT_UNSTAGED:
4441 type = LINE_STAT_SECTION;
4442 text = "Changed but not updated:";
4443 break;
4445 case LINE_STAT_UNTRACKED:
4446 type = LINE_STAT_SECTION;
4447 text = "Untracked files:";
4448 break;
4450 case LINE_STAT_NONE:
4451 type = LINE_DEFAULT;
4452 text = " (no files)";
4453 break;
4455 case LINE_STAT_HEAD:
4456 type = LINE_STAT_HEAD;
4457 text = status_onbranch;
4458 break;
4460 default:
4461 return FALSE;
4462 }
4463 } else {
4464 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4466 buf[0] = status->status;
4467 if (draw_text(view, line->type, buf, TRUE))
4468 return TRUE;
4469 type = LINE_DEFAULT;
4470 text = status->new.name;
4471 }
4473 draw_text(view, type, text, TRUE);
4474 return TRUE;
4475 }
4477 static enum request
4478 status_enter(struct view *view, struct line *line)
4479 {
4480 struct status *status = line->data;
4481 const char *oldpath = status ? status->old.name : NULL;
4482 /* Diffs for unmerged entries are empty when passing the new
4483 * path, so leave it empty. */
4484 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4485 const char *info;
4486 enum open_flags split;
4487 struct view *stage = VIEW(REQ_VIEW_STAGE);
4489 if (line->type == LINE_STAT_NONE ||
4490 (!status && line[1].type == LINE_STAT_NONE)) {
4491 report("No file to diff");
4492 return REQ_NONE;
4493 }
4495 switch (line->type) {
4496 case LINE_STAT_STAGED:
4497 if (is_initial_commit()) {
4498 const char *no_head_diff_argv[] = {
4499 "git", "diff", "--no-color", "--patch-with-stat",
4500 "--", "/dev/null", newpath, NULL
4501 };
4503 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4504 return REQ_QUIT;
4505 } else {
4506 const char *index_show_argv[] = {
4507 "git", "diff-index", "--root", "--patch-with-stat",
4508 "-C", "-M", "--cached", "HEAD", "--",
4509 oldpath, newpath, NULL
4510 };
4512 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4513 return REQ_QUIT;
4514 }
4516 if (status)
4517 info = "Staged changes to %s";
4518 else
4519 info = "Staged changes";
4520 break;
4522 case LINE_STAT_UNSTAGED:
4523 {
4524 const char *files_show_argv[] = {
4525 "git", "diff-files", "--root", "--patch-with-stat",
4526 "-C", "-M", "--", oldpath, newpath, NULL
4527 };
4529 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4530 return REQ_QUIT;
4531 if (status)
4532 info = "Unstaged changes to %s";
4533 else
4534 info = "Unstaged changes";
4535 break;
4536 }
4537 case LINE_STAT_UNTRACKED:
4538 if (!newpath) {
4539 report("No file to show");
4540 return REQ_NONE;
4541 }
4543 if (!suffixcmp(status->new.name, -1, "/")) {
4544 report("Cannot display a directory");
4545 return REQ_NONE;
4546 }
4548 if (!prepare_update_file(stage, newpath))
4549 return REQ_QUIT;
4550 info = "Untracked file %s";
4551 break;
4553 case LINE_STAT_HEAD:
4554 return REQ_NONE;
4556 default:
4557 die("line type %d not handled in switch", line->type);
4558 }
4560 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4561 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4562 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4563 if (status) {
4564 stage_status = *status;
4565 } else {
4566 memset(&stage_status, 0, sizeof(stage_status));
4567 }
4569 stage_line_type = line->type;
4570 stage_chunks = 0;
4571 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4572 }
4574 return REQ_NONE;
4575 }
4577 static bool
4578 status_exists(struct status *status, enum line_type type)
4579 {
4580 struct view *view = VIEW(REQ_VIEW_STATUS);
4581 struct line *line;
4583 for (line = view->line; line < view->line + view->lines; line++) {
4584 struct status *pos = line->data;
4586 if (line->type != type)
4587 continue;
4588 if (!pos && (!status || !status->status))
4589 return TRUE;
4590 if (pos && !strcmp(status->new.name, pos->new.name))
4591 return TRUE;
4592 }
4594 return FALSE;
4595 }
4598 static bool
4599 status_update_prepare(struct io *io, enum line_type type)
4600 {
4601 const char *staged_argv[] = {
4602 "git", "update-index", "-z", "--index-info", NULL
4603 };
4604 const char *others_argv[] = {
4605 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4606 };
4608 switch (type) {
4609 case LINE_STAT_STAGED:
4610 return run_io(io, staged_argv, opt_cdup, IO_WR);
4612 case LINE_STAT_UNSTAGED:
4613 return run_io(io, others_argv, opt_cdup, IO_WR);
4615 case LINE_STAT_UNTRACKED:
4616 return run_io(io, others_argv, NULL, IO_WR);
4618 default:
4619 die("line type %d not handled in switch", type);
4620 return FALSE;
4621 }
4622 }
4624 static bool
4625 status_update_write(struct io *io, struct status *status, enum line_type type)
4626 {
4627 char buf[SIZEOF_STR];
4628 size_t bufsize = 0;
4630 switch (type) {
4631 case LINE_STAT_STAGED:
4632 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4633 status->old.mode,
4634 status->old.rev,
4635 status->old.name, 0))
4636 return FALSE;
4637 break;
4639 case LINE_STAT_UNSTAGED:
4640 case LINE_STAT_UNTRACKED:
4641 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4642 return FALSE;
4643 break;
4645 default:
4646 die("line type %d not handled in switch", type);
4647 }
4649 return io_write(io, buf, bufsize);
4650 }
4652 static bool
4653 status_update_file(struct status *status, enum line_type type)
4654 {
4655 struct io io = {};
4656 bool result;
4658 if (!status_update_prepare(&io, type))
4659 return FALSE;
4661 result = status_update_write(&io, status, type);
4662 done_io(&io);
4663 return result;
4664 }
4666 static bool
4667 status_update_files(struct view *view, struct line *line)
4668 {
4669 struct io io = {};
4670 bool result = TRUE;
4671 struct line *pos = view->line + view->lines;
4672 int files = 0;
4673 int file, done;
4675 if (!status_update_prepare(&io, line->type))
4676 return FALSE;
4678 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4679 files++;
4681 for (file = 0, done = 0; result && file < files; line++, file++) {
4682 int almost_done = file * 100 / files;
4684 if (almost_done > done) {
4685 done = almost_done;
4686 string_format(view->ref, "updating file %u of %u (%d%% done)",
4687 file, files, done);
4688 update_view_title(view);
4689 }
4690 result = status_update_write(&io, line->data, line->type);
4691 }
4693 done_io(&io);
4694 return result;
4695 }
4697 static bool
4698 status_update(struct view *view)
4699 {
4700 struct line *line = &view->line[view->lineno];
4702 assert(view->lines);
4704 if (!line->data) {
4705 /* This should work even for the "On branch" line. */
4706 if (line < view->line + view->lines && !line[1].data) {
4707 report("Nothing to update");
4708 return FALSE;
4709 }
4711 if (!status_update_files(view, line + 1)) {
4712 report("Failed to update file status");
4713 return FALSE;
4714 }
4716 } else if (!status_update_file(line->data, line->type)) {
4717 report("Failed to update file status");
4718 return FALSE;
4719 }
4721 return TRUE;
4722 }
4724 static bool
4725 status_revert(struct status *status, enum line_type type, bool has_none)
4726 {
4727 if (!status || type != LINE_STAT_UNSTAGED) {
4728 if (type == LINE_STAT_STAGED) {
4729 report("Cannot revert changes to staged files");
4730 } else if (type == LINE_STAT_UNTRACKED) {
4731 report("Cannot revert changes to untracked files");
4732 } else if (has_none) {
4733 report("Nothing to revert");
4734 } else {
4735 report("Cannot revert changes to multiple files");
4736 }
4737 return FALSE;
4739 } else {
4740 const char *checkout_argv[] = {
4741 "git", "checkout", "--", status->old.name, NULL
4742 };
4744 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4745 return FALSE;
4746 return run_io_fg(checkout_argv, opt_cdup);
4747 }
4748 }
4750 static enum request
4751 status_request(struct view *view, enum request request, struct line *line)
4752 {
4753 struct status *status = line->data;
4755 switch (request) {
4756 case REQ_STATUS_UPDATE:
4757 if (!status_update(view))
4758 return REQ_NONE;
4759 break;
4761 case REQ_STATUS_REVERT:
4762 if (!status_revert(status, line->type, status_has_none(view, line)))
4763 return REQ_NONE;
4764 break;
4766 case REQ_STATUS_MERGE:
4767 if (!status || status->status != 'U') {
4768 report("Merging only possible for files with unmerged status ('U').");
4769 return REQ_NONE;
4770 }
4771 open_mergetool(status->new.name);
4772 break;
4774 case REQ_EDIT:
4775 if (!status)
4776 return request;
4777 if (status->status == 'D') {
4778 report("File has been deleted.");
4779 return REQ_NONE;
4780 }
4782 open_editor(status->status != '?', status->new.name);
4783 break;
4785 case REQ_VIEW_BLAME:
4786 if (status) {
4787 string_copy(opt_file, status->new.name);
4788 opt_ref[0] = 0;
4789 }
4790 return request;
4792 case REQ_ENTER:
4793 /* After returning the status view has been split to
4794 * show the stage view. No further reloading is
4795 * necessary. */
4796 status_enter(view, line);
4797 return REQ_NONE;
4799 case REQ_REFRESH:
4800 /* Simply reload the view. */
4801 break;
4803 default:
4804 return request;
4805 }
4807 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4809 return REQ_NONE;
4810 }
4812 static void
4813 status_select(struct view *view, struct line *line)
4814 {
4815 struct status *status = line->data;
4816 char file[SIZEOF_STR] = "all files";
4817 const char *text;
4818 const char *key;
4820 if (status && !string_format(file, "'%s'", status->new.name))
4821 return;
4823 if (!status && line[1].type == LINE_STAT_NONE)
4824 line++;
4826 switch (line->type) {
4827 case LINE_STAT_STAGED:
4828 text = "Press %s to unstage %s for commit";
4829 break;
4831 case LINE_STAT_UNSTAGED:
4832 text = "Press %s to stage %s for commit";
4833 break;
4835 case LINE_STAT_UNTRACKED:
4836 text = "Press %s to stage %s for addition";
4837 break;
4839 case LINE_STAT_HEAD:
4840 case LINE_STAT_NONE:
4841 text = "Nothing to update";
4842 break;
4844 default:
4845 die("line type %d not handled in switch", line->type);
4846 }
4848 if (status && status->status == 'U') {
4849 text = "Press %s to resolve conflict in %s";
4850 key = get_key(REQ_STATUS_MERGE);
4852 } else {
4853 key = get_key(REQ_STATUS_UPDATE);
4854 }
4856 string_format(view->ref, text, key, file);
4857 }
4859 static bool
4860 status_grep(struct view *view, struct line *line)
4861 {
4862 struct status *status = line->data;
4863 enum { S_STATUS, S_NAME, S_END } state;
4864 char buf[2] = "?";
4865 regmatch_t pmatch;
4867 if (!status)
4868 return FALSE;
4870 for (state = S_STATUS; state < S_END; state++) {
4871 const char *text;
4873 switch (state) {
4874 case S_NAME: text = status->new.name; break;
4875 case S_STATUS:
4876 buf[0] = status->status;
4877 text = buf;
4878 break;
4880 default:
4881 return FALSE;
4882 }
4884 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4885 return TRUE;
4886 }
4888 return FALSE;
4889 }
4891 static struct view_ops status_ops = {
4892 "file",
4893 NULL,
4894 status_open,
4895 NULL,
4896 status_draw,
4897 status_request,
4898 status_grep,
4899 status_select,
4900 };
4903 static bool
4904 stage_diff_write(struct io *io, struct line *line, struct line *end)
4905 {
4906 while (line < end) {
4907 if (!io_write(io, line->data, strlen(line->data)) ||
4908 !io_write(io, "\n", 1))
4909 return FALSE;
4910 line++;
4911 if (line->type == LINE_DIFF_CHUNK ||
4912 line->type == LINE_DIFF_HEADER)
4913 break;
4914 }
4916 return TRUE;
4917 }
4919 static struct line *
4920 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4921 {
4922 for (; view->line < line; line--)
4923 if (line->type == type)
4924 return line;
4926 return NULL;
4927 }
4929 static bool
4930 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4931 {
4932 const char *apply_argv[SIZEOF_ARG] = {
4933 "git", "apply", "--whitespace=nowarn", NULL
4934 };
4935 struct line *diff_hdr;
4936 struct io io = {};
4937 int argc = 3;
4939 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4940 if (!diff_hdr)
4941 return FALSE;
4943 if (!revert)
4944 apply_argv[argc++] = "--cached";
4945 if (revert || stage_line_type == LINE_STAT_STAGED)
4946 apply_argv[argc++] = "-R";
4947 apply_argv[argc++] = "-";
4948 apply_argv[argc++] = NULL;
4949 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4950 return FALSE;
4952 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4953 !stage_diff_write(&io, chunk, view->line + view->lines))
4954 chunk = NULL;
4956 done_io(&io);
4957 run_io_bg(update_index_argv);
4959 return chunk ? TRUE : FALSE;
4960 }
4962 static bool
4963 stage_update(struct view *view, struct line *line)
4964 {
4965 struct line *chunk = NULL;
4967 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4968 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4970 if (chunk) {
4971 if (!stage_apply_chunk(view, chunk, FALSE)) {
4972 report("Failed to apply chunk");
4973 return FALSE;
4974 }
4976 } else if (!stage_status.status) {
4977 view = VIEW(REQ_VIEW_STATUS);
4979 for (line = view->line; line < view->line + view->lines; line++)
4980 if (line->type == stage_line_type)
4981 break;
4983 if (!status_update_files(view, line + 1)) {
4984 report("Failed to update files");
4985 return FALSE;
4986 }
4988 } else if (!status_update_file(&stage_status, stage_line_type)) {
4989 report("Failed to update file");
4990 return FALSE;
4991 }
4993 return TRUE;
4994 }
4996 static bool
4997 stage_revert(struct view *view, struct line *line)
4998 {
4999 struct line *chunk = NULL;
5001 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5002 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5004 if (chunk) {
5005 if (!prompt_yesno("Are you sure you want to revert changes?"))
5006 return FALSE;
5008 if (!stage_apply_chunk(view, chunk, TRUE)) {
5009 report("Failed to revert chunk");
5010 return FALSE;
5011 }
5012 return TRUE;
5014 } else {
5015 return status_revert(stage_status.status ? &stage_status : NULL,
5016 stage_line_type, FALSE);
5017 }
5018 }
5021 static void
5022 stage_next(struct view *view, struct line *line)
5023 {
5024 int i;
5026 if (!stage_chunks) {
5027 static size_t alloc = 0;
5028 int *tmp;
5030 for (line = view->line; line < view->line + view->lines; line++) {
5031 if (line->type != LINE_DIFF_CHUNK)
5032 continue;
5034 tmp = realloc_items(stage_chunk, &alloc,
5035 stage_chunks, sizeof(*tmp));
5036 if (!tmp) {
5037 report("Allocation failure");
5038 return;
5039 }
5041 stage_chunk = tmp;
5042 stage_chunk[stage_chunks++] = line - view->line;
5043 }
5044 }
5046 for (i = 0; i < stage_chunks; i++) {
5047 if (stage_chunk[i] > view->lineno) {
5048 do_scroll_view(view, stage_chunk[i] - view->lineno);
5049 report("Chunk %d of %d", i + 1, stage_chunks);
5050 return;
5051 }
5052 }
5054 report("No next chunk found");
5055 }
5057 static enum request
5058 stage_request(struct view *view, enum request request, struct line *line)
5059 {
5060 switch (request) {
5061 case REQ_STATUS_UPDATE:
5062 if (!stage_update(view, line))
5063 return REQ_NONE;
5064 break;
5066 case REQ_STATUS_REVERT:
5067 if (!stage_revert(view, line))
5068 return REQ_NONE;
5069 break;
5071 case REQ_STAGE_NEXT:
5072 if (stage_line_type == LINE_STAT_UNTRACKED) {
5073 report("File is untracked; press %s to add",
5074 get_key(REQ_STATUS_UPDATE));
5075 return REQ_NONE;
5076 }
5077 stage_next(view, line);
5078 return REQ_NONE;
5080 case REQ_EDIT:
5081 if (!stage_status.new.name[0])
5082 return request;
5083 if (stage_status.status == 'D') {
5084 report("File has been deleted.");
5085 return REQ_NONE;
5086 }
5088 open_editor(stage_status.status != '?', stage_status.new.name);
5089 break;
5091 case REQ_REFRESH:
5092 /* Reload everything ... */
5093 break;
5095 case REQ_VIEW_BLAME:
5096 if (stage_status.new.name[0]) {
5097 string_copy(opt_file, stage_status.new.name);
5098 opt_ref[0] = 0;
5099 }
5100 return request;
5102 case REQ_ENTER:
5103 return pager_request(view, request, line);
5105 default:
5106 return request;
5107 }
5109 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5111 /* Check whether the staged entry still exists, and close the
5112 * stage view if it doesn't. */
5113 if (!status_exists(&stage_status, stage_line_type))
5114 return REQ_VIEW_CLOSE;
5116 if (stage_line_type == LINE_STAT_UNTRACKED) {
5117 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5118 report("Cannot display a directory");
5119 return REQ_NONE;
5120 }
5122 if (!prepare_update_file(view, stage_status.new.name)) {
5123 report("Failed to open file: %s", strerror(errno));
5124 return REQ_NONE;
5125 }
5126 }
5127 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5129 return REQ_NONE;
5130 }
5132 static struct view_ops stage_ops = {
5133 "line",
5134 NULL,
5135 NULL,
5136 pager_read,
5137 pager_draw,
5138 stage_request,
5139 pager_grep,
5140 pager_select,
5141 };
5144 /*
5145 * Revision graph
5146 */
5148 struct commit {
5149 char id[SIZEOF_REV]; /* SHA1 ID. */
5150 char title[128]; /* First line of the commit message. */
5151 char author[75]; /* Author of the commit. */
5152 struct tm time; /* Date from the author ident. */
5153 struct ref **refs; /* Repository references. */
5154 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5155 size_t graph_size; /* The width of the graph array. */
5156 bool has_parents; /* Rewritten --parents seen. */
5157 };
5159 /* Size of rev graph with no "padding" columns */
5160 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5162 struct rev_graph {
5163 struct rev_graph *prev, *next, *parents;
5164 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5165 size_t size;
5166 struct commit *commit;
5167 size_t pos;
5168 unsigned int boundary:1;
5169 };
5171 /* Parents of the commit being visualized. */
5172 static struct rev_graph graph_parents[4];
5174 /* The current stack of revisions on the graph. */
5175 static struct rev_graph graph_stacks[4] = {
5176 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5177 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5178 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5179 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5180 };
5182 static inline bool
5183 graph_parent_is_merge(struct rev_graph *graph)
5184 {
5185 return graph->parents->size > 1;
5186 }
5188 static inline void
5189 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5190 {
5191 struct commit *commit = graph->commit;
5193 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5194 commit->graph[commit->graph_size++] = symbol;
5195 }
5197 static void
5198 clear_rev_graph(struct rev_graph *graph)
5199 {
5200 graph->boundary = 0;
5201 graph->size = graph->pos = 0;
5202 graph->commit = NULL;
5203 memset(graph->parents, 0, sizeof(*graph->parents));
5204 }
5206 static void
5207 done_rev_graph(struct rev_graph *graph)
5208 {
5209 if (graph_parent_is_merge(graph) &&
5210 graph->pos < graph->size - 1 &&
5211 graph->next->size == graph->size + graph->parents->size - 1) {
5212 size_t i = graph->pos + graph->parents->size - 1;
5214 graph->commit->graph_size = i * 2;
5215 while (i < graph->next->size - 1) {
5216 append_to_rev_graph(graph, ' ');
5217 append_to_rev_graph(graph, '\\');
5218 i++;
5219 }
5220 }
5222 clear_rev_graph(graph);
5223 }
5225 static void
5226 push_rev_graph(struct rev_graph *graph, const char *parent)
5227 {
5228 int i;
5230 /* "Collapse" duplicate parents lines.
5231 *
5232 * FIXME: This needs to also update update the drawn graph but
5233 * for now it just serves as a method for pruning graph lines. */
5234 for (i = 0; i < graph->size; i++)
5235 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5236 return;
5238 if (graph->size < SIZEOF_REVITEMS) {
5239 string_copy_rev(graph->rev[graph->size++], parent);
5240 }
5241 }
5243 static chtype
5244 get_rev_graph_symbol(struct rev_graph *graph)
5245 {
5246 chtype symbol;
5248 if (graph->boundary)
5249 symbol = REVGRAPH_BOUND;
5250 else if (graph->parents->size == 0)
5251 symbol = REVGRAPH_INIT;
5252 else if (graph_parent_is_merge(graph))
5253 symbol = REVGRAPH_MERGE;
5254 else if (graph->pos >= graph->size)
5255 symbol = REVGRAPH_BRANCH;
5256 else
5257 symbol = REVGRAPH_COMMIT;
5259 return symbol;
5260 }
5262 static void
5263 draw_rev_graph(struct rev_graph *graph)
5264 {
5265 struct rev_filler {
5266 chtype separator, line;
5267 };
5268 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5269 static struct rev_filler fillers[] = {
5270 { ' ', '|' },
5271 { '`', '.' },
5272 { '\'', ' ' },
5273 { '/', ' ' },
5274 };
5275 chtype symbol = get_rev_graph_symbol(graph);
5276 struct rev_filler *filler;
5277 size_t i;
5279 if (opt_line_graphics)
5280 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5282 filler = &fillers[DEFAULT];
5284 for (i = 0; i < graph->pos; i++) {
5285 append_to_rev_graph(graph, filler->line);
5286 if (graph_parent_is_merge(graph->prev) &&
5287 graph->prev->pos == i)
5288 filler = &fillers[RSHARP];
5290 append_to_rev_graph(graph, filler->separator);
5291 }
5293 /* Place the symbol for this revision. */
5294 append_to_rev_graph(graph, symbol);
5296 if (graph->prev->size > graph->size)
5297 filler = &fillers[RDIAG];
5298 else
5299 filler = &fillers[DEFAULT];
5301 i++;
5303 for (; i < graph->size; i++) {
5304 append_to_rev_graph(graph, filler->separator);
5305 append_to_rev_graph(graph, filler->line);
5306 if (graph_parent_is_merge(graph->prev) &&
5307 i < graph->prev->pos + graph->parents->size)
5308 filler = &fillers[RSHARP];
5309 if (graph->prev->size > graph->size)
5310 filler = &fillers[LDIAG];
5311 }
5313 if (graph->prev->size > graph->size) {
5314 append_to_rev_graph(graph, filler->separator);
5315 if (filler->line != ' ')
5316 append_to_rev_graph(graph, filler->line);
5317 }
5318 }
5320 /* Prepare the next rev graph */
5321 static void
5322 prepare_rev_graph(struct rev_graph *graph)
5323 {
5324 size_t i;
5326 /* First, traverse all lines of revisions up to the active one. */
5327 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5328 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5329 break;
5331 push_rev_graph(graph->next, graph->rev[graph->pos]);
5332 }
5334 /* Interleave the new revision parent(s). */
5335 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5336 push_rev_graph(graph->next, graph->parents->rev[i]);
5338 /* Lastly, put any remaining revisions. */
5339 for (i = graph->pos + 1; i < graph->size; i++)
5340 push_rev_graph(graph->next, graph->rev[i]);
5341 }
5343 static void
5344 update_rev_graph(struct view *view, struct rev_graph *graph)
5345 {
5346 /* If this is the finalizing update ... */
5347 if (graph->commit)
5348 prepare_rev_graph(graph);
5350 /* Graph visualization needs a one rev look-ahead,
5351 * so the first update doesn't visualize anything. */
5352 if (!graph->prev->commit)
5353 return;
5355 if (view->lines > 2)
5356 view->line[view->lines - 3].dirty = 1;
5357 if (view->lines > 1)
5358 view->line[view->lines - 2].dirty = 1;
5359 draw_rev_graph(graph->prev);
5360 done_rev_graph(graph->prev->prev);
5361 }
5364 /*
5365 * Main view backend
5366 */
5368 static const char *main_argv[SIZEOF_ARG] = {
5369 "git", "log", "--no-color", "--pretty=raw", "--parents",
5370 "--topo-order", "%(head)", NULL
5371 };
5373 static bool
5374 main_draw(struct view *view, struct line *line, unsigned int lineno)
5375 {
5376 struct commit *commit = line->data;
5378 if (!*commit->author)
5379 return FALSE;
5381 if (opt_date && draw_date(view, &commit->time))
5382 return TRUE;
5384 if (opt_author &&
5385 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5386 return TRUE;
5388 if (opt_rev_graph && commit->graph_size &&
5389 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5390 return TRUE;
5392 if (opt_show_refs && commit->refs) {
5393 size_t i = 0;
5395 do {
5396 enum line_type type;
5398 if (commit->refs[i]->head)
5399 type = LINE_MAIN_HEAD;
5400 else if (commit->refs[i]->ltag)
5401 type = LINE_MAIN_LOCAL_TAG;
5402 else if (commit->refs[i]->tag)
5403 type = LINE_MAIN_TAG;
5404 else if (commit->refs[i]->tracked)
5405 type = LINE_MAIN_TRACKED;
5406 else if (commit->refs[i]->remote)
5407 type = LINE_MAIN_REMOTE;
5408 else
5409 type = LINE_MAIN_REF;
5411 if (draw_text(view, type, "[", TRUE) ||
5412 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5413 draw_text(view, type, "]", TRUE))
5414 return TRUE;
5416 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5417 return TRUE;
5418 } while (commit->refs[i++]->next);
5419 }
5421 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5422 return TRUE;
5423 }
5425 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5426 static bool
5427 main_read(struct view *view, char *line)
5428 {
5429 static struct rev_graph *graph = graph_stacks;
5430 enum line_type type;
5431 struct commit *commit;
5433 if (!line) {
5434 int i;
5436 if (!view->lines && !view->parent)
5437 die("No revisions match the given arguments.");
5438 if (view->lines > 0) {
5439 commit = view->line[view->lines - 1].data;
5440 view->line[view->lines - 1].dirty = 1;
5441 if (!*commit->author) {
5442 view->lines--;
5443 free(commit);
5444 graph->commit = NULL;
5445 }
5446 }
5447 update_rev_graph(view, graph);
5449 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5450 clear_rev_graph(&graph_stacks[i]);
5451 return TRUE;
5452 }
5454 type = get_line_type(line);
5455 if (type == LINE_COMMIT) {
5456 commit = calloc(1, sizeof(struct commit));
5457 if (!commit)
5458 return FALSE;
5460 line += STRING_SIZE("commit ");
5461 if (*line == '-') {
5462 graph->boundary = 1;
5463 line++;
5464 }
5466 string_copy_rev(commit->id, line);
5467 commit->refs = get_refs(commit->id);
5468 graph->commit = commit;
5469 add_line_data(view, commit, LINE_MAIN_COMMIT);
5471 while ((line = strchr(line, ' '))) {
5472 line++;
5473 push_rev_graph(graph->parents, line);
5474 commit->has_parents = TRUE;
5475 }
5476 return TRUE;
5477 }
5479 if (!view->lines)
5480 return TRUE;
5481 commit = view->line[view->lines - 1].data;
5483 switch (type) {
5484 case LINE_PARENT:
5485 if (commit->has_parents)
5486 break;
5487 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5488 break;
5490 case LINE_AUTHOR:
5491 {
5492 /* Parse author lines where the name may be empty:
5493 * author <email@address.tld> 1138474660 +0100
5494 */
5495 char *ident = line + STRING_SIZE("author ");
5496 char *nameend = strchr(ident, '<');
5497 char *emailend = strchr(ident, '>');
5499 if (!nameend || !emailend)
5500 break;
5502 update_rev_graph(view, graph);
5503 graph = graph->next;
5505 *nameend = *emailend = 0;
5506 ident = chomp_string(ident);
5507 if (!*ident) {
5508 ident = chomp_string(nameend + 1);
5509 if (!*ident)
5510 ident = "Unknown";
5511 }
5513 string_ncopy(commit->author, ident, strlen(ident));
5514 view->line[view->lines - 1].dirty = 1;
5516 /* Parse epoch and timezone */
5517 if (emailend[1] == ' ') {
5518 char *secs = emailend + 2;
5519 char *zone = strchr(secs, ' ');
5520 time_t time = (time_t) atol(secs);
5522 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5523 long tz;
5525 zone++;
5526 tz = ('0' - zone[1]) * 60 * 60 * 10;
5527 tz += ('0' - zone[2]) * 60 * 60;
5528 tz += ('0' - zone[3]) * 60;
5529 tz += ('0' - zone[4]) * 60;
5531 if (zone[0] == '-')
5532 tz = -tz;
5534 time -= tz;
5535 }
5537 gmtime_r(&time, &commit->time);
5538 }
5539 break;
5540 }
5541 default:
5542 /* Fill in the commit title if it has not already been set. */
5543 if (commit->title[0])
5544 break;
5546 /* Require titles to start with a non-space character at the
5547 * offset used by git log. */
5548 if (strncmp(line, " ", 4))
5549 break;
5550 line += 4;
5551 /* Well, if the title starts with a whitespace character,
5552 * try to be forgiving. Otherwise we end up with no title. */
5553 while (isspace(*line))
5554 line++;
5555 if (*line == '\0')
5556 break;
5557 /* FIXME: More graceful handling of titles; append "..." to
5558 * shortened titles, etc. */
5560 string_ncopy(commit->title, line, strlen(line));
5561 view->line[view->lines - 1].dirty = 1;
5562 }
5564 return TRUE;
5565 }
5567 static enum request
5568 main_request(struct view *view, enum request request, struct line *line)
5569 {
5570 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5572 switch (request) {
5573 case REQ_ENTER:
5574 open_view(view, REQ_VIEW_DIFF, flags);
5575 break;
5576 case REQ_REFRESH:
5577 load_refs();
5578 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5579 break;
5580 default:
5581 return request;
5582 }
5584 return REQ_NONE;
5585 }
5587 static bool
5588 grep_refs(struct ref **refs, regex_t *regex)
5589 {
5590 regmatch_t pmatch;
5591 size_t i = 0;
5593 if (!refs)
5594 return FALSE;
5595 do {
5596 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5597 return TRUE;
5598 } while (refs[i++]->next);
5600 return FALSE;
5601 }
5603 static bool
5604 main_grep(struct view *view, struct line *line)
5605 {
5606 struct commit *commit = line->data;
5607 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5608 char buf[DATE_COLS + 1];
5609 regmatch_t pmatch;
5611 for (state = S_TITLE; state < S_END; state++) {
5612 char *text;
5614 switch (state) {
5615 case S_TITLE: text = commit->title; break;
5616 case S_AUTHOR:
5617 if (!opt_author)
5618 continue;
5619 text = commit->author;
5620 break;
5621 case S_DATE:
5622 if (!opt_date)
5623 continue;
5624 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5625 continue;
5626 text = buf;
5627 break;
5628 case S_REFS:
5629 if (!opt_show_refs)
5630 continue;
5631 if (grep_refs(commit->refs, view->regex) == TRUE)
5632 return TRUE;
5633 continue;
5634 default:
5635 return FALSE;
5636 }
5638 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5639 return TRUE;
5640 }
5642 return FALSE;
5643 }
5645 static void
5646 main_select(struct view *view, struct line *line)
5647 {
5648 struct commit *commit = line->data;
5650 string_copy_rev(view->ref, commit->id);
5651 string_copy_rev(ref_commit, view->ref);
5652 }
5654 static struct view_ops main_ops = {
5655 "commit",
5656 main_argv,
5657 NULL,
5658 main_read,
5659 main_draw,
5660 main_request,
5661 main_grep,
5662 main_select,
5663 };
5666 /*
5667 * Unicode / UTF-8 handling
5668 *
5669 * NOTE: Much of the following code for dealing with unicode is derived from
5670 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5671 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5672 */
5674 /* I've (over)annotated a lot of code snippets because I am not entirely
5675 * confident that the approach taken by this small UTF-8 interface is correct.
5676 * --jonas */
5678 static inline int
5679 unicode_width(unsigned long c)
5680 {
5681 if (c >= 0x1100 &&
5682 (c <= 0x115f /* Hangul Jamo */
5683 || c == 0x2329
5684 || c == 0x232a
5685 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5686 /* CJK ... Yi */
5687 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5688 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5689 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5690 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5691 || (c >= 0xffe0 && c <= 0xffe6)
5692 || (c >= 0x20000 && c <= 0x2fffd)
5693 || (c >= 0x30000 && c <= 0x3fffd)))
5694 return 2;
5696 if (c == '\t')
5697 return opt_tab_size;
5699 return 1;
5700 }
5702 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5703 * Illegal bytes are set one. */
5704 static const unsigned char utf8_bytes[256] = {
5705 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,
5706 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,
5707 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,
5708 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,
5709 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,
5710 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,
5711 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,
5712 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,
5713 };
5715 /* Decode UTF-8 multi-byte representation into a unicode character. */
5716 static inline unsigned long
5717 utf8_to_unicode(const char *string, size_t length)
5718 {
5719 unsigned long unicode;
5721 switch (length) {
5722 case 1:
5723 unicode = string[0];
5724 break;
5725 case 2:
5726 unicode = (string[0] & 0x1f) << 6;
5727 unicode += (string[1] & 0x3f);
5728 break;
5729 case 3:
5730 unicode = (string[0] & 0x0f) << 12;
5731 unicode += ((string[1] & 0x3f) << 6);
5732 unicode += (string[2] & 0x3f);
5733 break;
5734 case 4:
5735 unicode = (string[0] & 0x0f) << 18;
5736 unicode += ((string[1] & 0x3f) << 12);
5737 unicode += ((string[2] & 0x3f) << 6);
5738 unicode += (string[3] & 0x3f);
5739 break;
5740 case 5:
5741 unicode = (string[0] & 0x0f) << 24;
5742 unicode += ((string[1] & 0x3f) << 18);
5743 unicode += ((string[2] & 0x3f) << 12);
5744 unicode += ((string[3] & 0x3f) << 6);
5745 unicode += (string[4] & 0x3f);
5746 break;
5747 case 6:
5748 unicode = (string[0] & 0x01) << 30;
5749 unicode += ((string[1] & 0x3f) << 24);
5750 unicode += ((string[2] & 0x3f) << 18);
5751 unicode += ((string[3] & 0x3f) << 12);
5752 unicode += ((string[4] & 0x3f) << 6);
5753 unicode += (string[5] & 0x3f);
5754 break;
5755 default:
5756 die("Invalid unicode length");
5757 }
5759 /* Invalid characters could return the special 0xfffd value but NUL
5760 * should be just as good. */
5761 return unicode > 0xffff ? 0 : unicode;
5762 }
5764 /* Calculates how much of string can be shown within the given maximum width
5765 * and sets trimmed parameter to non-zero value if all of string could not be
5766 * shown. If the reserve flag is TRUE, it will reserve at least one
5767 * trailing character, which can be useful when drawing a delimiter.
5768 *
5769 * Returns the number of bytes to output from string to satisfy max_width. */
5770 static size_t
5771 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5772 {
5773 const char *start = string;
5774 const char *end = strchr(string, '\0');
5775 unsigned char last_bytes = 0;
5776 size_t last_ucwidth = 0;
5778 *width = 0;
5779 *trimmed = 0;
5781 while (string < end) {
5782 int c = *(unsigned char *) string;
5783 unsigned char bytes = utf8_bytes[c];
5784 size_t ucwidth;
5785 unsigned long unicode;
5787 if (string + bytes > end)
5788 break;
5790 /* Change representation to figure out whether
5791 * it is a single- or double-width character. */
5793 unicode = utf8_to_unicode(string, bytes);
5794 /* FIXME: Graceful handling of invalid unicode character. */
5795 if (!unicode)
5796 break;
5798 ucwidth = unicode_width(unicode);
5799 *width += ucwidth;
5800 if (*width > max_width) {
5801 *trimmed = 1;
5802 *width -= ucwidth;
5803 if (reserve && *width == max_width) {
5804 string -= last_bytes;
5805 *width -= last_ucwidth;
5806 }
5807 break;
5808 }
5810 string += bytes;
5811 last_bytes = bytes;
5812 last_ucwidth = ucwidth;
5813 }
5815 return string - start;
5816 }
5819 /*
5820 * Status management
5821 */
5823 /* Whether or not the curses interface has been initialized. */
5824 static bool cursed = FALSE;
5826 /* The status window is used for polling keystrokes. */
5827 static WINDOW *status_win;
5829 static bool status_empty = TRUE;
5831 /* Update status and title window. */
5832 static void
5833 report(const char *msg, ...)
5834 {
5835 struct view *view = display[current_view];
5837 if (input_mode)
5838 return;
5840 if (!view) {
5841 char buf[SIZEOF_STR];
5842 va_list args;
5844 va_start(args, msg);
5845 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5846 buf[sizeof(buf) - 1] = 0;
5847 buf[sizeof(buf) - 2] = '.';
5848 buf[sizeof(buf) - 3] = '.';
5849 buf[sizeof(buf) - 4] = '.';
5850 }
5851 va_end(args);
5852 die("%s", buf);
5853 }
5855 if (!status_empty || *msg) {
5856 va_list args;
5858 va_start(args, msg);
5860 wmove(status_win, 0, 0);
5861 if (*msg) {
5862 vwprintw(status_win, msg, args);
5863 status_empty = FALSE;
5864 } else {
5865 status_empty = TRUE;
5866 }
5867 wclrtoeol(status_win);
5868 wrefresh(status_win);
5870 va_end(args);
5871 }
5873 update_view_title(view);
5874 update_display_cursor(view);
5875 }
5877 /* Controls when nodelay should be in effect when polling user input. */
5878 static void
5879 set_nonblocking_input(bool loading)
5880 {
5881 static unsigned int loading_views;
5883 if ((loading == FALSE && loading_views-- == 1) ||
5884 (loading == TRUE && loading_views++ == 0))
5885 nodelay(status_win, loading);
5886 }
5888 static void
5889 init_display(void)
5890 {
5891 int x, y;
5893 /* Initialize the curses library */
5894 if (isatty(STDIN_FILENO)) {
5895 cursed = !!initscr();
5896 opt_tty = stdin;
5897 } else {
5898 /* Leave stdin and stdout alone when acting as a pager. */
5899 opt_tty = fopen("/dev/tty", "r+");
5900 if (!opt_tty)
5901 die("Failed to open /dev/tty");
5902 cursed = !!newterm(NULL, opt_tty, opt_tty);
5903 }
5905 if (!cursed)
5906 die("Failed to initialize curses");
5908 nonl(); /* Tell curses not to do NL->CR/NL on output */
5909 cbreak(); /* Take input chars one at a time, no wait for \n */
5910 noecho(); /* Don't echo input */
5911 leaveok(stdscr, TRUE);
5913 if (has_colors())
5914 init_colors();
5916 getmaxyx(stdscr, y, x);
5917 status_win = newwin(1, 0, y - 1, 0);
5918 if (!status_win)
5919 die("Failed to create status window");
5921 /* Enable keyboard mapping */
5922 keypad(status_win, TRUE);
5923 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5925 TABSIZE = opt_tab_size;
5926 if (opt_line_graphics) {
5927 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5928 }
5929 }
5931 static bool
5932 prompt_yesno(const char *prompt)
5933 {
5934 enum { WAIT, STOP, CANCEL } status = WAIT;
5935 bool answer = FALSE;
5937 while (status == WAIT) {
5938 struct view *view;
5939 int i, key;
5941 input_mode = TRUE;
5943 foreach_view (view, i)
5944 update_view(view);
5946 input_mode = FALSE;
5948 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5949 wclrtoeol(status_win);
5951 /* Refresh, accept single keystroke of input */
5952 key = wgetch(status_win);
5953 switch (key) {
5954 case ERR:
5955 break;
5957 case 'y':
5958 case 'Y':
5959 answer = TRUE;
5960 status = STOP;
5961 break;
5963 case KEY_ESC:
5964 case KEY_RETURN:
5965 case KEY_ENTER:
5966 case KEY_BACKSPACE:
5967 case 'n':
5968 case 'N':
5969 case '\n':
5970 default:
5971 answer = FALSE;
5972 status = CANCEL;
5973 }
5974 }
5976 /* Clear the status window */
5977 status_empty = FALSE;
5978 report("");
5980 return answer;
5981 }
5983 static char *
5984 read_prompt(const char *prompt)
5985 {
5986 enum { READING, STOP, CANCEL } status = READING;
5987 static char buf[SIZEOF_STR];
5988 int pos = 0;
5990 while (status == READING) {
5991 struct view *view;
5992 int i, key;
5994 input_mode = TRUE;
5996 foreach_view (view, i)
5997 update_view(view);
5999 input_mode = FALSE;
6001 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6002 wclrtoeol(status_win);
6004 /* Refresh, accept single keystroke of input */
6005 key = wgetch(status_win);
6006 switch (key) {
6007 case KEY_RETURN:
6008 case KEY_ENTER:
6009 case '\n':
6010 status = pos ? STOP : CANCEL;
6011 break;
6013 case KEY_BACKSPACE:
6014 if (pos > 0)
6015 pos--;
6016 else
6017 status = CANCEL;
6018 break;
6020 case KEY_ESC:
6021 status = CANCEL;
6022 break;
6024 case ERR:
6025 break;
6027 default:
6028 if (pos >= sizeof(buf)) {
6029 report("Input string too long");
6030 return NULL;
6031 }
6033 if (isprint(key))
6034 buf[pos++] = (char) key;
6035 }
6036 }
6038 /* Clear the status window */
6039 status_empty = FALSE;
6040 report("");
6042 if (status == CANCEL)
6043 return NULL;
6045 buf[pos++] = 0;
6047 return buf;
6048 }
6050 /*
6051 * Repository properties
6052 */
6054 static int
6055 git_properties(const char **argv, const char *separators,
6056 int (*read_property)(char *, size_t, char *, size_t))
6057 {
6058 struct io io = {};
6060 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6061 return read_properties(&io, separators, read_property);
6062 return ERR;
6063 }
6065 static struct ref *refs = NULL;
6066 static size_t refs_alloc = 0;
6067 static size_t refs_size = 0;
6069 /* Id <-> ref store */
6070 static struct ref ***id_refs = NULL;
6071 static size_t id_refs_alloc = 0;
6072 static size_t id_refs_size = 0;
6074 static int
6075 compare_refs(const void *ref1_, const void *ref2_)
6076 {
6077 const struct ref *ref1 = *(const struct ref **)ref1_;
6078 const struct ref *ref2 = *(const struct ref **)ref2_;
6080 if (ref1->tag != ref2->tag)
6081 return ref2->tag - ref1->tag;
6082 if (ref1->ltag != ref2->ltag)
6083 return ref2->ltag - ref2->ltag;
6084 if (ref1->head != ref2->head)
6085 return ref2->head - ref1->head;
6086 if (ref1->tracked != ref2->tracked)
6087 return ref2->tracked - ref1->tracked;
6088 if (ref1->remote != ref2->remote)
6089 return ref2->remote - ref1->remote;
6090 return strcmp(ref1->name, ref2->name);
6091 }
6093 static struct ref **
6094 get_refs(const char *id)
6095 {
6096 struct ref ***tmp_id_refs;
6097 struct ref **ref_list = NULL;
6098 size_t ref_list_alloc = 0;
6099 size_t ref_list_size = 0;
6100 size_t i;
6102 for (i = 0; i < id_refs_size; i++)
6103 if (!strcmp(id, id_refs[i][0]->id))
6104 return id_refs[i];
6106 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6107 sizeof(*id_refs));
6108 if (!tmp_id_refs)
6109 return NULL;
6111 id_refs = tmp_id_refs;
6113 for (i = 0; i < refs_size; i++) {
6114 struct ref **tmp;
6116 if (strcmp(id, refs[i].id))
6117 continue;
6119 tmp = realloc_items(ref_list, &ref_list_alloc,
6120 ref_list_size + 1, sizeof(*ref_list));
6121 if (!tmp) {
6122 if (ref_list)
6123 free(ref_list);
6124 return NULL;
6125 }
6127 ref_list = tmp;
6128 ref_list[ref_list_size] = &refs[i];
6129 /* XXX: The properties of the commit chains ensures that we can
6130 * safely modify the shared ref. The repo references will
6131 * always be similar for the same id. */
6132 ref_list[ref_list_size]->next = 1;
6134 ref_list_size++;
6135 }
6137 if (ref_list) {
6138 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6139 ref_list[ref_list_size - 1]->next = 0;
6140 id_refs[id_refs_size++] = ref_list;
6141 }
6143 return ref_list;
6144 }
6146 static int
6147 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6148 {
6149 struct ref *ref;
6150 bool tag = FALSE;
6151 bool ltag = FALSE;
6152 bool remote = FALSE;
6153 bool tracked = FALSE;
6154 bool check_replace = FALSE;
6155 bool head = FALSE;
6157 if (!prefixcmp(name, "refs/tags/")) {
6158 if (!suffixcmp(name, namelen, "^{}")) {
6159 namelen -= 3;
6160 name[namelen] = 0;
6161 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6162 check_replace = TRUE;
6163 } else {
6164 ltag = TRUE;
6165 }
6167 tag = TRUE;
6168 namelen -= STRING_SIZE("refs/tags/");
6169 name += STRING_SIZE("refs/tags/");
6171 } else if (!prefixcmp(name, "refs/remotes/")) {
6172 remote = TRUE;
6173 namelen -= STRING_SIZE("refs/remotes/");
6174 name += STRING_SIZE("refs/remotes/");
6175 tracked = !strcmp(opt_remote, name);
6177 } else if (!prefixcmp(name, "refs/heads/")) {
6178 namelen -= STRING_SIZE("refs/heads/");
6179 name += STRING_SIZE("refs/heads/");
6180 head = !strncmp(opt_head, name, namelen);
6182 } else if (!strcmp(name, "HEAD")) {
6183 string_ncopy(opt_head_rev, id, idlen);
6184 return OK;
6185 }
6187 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6188 /* it's an annotated tag, replace the previous sha1 with the
6189 * resolved commit id; relies on the fact git-ls-remote lists
6190 * the commit id of an annotated tag right before the commit id
6191 * it points to. */
6192 refs[refs_size - 1].ltag = ltag;
6193 string_copy_rev(refs[refs_size - 1].id, id);
6195 return OK;
6196 }
6197 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6198 if (!refs)
6199 return ERR;
6201 ref = &refs[refs_size++];
6202 ref->name = malloc(namelen + 1);
6203 if (!ref->name)
6204 return ERR;
6206 strncpy(ref->name, name, namelen);
6207 ref->name[namelen] = 0;
6208 ref->head = head;
6209 ref->tag = tag;
6210 ref->ltag = ltag;
6211 ref->remote = remote;
6212 ref->tracked = tracked;
6213 string_copy_rev(ref->id, id);
6215 return OK;
6216 }
6218 static int
6219 load_refs(void)
6220 {
6221 static const char *ls_remote_argv[SIZEOF_ARG] = {
6222 "git", "ls-remote", ".", NULL
6223 };
6224 static bool init = FALSE;
6226 if (!init) {
6227 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6228 init = TRUE;
6229 }
6231 if (!*opt_git_dir)
6232 return OK;
6234 while (refs_size > 0)
6235 free(refs[--refs_size].name);
6236 while (id_refs_size > 0)
6237 free(id_refs[--id_refs_size]);
6239 return git_properties(ls_remote_argv, "\t", read_ref);
6240 }
6242 static int
6243 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6244 {
6245 if (!strcmp(name, "i18n.commitencoding"))
6246 string_ncopy(opt_encoding, value, valuelen);
6248 if (!strcmp(name, "core.editor"))
6249 string_ncopy(opt_editor, value, valuelen);
6251 /* branch.<head>.remote */
6252 if (*opt_head &&
6253 !strncmp(name, "branch.", 7) &&
6254 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6255 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6256 string_ncopy(opt_remote, value, valuelen);
6258 if (*opt_head && *opt_remote &&
6259 !strncmp(name, "branch.", 7) &&
6260 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6261 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6262 size_t from = strlen(opt_remote);
6264 if (!prefixcmp(value, "refs/heads/")) {
6265 value += STRING_SIZE("refs/heads/");
6266 valuelen -= STRING_SIZE("refs/heads/");
6267 }
6269 if (!string_format_from(opt_remote, &from, "/%s", value))
6270 opt_remote[0] = 0;
6271 }
6273 return OK;
6274 }
6276 static int
6277 load_git_config(void)
6278 {
6279 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6281 return git_properties(config_list_argv, "=", read_repo_config_option);
6282 }
6284 static int
6285 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6286 {
6287 if (!opt_git_dir[0]) {
6288 string_ncopy(opt_git_dir, name, namelen);
6290 } else if (opt_is_inside_work_tree == -1) {
6291 /* This can be 3 different values depending on the
6292 * version of git being used. If git-rev-parse does not
6293 * understand --is-inside-work-tree it will simply echo
6294 * the option else either "true" or "false" is printed.
6295 * Default to true for the unknown case. */
6296 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6297 } else {
6298 string_ncopy(opt_cdup, name, namelen);
6299 }
6301 return OK;
6302 }
6304 static int
6305 load_repo_info(void)
6306 {
6307 const char *head_argv[] = {
6308 "git", "symbolic-ref", "HEAD", NULL
6309 };
6310 const char *rev_parse_argv[] = {
6311 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6312 "--show-cdup", NULL
6313 };
6315 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6316 chomp_string(opt_head);
6317 if (!prefixcmp(opt_head, "refs/heads/")) {
6318 char *offset = opt_head + STRING_SIZE("refs/heads/");
6320 memmove(opt_head, offset, strlen(offset) + 1);
6321 }
6322 }
6324 return git_properties(rev_parse_argv, "=", read_repo_info);
6325 }
6327 static int
6328 read_properties(struct io *io, const char *separators,
6329 int (*read_property)(char *, size_t, char *, size_t))
6330 {
6331 char *name;
6332 int state = OK;
6334 if (!start_io(io))
6335 return ERR;
6337 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6338 char *value;
6339 size_t namelen;
6340 size_t valuelen;
6342 name = chomp_string(name);
6343 namelen = strcspn(name, separators);
6345 if (name[namelen]) {
6346 name[namelen] = 0;
6347 value = chomp_string(name + namelen + 1);
6348 valuelen = strlen(value);
6350 } else {
6351 value = "";
6352 valuelen = 0;
6353 }
6355 state = read_property(name, namelen, value, valuelen);
6356 }
6358 if (state != ERR && io_error(io))
6359 state = ERR;
6360 done_io(io);
6362 return state;
6363 }
6366 /*
6367 * Main
6368 */
6370 static void __NORETURN
6371 quit(int sig)
6372 {
6373 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6374 if (cursed)
6375 endwin();
6376 exit(0);
6377 }
6379 static void __NORETURN
6380 die(const char *err, ...)
6381 {
6382 va_list args;
6384 endwin();
6386 va_start(args, err);
6387 fputs("tig: ", stderr);
6388 vfprintf(stderr, err, args);
6389 fputs("\n", stderr);
6390 va_end(args);
6392 exit(1);
6393 }
6395 static void
6396 warn(const char *msg, ...)
6397 {
6398 va_list args;
6400 va_start(args, msg);
6401 fputs("tig warning: ", stderr);
6402 vfprintf(stderr, msg, args);
6403 fputs("\n", stderr);
6404 va_end(args);
6405 }
6407 int
6408 main(int argc, const char *argv[])
6409 {
6410 const char **run_argv = NULL;
6411 struct view *view;
6412 enum request request;
6413 size_t i;
6415 signal(SIGINT, quit);
6417 if (setlocale(LC_ALL, "")) {
6418 char *codeset = nl_langinfo(CODESET);
6420 string_ncopy(opt_codeset, codeset, strlen(codeset));
6421 }
6423 if (load_repo_info() == ERR)
6424 die("Failed to load repo info.");
6426 if (load_options() == ERR)
6427 die("Failed to load user config.");
6429 if (load_git_config() == ERR)
6430 die("Failed to load repo config.");
6432 request = parse_options(argc, argv, &run_argv);
6433 if (request == REQ_NONE)
6434 return 0;
6436 /* Require a git repository unless when running in pager mode. */
6437 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6438 die("Not a git repository");
6440 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6441 opt_utf8 = FALSE;
6443 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6444 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6445 if (opt_iconv == ICONV_NONE)
6446 die("Failed to initialize character set conversion");
6447 }
6449 if (load_refs() == ERR)
6450 die("Failed to load refs.");
6452 foreach_view (view, i)
6453 argv_from_env(view->ops->argv, view->cmd_env);
6455 init_display();
6457 if (request == REQ_VIEW_PAGER || run_argv) {
6458 if (request == REQ_VIEW_PAGER)
6459 io_open(&VIEW(request)->io, "");
6460 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6461 die("Failed to format arguments");
6462 open_view(NULL, request, OPEN_PREPARED);
6463 request = REQ_NONE;
6464 }
6466 while (view_driver(display[current_view], request)) {
6467 int key;
6468 int i;
6470 foreach_view (view, i)
6471 update_view(view);
6472 view = display[current_view];
6474 /* Refresh, accept single keystroke of input */
6475 key = wgetch(status_win);
6477 /* wgetch() with nodelay() enabled returns ERR when there's no
6478 * input. */
6479 if (key == ERR) {
6480 request = REQ_NONE;
6481 continue;
6482 }
6484 request = get_keybinding(view->keymap, key);
6486 /* Some low-level request handling. This keeps access to
6487 * status_win restricted. */
6488 switch (request) {
6489 case REQ_PROMPT:
6490 {
6491 char *cmd = read_prompt(":");
6493 if (cmd) {
6494 struct view *next = VIEW(REQ_VIEW_PAGER);
6495 const char *argv[SIZEOF_ARG] = { "git" };
6496 int argc = 1;
6498 /* When running random commands, initially show the
6499 * command in the title. However, it maybe later be
6500 * overwritten if a commit line is selected. */
6501 string_ncopy(next->ref, cmd, strlen(cmd));
6503 if (!argv_from_string(argv, &argc, cmd)) {
6504 report("Too many arguments");
6505 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6506 report("Failed to format command");
6507 } else {
6508 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6509 }
6510 }
6512 request = REQ_NONE;
6513 break;
6514 }
6515 case REQ_SEARCH:
6516 case REQ_SEARCH_BACK:
6517 {
6518 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6519 char *search = read_prompt(prompt);
6521 if (search)
6522 string_ncopy(opt_search, search, strlen(search));
6523 else
6524 request = REQ_NONE;
6525 break;
6526 }
6527 case REQ_SCREEN_RESIZE:
6528 {
6529 int height, width;
6531 getmaxyx(stdscr, height, width);
6533 /* Resize the status view and let the view driver take
6534 * care of resizing the displayed views. */
6535 wresize(status_win, 1, width);
6536 mvwin(status_win, height - 1, 0);
6537 wrefresh(status_win);
6538 break;
6539 }
6540 default:
6541 break;
6542 }
6543 }
6545 quit(0);
6547 return 0;
6548 }