566f8031d01047e92f9359e42690108f7f9d5486
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 int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
148 };
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153 INPUT_OK,
154 INPUT_SKIP,
155 INPUT_STOP,
156 INPUT_CANCEL
157 };
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 /*
165 * Allocation helpers ... Entering macro hell to never be seen again.
166 */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t *alloc, size_t new_size) \
171 { \
172 size_t num_chunks = *alloc / chunk_size; \
173 size_t num_chunks_new = (new_size + chunk_size - 1) / chunk_size; \
174 type *tmp = *mem; \
175 \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 size_t memsize = num_chunks_new * chunk_size; \
178 tmp = realloc(tmp, memsize * sizeof(type)); \
179 if (tmp) \
180 *mem = tmp, *alloc = memsize; \
181 } \
182 \
183 return tmp; \
184 }
186 /*
187 * String helpers
188 */
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 {
193 if (srclen > dstlen - 1)
194 srclen = dstlen - 1;
196 strncpy(dst, src, srclen);
197 dst[srclen] = 0;
198 }
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static void
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216 {
217 size_t size, pos;
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
226 size += expanded;
227 } else {
228 dst[size++] = src[pos];
229 }
230 }
232 dst[size] = 0;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 struct enum_map {
295 const char *name;
296 int namelen;
297 int value;
298 };
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
302 static bool
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
304 {
305 size_t namelen = strlen(name);
306 int i;
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
312 return TRUE;
313 }
315 return FALSE;
316 }
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
324 static inline int
325 suffixcmp(const char *str, int slen, const char *suffix)
326 {
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
331 }
334 static const char *
335 mkdate(const time_t *time)
336 {
337 static char buf[DATE_COLS + 1];
338 struct tm tm;
340 gmtime_r(time, &tm);
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
342 }
345 static bool
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
347 {
348 int valuelen;
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
353 cmd[valuelen] = 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
356 }
358 if (*argc < SIZEOF_ARG)
359 argv[*argc] = NULL;
360 return *argc < SIZEOF_ARG;
361 }
363 static void
364 argv_from_env(const char **argv, const char *name)
365 {
366 char *env = argv ? getenv(name) : NULL;
367 int argc = 0;
369 if (env && *env)
370 env = strdup(env);
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
373 }
376 /*
377 * Executing external commands.
378 */
380 enum io_type {
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
387 };
389 struct io {
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
401 };
403 static void
404 reset_io(struct io *io)
405 {
406 io->pipe = -1;
407 io->pid = 0;
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
410 io->error = 0;
411 io->eof = 0;
412 }
414 static void
415 init_io(struct io *io, const char *dir, enum io_type type)
416 {
417 reset_io(io);
418 io->type = type;
419 io->dir = dir;
420 }
422 static bool
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
425 {
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
428 }
430 static bool
431 io_open(struct io *io, const char *name)
432 {
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
435 if (io->pipe == -1)
436 io->error = errno;
437 return io->pipe != -1;
438 }
440 static bool
441 kill_io(struct io *io)
442 {
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
444 }
446 static bool
447 done_io(struct io *io)
448 {
449 pid_t pid = io->pid;
451 if (io->pipe != -1)
452 close(io->pipe);
453 free(io->buf);
454 reset_io(io);
456 while (pid > 0) {
457 int status;
458 pid_t waiting = waitpid(pid, &status, 0);
460 if (waiting < 0) {
461 if (errno == EINTR)
462 continue;
463 report("waitpid failed (%s)", strerror(errno));
464 return FALSE;
465 }
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
469 WIFEXITED(status) &&
470 !WEXITSTATUS(status);
471 }
473 return TRUE;
474 }
476 static bool
477 start_io(struct io *io)
478 {
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
482 return TRUE;
484 if ((io->type == IO_RD || io->type == IO_WR) &&
485 pipe(pipefds) < 0)
486 return FALSE;
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
493 if (io->pid != -1) {
494 io->pipe = pipefds[!!(io->type == IO_WR)];
495 return TRUE;
496 }
498 } else {
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
509 close(devnull);
510 if (pipefds[0] != -1)
511 close(pipefds[0]);
512 if (pipefds[1] != -1)
513 close(pipefds[1]);
514 }
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
521 }
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
525 return FALSE;
526 }
528 static bool
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
530 {
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
533 return FALSE;
534 return start_io(io);
535 }
537 static int
538 run_io_do(struct io *io)
539 {
540 return start_io(io) && done_io(io);
541 }
543 static int
544 run_io_bg(const char **argv)
545 {
546 struct io io = {};
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
550 return FALSE;
551 return run_io_do(&io);
552 }
554 static bool
555 run_io_fg(const char **argv, const char *dir)
556 {
557 struct io io = {};
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
561 return FALSE;
562 return run_io_do(&io);
563 }
565 static bool
566 run_io_append(const char **argv, enum format_flags flags, int fd)
567 {
568 struct io io = {};
570 init_io(&io, NULL, IO_AP);
571 io.pipe = fd;
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
574 close(fd);
575 return FALSE;
576 }
578 static bool
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
580 {
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
582 }
584 static bool
585 io_eof(struct io *io)
586 {
587 return io->eof;
588 }
590 static int
591 io_error(struct io *io)
592 {
593 return io->error;
594 }
596 static char *
597 io_strerror(struct io *io)
598 {
599 return strerror(io->error);
600 }
602 static bool
603 io_can_read(struct io *io)
604 {
605 struct timeval tv = { 0, 500 };
606 fd_set fds;
608 FD_ZERO(&fds);
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
612 }
614 static ssize_t
615 io_read(struct io *io, void *buf, size_t bufsize)
616 {
617 do {
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
621 continue;
622 else if (readsize == -1)
623 io->error = errno;
624 else if (readsize == 0)
625 io->eof = 1;
626 return readsize;
627 } while (1);
628 }
630 static char *
631 io_get(struct io *io, int c, bool can_read)
632 {
633 char *eol;
634 ssize_t readsize;
636 if (!io->buf) {
637 io->buf = io->bufpos = malloc(BUFSIZ);
638 if (!io->buf)
639 return NULL;
640 io->bufalloc = BUFSIZ;
641 io->bufsize = 0;
642 }
644 while (TRUE) {
645 if (io->bufsize > 0) {
646 eol = memchr(io->bufpos, c, io->bufsize);
647 if (eol) {
648 char *line = io->bufpos;
650 *eol = 0;
651 io->bufpos = eol + 1;
652 io->bufsize -= io->bufpos - line;
653 return line;
654 }
655 }
657 if (io_eof(io)) {
658 if (io->bufsize) {
659 io->bufpos[io->bufsize] = 0;
660 io->bufsize = 0;
661 return io->bufpos;
662 }
663 return NULL;
664 }
666 if (!can_read)
667 return NULL;
669 if (io->bufsize > 0 && io->bufpos > io->buf)
670 memmove(io->buf, io->bufpos, io->bufsize);
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
674 if (io_error(io))
675 return NULL;
676 io->bufsize += readsize;
677 }
678 }
680 static bool
681 io_write(struct io *io, const void *buf, size_t bufsize)
682 {
683 size_t written = 0;
685 while (!io_error(io) && written < bufsize) {
686 ssize_t size;
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
690 continue;
691 else if (size == -1)
692 io->error = errno;
693 else
694 written += size;
695 }
697 return written == bufsize;
698 }
700 static bool
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
702 {
703 bool error;
705 io->buf = io->bufpos = buf;
706 io->bufalloc = bufsize;
707 error = !io_get(io, '\n', TRUE) && io_error(io);
708 io->buf = NULL;
710 return done_io(io) || error;
711 }
713 static bool
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
715 {
716 struct io io = {};
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
719 }
721 static int
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
724 {
725 char *name;
726 int state = OK;
728 if (!start_io(io))
729 return ERR;
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
732 char *value;
733 size_t namelen;
734 size_t valuelen;
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
739 if (name[namelen]) {
740 name[namelen] = 0;
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
744 } else {
745 value = "";
746 valuelen = 0;
747 }
749 state = read_property(name, namelen, value, valuelen);
750 }
752 if (state != ERR && io_error(io))
753 state = ERR;
754 done_io(io);
756 return state;
757 }
759 static int
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
762 {
763 struct io io = {};
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
767 }
770 /*
771 * User requests
772 */
774 #define REQ_INFO \
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_HELP, "Show help page"), \
784 REQ_(VIEW_PAGER, "Show pager view"), \
785 REQ_(VIEW_STATUS, "Show status view"), \
786 REQ_(VIEW_STAGE, "Show stage view"), \
787 \
788 REQ_GROUP("View manipulation") \
789 REQ_(ENTER, "Enter current line and scroll"), \
790 REQ_(NEXT, "Move to next"), \
791 REQ_(PREVIOUS, "Move to previous"), \
792 REQ_(PARENT, "Move to parent"), \
793 REQ_(VIEW_NEXT, "Move focus to next view"), \
794 REQ_(REFRESH, "Reload and refresh"), \
795 REQ_(MAXIMIZE, "Maximize the current view"), \
796 REQ_(VIEW_CLOSE, "Close the current view"), \
797 REQ_(QUIT, "Close all views and quit"), \
798 \
799 REQ_GROUP("View specific requests") \
800 REQ_(STATUS_UPDATE, "Update file status"), \
801 REQ_(STATUS_REVERT, "Revert file changes"), \
802 REQ_(STATUS_MERGE, "Merge file using external tool"), \
803 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
804 \
805 REQ_GROUP("Cursor navigation") \
806 REQ_(MOVE_UP, "Move cursor one line up"), \
807 REQ_(MOVE_DOWN, "Move cursor one line down"), \
808 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
809 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
810 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
811 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
812 \
813 REQ_GROUP("Scrolling") \
814 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
815 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
816 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
817 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
818 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
819 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
820 \
821 REQ_GROUP("Searching") \
822 REQ_(SEARCH, "Search the view"), \
823 REQ_(SEARCH_BACK, "Search backwards in the view"), \
824 REQ_(FIND_NEXT, "Find next search match"), \
825 REQ_(FIND_PREV, "Find previous search match"), \
826 \
827 REQ_GROUP("Option manipulation") \
828 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
829 REQ_(TOGGLE_DATE, "Toggle date display"), \
830 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
831 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
832 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
833 \
834 REQ_GROUP("Misc") \
835 REQ_(PROMPT, "Bring up the prompt"), \
836 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
837 REQ_(SHOW_VERSION, "Show version information"), \
838 REQ_(STOP_LOADING, "Stop all loading views"), \
839 REQ_(EDIT, "Open in editor"), \
840 REQ_(NONE, "Do nothing")
843 /* User action requests. */
844 enum request {
845 #define REQ_GROUP(help)
846 #define REQ_(req, help) REQ_##req
848 /* Offset all requests to avoid conflicts with ncurses getch values. */
849 REQ_OFFSET = KEY_MAX + 1,
850 REQ_INFO
852 #undef REQ_GROUP
853 #undef REQ_
854 };
856 struct request_info {
857 enum request request;
858 const char *name;
859 int namelen;
860 const char *help;
861 };
863 static const struct request_info req_info[] = {
864 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
865 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
866 REQ_INFO
867 #undef REQ_GROUP
868 #undef REQ_
869 };
871 static enum request
872 get_request(const char *name)
873 {
874 int namelen = strlen(name);
875 int i;
877 for (i = 0; i < ARRAY_SIZE(req_info); i++)
878 if (req_info[i].namelen == namelen &&
879 !string_enum_compare(req_info[i].name, name, namelen))
880 return req_info[i].request;
882 return REQ_NONE;
883 }
886 /*
887 * Options
888 */
890 /* Option and state variables. */
891 static bool opt_date = TRUE;
892 static bool opt_author = TRUE;
893 static bool opt_line_number = FALSE;
894 static bool opt_line_graphics = TRUE;
895 static bool opt_rev_graph = FALSE;
896 static bool opt_show_refs = TRUE;
897 static int opt_num_interval = NUMBER_INTERVAL;
898 static double opt_hscroll = 0.50;
899 static int opt_tab_size = TAB_SIZE;
900 static int opt_author_cols = AUTHOR_COLS-1;
901 static char opt_path[SIZEOF_STR] = "";
902 static char opt_file[SIZEOF_STR] = "";
903 static char opt_ref[SIZEOF_REF] = "";
904 static char opt_head[SIZEOF_REF] = "";
905 static char opt_head_rev[SIZEOF_REV] = "";
906 static char opt_remote[SIZEOF_REF] = "";
907 static char opt_encoding[20] = "UTF-8";
908 static bool opt_utf8 = TRUE;
909 static char opt_codeset[20] = "UTF-8";
910 static iconv_t opt_iconv = ICONV_NONE;
911 static char opt_search[SIZEOF_STR] = "";
912 static char opt_cdup[SIZEOF_STR] = "";
913 static char opt_prefix[SIZEOF_STR] = "";
914 static char opt_git_dir[SIZEOF_STR] = "";
915 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
916 static char opt_editor[SIZEOF_STR] = "";
917 static FILE *opt_tty = NULL;
919 #define is_initial_commit() (!*opt_head_rev)
920 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
923 /*
924 * Line-oriented content detection.
925 */
927 #define LINE_INFO \
928 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
930 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
931 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
932 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
933 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
934 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
942 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
943 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
944 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
945 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
949 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
950 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
951 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
952 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
953 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
954 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
958 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
959 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
960 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
961 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
962 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
963 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
964 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
965 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
966 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
967 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
968 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
969 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
970 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
971 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
972 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
973 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
974 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
975 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
976 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
977 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
978 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
979 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
980 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
982 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
984 enum line_type {
985 #define LINE(type, line, fg, bg, attr) \
986 LINE_##type
987 LINE_INFO,
988 LINE_NONE
989 #undef LINE
990 };
992 struct line_info {
993 const char *name; /* Option name. */
994 int namelen; /* Size of option name. */
995 const char *line; /* The start of line to match. */
996 int linelen; /* Size of string to match. */
997 int fg, bg, attr; /* Color and text attributes for the lines. */
998 };
1000 static struct line_info line_info[] = {
1001 #define LINE(type, line, fg, bg, attr) \
1002 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1003 LINE_INFO
1004 #undef LINE
1005 };
1007 static enum line_type
1008 get_line_type(const char *line)
1009 {
1010 int linelen = strlen(line);
1011 enum line_type type;
1013 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1014 /* Case insensitive search matches Signed-off-by lines better. */
1015 if (linelen >= line_info[type].linelen &&
1016 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1017 return type;
1019 return LINE_DEFAULT;
1020 }
1022 static inline int
1023 get_line_attr(enum line_type type)
1024 {
1025 assert(type < ARRAY_SIZE(line_info));
1026 return COLOR_PAIR(type) | line_info[type].attr;
1027 }
1029 static struct line_info *
1030 get_line_info(const char *name)
1031 {
1032 size_t namelen = strlen(name);
1033 enum line_type type;
1035 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1036 if (namelen == line_info[type].namelen &&
1037 !string_enum_compare(line_info[type].name, name, namelen))
1038 return &line_info[type];
1040 return NULL;
1041 }
1043 static void
1044 init_colors(void)
1045 {
1046 int default_bg = line_info[LINE_DEFAULT].bg;
1047 int default_fg = line_info[LINE_DEFAULT].fg;
1048 enum line_type type;
1050 start_color();
1052 if (assume_default_colors(default_fg, default_bg) == ERR) {
1053 default_bg = COLOR_BLACK;
1054 default_fg = COLOR_WHITE;
1055 }
1057 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1058 struct line_info *info = &line_info[type];
1059 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1060 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1062 init_pair(type, fg, bg);
1063 }
1064 }
1066 struct line {
1067 enum line_type type;
1069 /* State flags */
1070 unsigned int selected:1;
1071 unsigned int dirty:1;
1072 unsigned int cleareol:1;
1074 void *data; /* User data */
1075 };
1078 /*
1079 * Keys
1080 */
1082 struct keybinding {
1083 int alias;
1084 enum request request;
1085 };
1087 static const struct keybinding default_keybindings[] = {
1088 /* View switching */
1089 { 'm', REQ_VIEW_MAIN },
1090 { 'd', REQ_VIEW_DIFF },
1091 { 'l', REQ_VIEW_LOG },
1092 { 't', REQ_VIEW_TREE },
1093 { 'f', REQ_VIEW_BLOB },
1094 { 'B', REQ_VIEW_BLAME },
1095 { 'p', REQ_VIEW_PAGER },
1096 { 'h', REQ_VIEW_HELP },
1097 { 'S', REQ_VIEW_STATUS },
1098 { 'c', REQ_VIEW_STAGE },
1100 /* View manipulation */
1101 { 'q', REQ_VIEW_CLOSE },
1102 { KEY_TAB, REQ_VIEW_NEXT },
1103 { KEY_RETURN, REQ_ENTER },
1104 { KEY_UP, REQ_PREVIOUS },
1105 { KEY_DOWN, REQ_NEXT },
1106 { 'R', REQ_REFRESH },
1107 { KEY_F(5), REQ_REFRESH },
1108 { 'O', REQ_MAXIMIZE },
1110 /* Cursor navigation */
1111 { 'k', REQ_MOVE_UP },
1112 { 'j', REQ_MOVE_DOWN },
1113 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1114 { KEY_END, REQ_MOVE_LAST_LINE },
1115 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1116 { ' ', REQ_MOVE_PAGE_DOWN },
1117 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1118 { 'b', REQ_MOVE_PAGE_UP },
1119 { '-', REQ_MOVE_PAGE_UP },
1121 /* Scrolling */
1122 { KEY_LEFT, REQ_SCROLL_LEFT },
1123 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1124 { KEY_IC, REQ_SCROLL_LINE_UP },
1125 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1126 { 'w', REQ_SCROLL_PAGE_UP },
1127 { 's', REQ_SCROLL_PAGE_DOWN },
1129 /* Searching */
1130 { '/', REQ_SEARCH },
1131 { '?', REQ_SEARCH_BACK },
1132 { 'n', REQ_FIND_NEXT },
1133 { 'N', REQ_FIND_PREV },
1135 /* Misc */
1136 { 'Q', REQ_QUIT },
1137 { 'z', REQ_STOP_LOADING },
1138 { 'v', REQ_SHOW_VERSION },
1139 { 'r', REQ_SCREEN_REDRAW },
1140 { '.', REQ_TOGGLE_LINENO },
1141 { 'D', REQ_TOGGLE_DATE },
1142 { 'A', REQ_TOGGLE_AUTHOR },
1143 { 'g', REQ_TOGGLE_REV_GRAPH },
1144 { 'F', REQ_TOGGLE_REFS },
1145 { ':', REQ_PROMPT },
1146 { 'u', REQ_STATUS_UPDATE },
1147 { '!', REQ_STATUS_REVERT },
1148 { 'M', REQ_STATUS_MERGE },
1149 { '@', REQ_STAGE_NEXT },
1150 { ',', REQ_PARENT },
1151 { 'e', REQ_EDIT },
1152 };
1154 #define KEYMAP_INFO \
1155 KEYMAP_(GENERIC), \
1156 KEYMAP_(MAIN), \
1157 KEYMAP_(DIFF), \
1158 KEYMAP_(LOG), \
1159 KEYMAP_(TREE), \
1160 KEYMAP_(BLOB), \
1161 KEYMAP_(BLAME), \
1162 KEYMAP_(PAGER), \
1163 KEYMAP_(HELP), \
1164 KEYMAP_(STATUS), \
1165 KEYMAP_(STAGE)
1167 enum keymap {
1168 #define KEYMAP_(name) KEYMAP_##name
1169 KEYMAP_INFO
1170 #undef KEYMAP_
1171 };
1173 static const struct enum_map keymap_table[] = {
1174 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1175 KEYMAP_INFO
1176 #undef KEYMAP_
1177 };
1179 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1181 struct keybinding_table {
1182 struct keybinding *data;
1183 size_t size;
1184 };
1186 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1188 static void
1189 add_keybinding(enum keymap keymap, enum request request, int key)
1190 {
1191 struct keybinding_table *table = &keybindings[keymap];
1193 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1194 if (!table->data)
1195 die("Failed to allocate keybinding");
1196 table->data[table->size].alias = key;
1197 table->data[table->size++].request = request;
1198 }
1200 /* Looks for a key binding first in the given map, then in the generic map, and
1201 * lastly in the default keybindings. */
1202 static enum request
1203 get_keybinding(enum keymap keymap, int key)
1204 {
1205 size_t i;
1207 for (i = 0; i < keybindings[keymap].size; i++)
1208 if (keybindings[keymap].data[i].alias == key)
1209 return keybindings[keymap].data[i].request;
1211 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1212 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1213 return keybindings[KEYMAP_GENERIC].data[i].request;
1215 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1216 if (default_keybindings[i].alias == key)
1217 return default_keybindings[i].request;
1219 return (enum request) key;
1220 }
1223 struct key {
1224 const char *name;
1225 int value;
1226 };
1228 static const struct key key_table[] = {
1229 { "Enter", KEY_RETURN },
1230 { "Space", ' ' },
1231 { "Backspace", KEY_BACKSPACE },
1232 { "Tab", KEY_TAB },
1233 { "Escape", KEY_ESC },
1234 { "Left", KEY_LEFT },
1235 { "Right", KEY_RIGHT },
1236 { "Up", KEY_UP },
1237 { "Down", KEY_DOWN },
1238 { "Insert", KEY_IC },
1239 { "Delete", KEY_DC },
1240 { "Hash", '#' },
1241 { "Home", KEY_HOME },
1242 { "End", KEY_END },
1243 { "PageUp", KEY_PPAGE },
1244 { "PageDown", KEY_NPAGE },
1245 { "F1", KEY_F(1) },
1246 { "F2", KEY_F(2) },
1247 { "F3", KEY_F(3) },
1248 { "F4", KEY_F(4) },
1249 { "F5", KEY_F(5) },
1250 { "F6", KEY_F(6) },
1251 { "F7", KEY_F(7) },
1252 { "F8", KEY_F(8) },
1253 { "F9", KEY_F(9) },
1254 { "F10", KEY_F(10) },
1255 { "F11", KEY_F(11) },
1256 { "F12", KEY_F(12) },
1257 };
1259 static int
1260 get_key_value(const char *name)
1261 {
1262 int i;
1264 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1265 if (!strcasecmp(key_table[i].name, name))
1266 return key_table[i].value;
1268 if (strlen(name) == 1 && isprint(*name))
1269 return (int) *name;
1271 return ERR;
1272 }
1274 static const char *
1275 get_key_name(int key_value)
1276 {
1277 static char key_char[] = "'X'";
1278 const char *seq = NULL;
1279 int key;
1281 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1282 if (key_table[key].value == key_value)
1283 seq = key_table[key].name;
1285 if (seq == NULL &&
1286 key_value < 127 &&
1287 isprint(key_value)) {
1288 key_char[1] = (char) key_value;
1289 seq = key_char;
1290 }
1292 return seq ? seq : "(no key)";
1293 }
1295 static const char *
1296 get_key(enum request request)
1297 {
1298 static char buf[BUFSIZ];
1299 size_t pos = 0;
1300 char *sep = "";
1301 int i;
1303 buf[pos] = 0;
1305 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1306 const struct keybinding *keybinding = &default_keybindings[i];
1308 if (keybinding->request != request)
1309 continue;
1311 if (!string_format_from(buf, &pos, "%s%s", sep,
1312 get_key_name(keybinding->alias)))
1313 return "Too many keybindings!";
1314 sep = ", ";
1315 }
1317 return buf;
1318 }
1320 struct run_request {
1321 enum keymap keymap;
1322 int key;
1323 const char *argv[SIZEOF_ARG];
1324 };
1326 static struct run_request *run_request;
1327 static size_t run_requests;
1329 static enum request
1330 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1331 {
1332 struct run_request *req;
1334 if (argc >= ARRAY_SIZE(req->argv) - 1)
1335 return REQ_NONE;
1337 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1338 if (!req)
1339 return REQ_NONE;
1341 run_request = req;
1342 req = &run_request[run_requests];
1343 req->keymap = keymap;
1344 req->key = key;
1345 req->argv[0] = NULL;
1347 if (!format_argv(req->argv, argv, FORMAT_NONE))
1348 return REQ_NONE;
1350 return REQ_NONE + ++run_requests;
1351 }
1353 static struct run_request *
1354 get_run_request(enum request request)
1355 {
1356 if (request <= REQ_NONE)
1357 return NULL;
1358 return &run_request[request - REQ_NONE - 1];
1359 }
1361 static void
1362 add_builtin_run_requests(void)
1363 {
1364 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1365 const char *gc[] = { "git", "gc", NULL };
1366 struct {
1367 enum keymap keymap;
1368 int key;
1369 int argc;
1370 const char **argv;
1371 } reqs[] = {
1372 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1373 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1374 };
1375 int i;
1377 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1378 enum request req;
1380 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1381 if (req != REQ_NONE)
1382 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1383 }
1384 }
1386 /*
1387 * User config file handling.
1388 */
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 static const struct enum_map color_map[] = {
1395 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1396 COLOR_MAP(DEFAULT),
1397 COLOR_MAP(BLACK),
1398 COLOR_MAP(BLUE),
1399 COLOR_MAP(CYAN),
1400 COLOR_MAP(GREEN),
1401 COLOR_MAP(MAGENTA),
1402 COLOR_MAP(RED),
1403 COLOR_MAP(WHITE),
1404 COLOR_MAP(YELLOW),
1405 };
1407 static const struct enum_map attr_map[] = {
1408 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1409 ATTR_MAP(NORMAL),
1410 ATTR_MAP(BLINK),
1411 ATTR_MAP(BOLD),
1412 ATTR_MAP(DIM),
1413 ATTR_MAP(REVERSE),
1414 ATTR_MAP(STANDOUT),
1415 ATTR_MAP(UNDERLINE),
1416 };
1418 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1420 static int parse_step(double *opt, const char *arg)
1421 {
1422 *opt = atoi(arg);
1423 if (!strchr(arg, '%'))
1424 return OK;
1426 /* "Shift down" so 100% and 1 does not conflict. */
1427 *opt = (*opt - 1) / 100;
1428 if (*opt >= 1.0) {
1429 *opt = 0.99;
1430 config_msg = "Step value larger than 100%";
1431 return ERR;
1432 }
1433 if (*opt < 0.0) {
1434 *opt = 1;
1435 config_msg = "Invalid step value";
1436 return ERR;
1437 }
1438 return OK;
1439 }
1441 static int
1442 parse_int(int *opt, const char *arg, int min, int max)
1443 {
1444 int value = atoi(arg);
1446 if (min <= value && value <= max) {
1447 *opt = value;
1448 return OK;
1449 }
1451 config_msg = "Integer value out of bound";
1452 return ERR;
1453 }
1455 static bool
1456 set_color(int *color, const char *name)
1457 {
1458 if (map_enum(color, color_map, name))
1459 return TRUE;
1460 if (!prefixcmp(name, "color"))
1461 return parse_int(color, name + 5, 0, 255) == OK;
1462 return FALSE;
1463 }
1465 /* Wants: object fgcolor bgcolor [attribute] */
1466 static int
1467 option_color_command(int argc, const char *argv[])
1468 {
1469 struct line_info *info;
1471 if (argc != 3 && argc != 4) {
1472 config_msg = "Wrong number of arguments given to color command";
1473 return ERR;
1474 }
1476 info = get_line_info(argv[0]);
1477 if (!info) {
1478 static const struct enum_map obsolete[] = {
1479 ENUM_MAP("main-delim", LINE_DELIMITER),
1480 ENUM_MAP("main-date", LINE_DATE),
1481 ENUM_MAP("main-author", LINE_AUTHOR),
1482 };
1483 int index;
1485 if (!map_enum(&index, obsolete, argv[0])) {
1486 config_msg = "Unknown color name";
1487 return ERR;
1488 }
1489 info = &line_info[index];
1490 }
1492 if (!set_color(&info->fg, argv[1]) ||
1493 !set_color(&info->bg, argv[2])) {
1494 config_msg = "Unknown color";
1495 return ERR;
1496 }
1498 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1499 config_msg = "Unknown attribute";
1500 return ERR;
1501 }
1503 return OK;
1504 }
1506 static int parse_bool(bool *opt, const char *arg)
1507 {
1508 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1509 ? TRUE : FALSE;
1510 return OK;
1511 }
1513 static int
1514 parse_string(char *opt, const char *arg, size_t optsize)
1515 {
1516 int arglen = strlen(arg);
1518 switch (arg[0]) {
1519 case '\"':
1520 case '\'':
1521 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1522 config_msg = "Unmatched quotation";
1523 return ERR;
1524 }
1525 arg += 1; arglen -= 2;
1526 default:
1527 string_ncopy_do(opt, optsize, arg, arglen);
1528 return OK;
1529 }
1530 }
1532 /* Wants: name = value */
1533 static int
1534 option_set_command(int argc, const char *argv[])
1535 {
1536 if (argc != 3) {
1537 config_msg = "Wrong number of arguments given to set command";
1538 return ERR;
1539 }
1541 if (strcmp(argv[1], "=")) {
1542 config_msg = "No value assigned";
1543 return ERR;
1544 }
1546 if (!strcmp(argv[0], "show-author"))
1547 return parse_bool(&opt_author, argv[2]);
1549 if (!strcmp(argv[0], "show-date"))
1550 return parse_bool(&opt_date, argv[2]);
1552 if (!strcmp(argv[0], "show-rev-graph"))
1553 return parse_bool(&opt_rev_graph, argv[2]);
1555 if (!strcmp(argv[0], "show-refs"))
1556 return parse_bool(&opt_show_refs, argv[2]);
1558 if (!strcmp(argv[0], "show-line-numbers"))
1559 return parse_bool(&opt_line_number, argv[2]);
1561 if (!strcmp(argv[0], "line-graphics"))
1562 return parse_bool(&opt_line_graphics, argv[2]);
1564 if (!strcmp(argv[0], "line-number-interval"))
1565 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1567 if (!strcmp(argv[0], "author-width"))
1568 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1570 if (!strcmp(argv[0], "horizontal-scroll"))
1571 return parse_step(&opt_hscroll, argv[2]);
1573 if (!strcmp(argv[0], "tab-size"))
1574 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1576 if (!strcmp(argv[0], "commit-encoding"))
1577 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1579 config_msg = "Unknown variable name";
1580 return ERR;
1581 }
1583 /* Wants: mode request key */
1584 static int
1585 option_bind_command(int argc, const char *argv[])
1586 {
1587 enum request request;
1588 int keymap;
1589 int key;
1591 if (argc < 3) {
1592 config_msg = "Wrong number of arguments given to bind command";
1593 return ERR;
1594 }
1596 if (set_keymap(&keymap, argv[0]) == ERR) {
1597 config_msg = "Unknown key map";
1598 return ERR;
1599 }
1601 key = get_key_value(argv[1]);
1602 if (key == ERR) {
1603 config_msg = "Unknown key";
1604 return ERR;
1605 }
1607 request = get_request(argv[2]);
1608 if (request == REQ_NONE) {
1609 static const struct enum_map obsolete[] = {
1610 ENUM_MAP("cherry-pick", REQ_NONE),
1611 ENUM_MAP("screen-resize", REQ_NONE),
1612 ENUM_MAP("tree-parent", REQ_PARENT),
1613 };
1614 int alias;
1616 if (map_enum(&alias, obsolete, argv[2])) {
1617 if (alias != REQ_NONE)
1618 add_keybinding(keymap, alias, key);
1619 config_msg = "Obsolete request name";
1620 return ERR;
1621 }
1622 }
1623 if (request == REQ_NONE && *argv[2]++ == '!')
1624 request = add_run_request(keymap, key, argc - 2, argv + 2);
1625 if (request == REQ_NONE) {
1626 config_msg = "Unknown request name";
1627 return ERR;
1628 }
1630 add_keybinding(keymap, request, key);
1632 return OK;
1633 }
1635 static int
1636 set_option(const char *opt, char *value)
1637 {
1638 const char *argv[SIZEOF_ARG];
1639 int argc = 0;
1641 if (!argv_from_string(argv, &argc, value)) {
1642 config_msg = "Too many option arguments";
1643 return ERR;
1644 }
1646 if (!strcmp(opt, "color"))
1647 return option_color_command(argc, argv);
1649 if (!strcmp(opt, "set"))
1650 return option_set_command(argc, argv);
1652 if (!strcmp(opt, "bind"))
1653 return option_bind_command(argc, argv);
1655 config_msg = "Unknown option command";
1656 return ERR;
1657 }
1659 static int
1660 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1661 {
1662 int status = OK;
1664 config_lineno++;
1665 config_msg = "Internal error";
1667 /* Check for comment markers, since read_properties() will
1668 * only ensure opt and value are split at first " \t". */
1669 optlen = strcspn(opt, "#");
1670 if (optlen == 0)
1671 return OK;
1673 if (opt[optlen] != 0) {
1674 config_msg = "No option value";
1675 status = ERR;
1677 } else {
1678 /* Look for comment endings in the value. */
1679 size_t len = strcspn(value, "#");
1681 if (len < valuelen) {
1682 valuelen = len;
1683 value[valuelen] = 0;
1684 }
1686 status = set_option(opt, value);
1687 }
1689 if (status == ERR) {
1690 warn("Error on line %d, near '%.*s': %s",
1691 config_lineno, (int) optlen, opt, config_msg);
1692 config_errors = TRUE;
1693 }
1695 /* Always keep going if errors are encountered. */
1696 return OK;
1697 }
1699 static void
1700 load_option_file(const char *path)
1701 {
1702 struct io io = {};
1704 /* It's OK that the file doesn't exist. */
1705 if (!io_open(&io, path))
1706 return;
1708 config_lineno = 0;
1709 config_errors = FALSE;
1711 if (io_load(&io, " \t", read_option) == ERR ||
1712 config_errors == TRUE)
1713 warn("Errors while loading %s.", path);
1714 }
1716 static int
1717 load_options(void)
1718 {
1719 const char *home = getenv("HOME");
1720 const char *tigrc_user = getenv("TIGRC_USER");
1721 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1722 char buf[SIZEOF_STR];
1724 add_builtin_run_requests();
1726 if (!tigrc_system)
1727 tigrc_system = SYSCONFDIR "/tigrc";
1728 load_option_file(tigrc_system);
1730 if (!tigrc_user) {
1731 if (!home || !string_format(buf, "%s/.tigrc", home))
1732 return ERR;
1733 tigrc_user = buf;
1734 }
1735 load_option_file(tigrc_user);
1737 return OK;
1738 }
1741 /*
1742 * The viewer
1743 */
1745 struct view;
1746 struct view_ops;
1748 /* The display array of active views and the index of the current view. */
1749 static struct view *display[2];
1750 static unsigned int current_view;
1752 #define foreach_displayed_view(view, i) \
1753 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1755 #define displayed_views() (display[1] != NULL ? 2 : 1)
1757 /* Current head and commit ID */
1758 static char ref_blob[SIZEOF_REF] = "";
1759 static char ref_commit[SIZEOF_REF] = "HEAD";
1760 static char ref_head[SIZEOF_REF] = "HEAD";
1762 struct view {
1763 const char *name; /* View name */
1764 const char *cmd_env; /* Command line set via environment */
1765 const char *id; /* Points to either of ref_{head,commit,blob} */
1767 struct view_ops *ops; /* View operations */
1769 enum keymap keymap; /* What keymap does this view have */
1770 bool git_dir; /* Whether the view requires a git directory. */
1772 char ref[SIZEOF_REF]; /* Hovered commit reference */
1773 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1775 int height, width; /* The width and height of the main window */
1776 WINDOW *win; /* The main window */
1777 WINDOW *title; /* The title window living below the main window */
1779 /* Navigation */
1780 unsigned long offset; /* Offset of the window top */
1781 unsigned long yoffset; /* Offset from the window side. */
1782 unsigned long lineno; /* Current line number */
1783 unsigned long p_offset; /* Previous offset of the window top */
1784 unsigned long p_yoffset;/* Previous offset from the window side */
1785 unsigned long p_lineno; /* Previous current line number */
1786 bool p_restore; /* Should the previous position be restored. */
1788 /* Searching */
1789 char grep[SIZEOF_STR]; /* Search string */
1790 regex_t *regex; /* Pre-compiled regexp */
1792 /* If non-NULL, points to the view that opened this view. If this view
1793 * is closed tig will switch back to the parent view. */
1794 struct view *parent;
1796 /* Buffering */
1797 size_t lines; /* Total number of lines */
1798 struct line *line; /* Line index */
1799 size_t line_alloc; /* Total number of allocated lines */
1800 unsigned int digits; /* Number of digits in the lines member. */
1802 /* Drawing */
1803 struct line *curline; /* Line currently being drawn. */
1804 enum line_type curtype; /* Attribute currently used for drawing. */
1805 unsigned long col; /* Column when drawing. */
1806 bool has_scrolled; /* View was scrolled. */
1808 /* Loading */
1809 struct io io;
1810 struct io *pipe;
1811 time_t start_time;
1812 time_t update_secs;
1813 };
1815 struct view_ops {
1816 /* What type of content being displayed. Used in the title bar. */
1817 const char *type;
1818 /* Default command arguments. */
1819 const char **argv;
1820 /* Open and reads in all view content. */
1821 bool (*open)(struct view *view);
1822 /* Read one line; updates view->line. */
1823 bool (*read)(struct view *view, char *data);
1824 /* Draw one line; @lineno must be < view->height. */
1825 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1826 /* Depending on view handle a special requests. */
1827 enum request (*request)(struct view *view, enum request request, struct line *line);
1828 /* Search for regexp in a line. */
1829 bool (*grep)(struct view *view, struct line *line);
1830 /* Select line */
1831 void (*select)(struct view *view, struct line *line);
1832 };
1834 static struct view_ops blame_ops;
1835 static struct view_ops blob_ops;
1836 static struct view_ops diff_ops;
1837 static struct view_ops help_ops;
1838 static struct view_ops log_ops;
1839 static struct view_ops main_ops;
1840 static struct view_ops pager_ops;
1841 static struct view_ops stage_ops;
1842 static struct view_ops status_ops;
1843 static struct view_ops tree_ops;
1845 #define VIEW_STR(name, env, ref, ops, map, git) \
1846 { name, #env, ref, ops, map, git }
1848 #define VIEW_(id, name, ops, git, ref) \
1849 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1852 static struct view views[] = {
1853 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1854 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1855 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1856 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1857 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1858 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1859 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1860 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1861 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1862 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1863 };
1865 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1866 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1868 #define foreach_view(view, i) \
1869 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1871 #define view_is_displayed(view) \
1872 (view == display[0] || view == display[1])
1875 enum line_graphic {
1876 LINE_GRAPHIC_VLINE
1877 };
1879 static chtype line_graphics[] = {
1880 /* LINE_GRAPHIC_VLINE: */ '|'
1881 };
1883 static inline void
1884 set_view_attr(struct view *view, enum line_type type)
1885 {
1886 if (!view->curline->selected && view->curtype != type) {
1887 wattrset(view->win, get_line_attr(type));
1888 wchgat(view->win, -1, 0, type, NULL);
1889 view->curtype = type;
1890 }
1891 }
1893 static int
1894 draw_chars(struct view *view, enum line_type type, const char *string,
1895 int max_len, bool use_tilde)
1896 {
1897 int len = 0;
1898 int col = 0;
1899 int trimmed = FALSE;
1900 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1902 if (max_len <= 0)
1903 return 0;
1905 if (opt_utf8) {
1906 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1907 } else {
1908 col = len = strlen(string);
1909 if (len > max_len) {
1910 if (use_tilde) {
1911 max_len -= 1;
1912 }
1913 col = len = max_len;
1914 trimmed = TRUE;
1915 }
1916 }
1918 set_view_attr(view, type);
1919 if (len > 0)
1920 waddnstr(view->win, string, len);
1921 if (trimmed && use_tilde) {
1922 set_view_attr(view, LINE_DELIMITER);
1923 waddch(view->win, '~');
1924 col++;
1925 }
1927 return col;
1928 }
1930 static int
1931 draw_space(struct view *view, enum line_type type, int max, int spaces)
1932 {
1933 static char space[] = " ";
1934 int col = 0;
1936 spaces = MIN(max, spaces);
1938 while (spaces > 0) {
1939 int len = MIN(spaces, sizeof(space) - 1);
1941 col += draw_chars(view, type, space, len, FALSE);
1942 spaces -= len;
1943 }
1945 return col;
1946 }
1948 static bool
1949 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1950 {
1951 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1952 return view->width + view->yoffset <= view->col;
1953 }
1955 static bool
1956 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1957 {
1958 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1959 int max = view->width + view->yoffset - view->col;
1960 int i;
1962 if (max < size)
1963 size = max;
1965 set_view_attr(view, type);
1966 /* Using waddch() instead of waddnstr() ensures that
1967 * they'll be rendered correctly for the cursor line. */
1968 for (i = skip; i < size; i++)
1969 waddch(view->win, graphic[i]);
1971 view->col += size;
1972 if (size < max && skip <= size)
1973 waddch(view->win, ' ');
1974 view->col++;
1976 return view->width + view->yoffset <= view->col;
1977 }
1979 static bool
1980 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1981 {
1982 int max = MIN(view->width + view->yoffset - view->col, len);
1983 int col;
1985 if (text)
1986 col = draw_chars(view, type, text, max - 1, trim);
1987 else
1988 col = draw_space(view, type, max - 1, max - 1);
1990 view->col += col;
1991 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1992 return view->width + view->yoffset <= view->col;
1993 }
1995 static bool
1996 draw_date(struct view *view, time_t *time)
1997 {
1998 const char *date = mkdate(time);
2000 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2001 }
2003 static bool
2004 draw_author(struct view *view, const char *author)
2005 {
2006 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2008 if (!trim) {
2009 static char initials[10];
2010 size_t pos;
2012 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2014 memset(initials, 0, sizeof(initials));
2015 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2016 while (is_initial_sep(*author))
2017 author++;
2018 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2019 while (*author && !is_initial_sep(author[1]))
2020 author++;
2021 }
2023 author = initials;
2024 }
2026 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2027 }
2029 static bool
2030 draw_mode(struct view *view, mode_t mode)
2031 {
2032 const char *str;
2034 if (S_ISDIR(mode))
2035 str = "drwxr-xr-x";
2036 else if (S_ISLNK(mode))
2037 str = "lrwxrwxrwx";
2038 else if (S_ISGITLINK(mode))
2039 str = "m---------";
2040 else if (S_ISREG(mode) && mode & S_IXUSR)
2041 str = "-rwxr-xr-x";
2042 else if (S_ISREG(mode))
2043 str = "-rw-r--r--";
2044 else
2045 str = "----------";
2047 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2048 }
2050 static bool
2051 draw_lineno(struct view *view, unsigned int lineno)
2052 {
2053 char number[10];
2054 int digits3 = view->digits < 3 ? 3 : view->digits;
2055 int max = MIN(view->width + view->yoffset - view->col, digits3);
2056 char *text = NULL;
2058 lineno += view->offset + 1;
2059 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2060 static char fmt[] = "%1ld";
2062 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2063 if (string_format(number, fmt, lineno))
2064 text = number;
2065 }
2066 if (text)
2067 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2068 else
2069 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2070 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2071 }
2073 static bool
2074 draw_view_line(struct view *view, unsigned int lineno)
2075 {
2076 struct line *line;
2077 bool selected = (view->offset + lineno == view->lineno);
2079 assert(view_is_displayed(view));
2081 if (view->offset + lineno >= view->lines)
2082 return FALSE;
2084 line = &view->line[view->offset + lineno];
2086 wmove(view->win, lineno, 0);
2087 if (line->cleareol)
2088 wclrtoeol(view->win);
2089 view->col = 0;
2090 view->curline = line;
2091 view->curtype = LINE_NONE;
2092 line->selected = FALSE;
2093 line->dirty = line->cleareol = 0;
2095 if (selected) {
2096 set_view_attr(view, LINE_CURSOR);
2097 line->selected = TRUE;
2098 view->ops->select(view, line);
2099 }
2101 return view->ops->draw(view, line, lineno);
2102 }
2104 static void
2105 redraw_view_dirty(struct view *view)
2106 {
2107 bool dirty = FALSE;
2108 int lineno;
2110 for (lineno = 0; lineno < view->height; lineno++) {
2111 if (view->offset + lineno >= view->lines)
2112 break;
2113 if (!view->line[view->offset + lineno].dirty)
2114 continue;
2115 dirty = TRUE;
2116 if (!draw_view_line(view, lineno))
2117 break;
2118 }
2120 if (!dirty)
2121 return;
2122 wnoutrefresh(view->win);
2123 }
2125 static void
2126 redraw_view_from(struct view *view, int lineno)
2127 {
2128 assert(0 <= lineno && lineno < view->height);
2130 for (; lineno < view->height; lineno++) {
2131 if (!draw_view_line(view, lineno))
2132 break;
2133 }
2135 wnoutrefresh(view->win);
2136 }
2138 static void
2139 redraw_view(struct view *view)
2140 {
2141 werase(view->win);
2142 redraw_view_from(view, 0);
2143 }
2146 static void
2147 update_view_title(struct view *view)
2148 {
2149 char buf[SIZEOF_STR];
2150 char state[SIZEOF_STR];
2151 size_t bufpos = 0, statelen = 0;
2153 assert(view_is_displayed(view));
2155 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2156 unsigned int view_lines = view->offset + view->height;
2157 unsigned int lines = view->lines
2158 ? MIN(view_lines, view->lines) * 100 / view->lines
2159 : 0;
2161 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2162 view->ops->type,
2163 view->lineno + 1,
2164 view->lines,
2165 lines);
2167 }
2169 if (view->pipe) {
2170 time_t secs = time(NULL) - view->start_time;
2172 /* Three git seconds are a long time ... */
2173 if (secs > 2)
2174 string_format_from(state, &statelen, " loading %lds", secs);
2175 }
2177 string_format_from(buf, &bufpos, "[%s]", view->name);
2178 if (*view->ref && bufpos < view->width) {
2179 size_t refsize = strlen(view->ref);
2180 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2182 if (minsize < view->width)
2183 refsize = view->width - minsize + 7;
2184 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2185 }
2187 if (statelen && bufpos < view->width) {
2188 string_format_from(buf, &bufpos, "%s", state);
2189 }
2191 if (view == display[current_view])
2192 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2193 else
2194 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2196 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2197 wclrtoeol(view->title);
2198 wnoutrefresh(view->title);
2199 }
2201 static void
2202 resize_display(void)
2203 {
2204 int offset, i;
2205 struct view *base = display[0];
2206 struct view *view = display[1] ? display[1] : display[0];
2208 /* Setup window dimensions */
2210 getmaxyx(stdscr, base->height, base->width);
2212 /* Make room for the status window. */
2213 base->height -= 1;
2215 if (view != base) {
2216 /* Horizontal split. */
2217 view->width = base->width;
2218 view->height = SCALE_SPLIT_VIEW(base->height);
2219 base->height -= view->height;
2221 /* Make room for the title bar. */
2222 view->height -= 1;
2223 }
2225 /* Make room for the title bar. */
2226 base->height -= 1;
2228 offset = 0;
2230 foreach_displayed_view (view, i) {
2231 if (!view->win) {
2232 view->win = newwin(view->height, 0, offset, 0);
2233 if (!view->win)
2234 die("Failed to create %s view", view->name);
2236 scrollok(view->win, FALSE);
2238 view->title = newwin(1, 0, offset + view->height, 0);
2239 if (!view->title)
2240 die("Failed to create title window");
2242 } else {
2243 wresize(view->win, view->height, view->width);
2244 mvwin(view->win, offset, 0);
2245 mvwin(view->title, offset + view->height, 0);
2246 }
2248 offset += view->height + 1;
2249 }
2250 }
2252 static void
2253 redraw_display(bool clear)
2254 {
2255 struct view *view;
2256 int i;
2258 foreach_displayed_view (view, i) {
2259 if (clear)
2260 wclear(view->win);
2261 redraw_view(view);
2262 update_view_title(view);
2263 }
2264 }
2266 static void
2267 toggle_view_option(bool *option, const char *help)
2268 {
2269 *option = !*option;
2270 redraw_display(FALSE);
2271 report("%sabling %s", *option ? "En" : "Dis", help);
2272 }
2274 static void
2275 maximize_view(struct view *view)
2276 {
2277 memset(display, 0, sizeof(display));
2278 current_view = 0;
2279 display[current_view] = view;
2280 resize_display();
2281 redraw_display(FALSE);
2282 report("");
2283 }
2286 /*
2287 * Navigation
2288 */
2290 static bool
2291 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2292 {
2293 if (lineno >= view->lines)
2294 lineno = view->lines > 0 ? view->lines - 1 : 0;
2296 if (offset > lineno || offset + view->height <= lineno) {
2297 unsigned long half = view->height / 2;
2299 if (lineno > half)
2300 offset = lineno - half;
2301 else
2302 offset = 0;
2303 }
2305 if (offset != view->offset || lineno != view->lineno) {
2306 view->offset = offset;
2307 view->lineno = lineno;
2308 return TRUE;
2309 }
2311 return FALSE;
2312 }
2314 static int
2315 apply_step(double step, int value)
2316 {
2317 if (step >= 1)
2318 return (int) step;
2319 value *= step + 0.01;
2320 return value ? value : 1;
2321 }
2323 /* Scrolling backend */
2324 static void
2325 do_scroll_view(struct view *view, int lines)
2326 {
2327 bool redraw_current_line = FALSE;
2329 /* The rendering expects the new offset. */
2330 view->offset += lines;
2332 assert(0 <= view->offset && view->offset < view->lines);
2333 assert(lines);
2335 /* Move current line into the view. */
2336 if (view->lineno < view->offset) {
2337 view->lineno = view->offset;
2338 redraw_current_line = TRUE;
2339 } else if (view->lineno >= view->offset + view->height) {
2340 view->lineno = view->offset + view->height - 1;
2341 redraw_current_line = TRUE;
2342 }
2344 assert(view->offset <= view->lineno && view->lineno < view->lines);
2346 /* Redraw the whole screen if scrolling is pointless. */
2347 if (view->height < ABS(lines)) {
2348 redraw_view(view);
2350 } else {
2351 int line = lines > 0 ? view->height - lines : 0;
2352 int end = line + ABS(lines);
2354 scrollok(view->win, TRUE);
2355 wscrl(view->win, lines);
2356 scrollok(view->win, FALSE);
2358 while (line < end && draw_view_line(view, line))
2359 line++;
2361 if (redraw_current_line)
2362 draw_view_line(view, view->lineno - view->offset);
2363 wnoutrefresh(view->win);
2364 }
2366 view->has_scrolled = TRUE;
2367 report("");
2368 }
2370 /* Scroll frontend */
2371 static void
2372 scroll_view(struct view *view, enum request request)
2373 {
2374 int lines = 1;
2376 assert(view_is_displayed(view));
2378 switch (request) {
2379 case REQ_SCROLL_LEFT:
2380 if (view->yoffset == 0) {
2381 report("Cannot scroll beyond the first column");
2382 return;
2383 }
2384 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2385 view->yoffset = 0;
2386 else
2387 view->yoffset -= apply_step(opt_hscroll, view->width);
2388 redraw_view_from(view, 0);
2389 report("");
2390 return;
2391 case REQ_SCROLL_RIGHT:
2392 view->yoffset += apply_step(opt_hscroll, view->width);
2393 redraw_view(view);
2394 report("");
2395 return;
2396 case REQ_SCROLL_PAGE_DOWN:
2397 lines = view->height;
2398 case REQ_SCROLL_LINE_DOWN:
2399 if (view->offset + lines > view->lines)
2400 lines = view->lines - view->offset;
2402 if (lines == 0 || view->offset + view->height >= view->lines) {
2403 report("Cannot scroll beyond the last line");
2404 return;
2405 }
2406 break;
2408 case REQ_SCROLL_PAGE_UP:
2409 lines = view->height;
2410 case REQ_SCROLL_LINE_UP:
2411 if (lines > view->offset)
2412 lines = view->offset;
2414 if (lines == 0) {
2415 report("Cannot scroll beyond the first line");
2416 return;
2417 }
2419 lines = -lines;
2420 break;
2422 default:
2423 die("request %d not handled in switch", request);
2424 }
2426 do_scroll_view(view, lines);
2427 }
2429 /* Cursor moving */
2430 static void
2431 move_view(struct view *view, enum request request)
2432 {
2433 int scroll_steps = 0;
2434 int steps;
2436 switch (request) {
2437 case REQ_MOVE_FIRST_LINE:
2438 steps = -view->lineno;
2439 break;
2441 case REQ_MOVE_LAST_LINE:
2442 steps = view->lines - view->lineno - 1;
2443 break;
2445 case REQ_MOVE_PAGE_UP:
2446 steps = view->height > view->lineno
2447 ? -view->lineno : -view->height;
2448 break;
2450 case REQ_MOVE_PAGE_DOWN:
2451 steps = view->lineno + view->height >= view->lines
2452 ? view->lines - view->lineno - 1 : view->height;
2453 break;
2455 case REQ_MOVE_UP:
2456 steps = -1;
2457 break;
2459 case REQ_MOVE_DOWN:
2460 steps = 1;
2461 break;
2463 default:
2464 die("request %d not handled in switch", request);
2465 }
2467 if (steps <= 0 && view->lineno == 0) {
2468 report("Cannot move beyond the first line");
2469 return;
2471 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2472 report("Cannot move beyond the last line");
2473 return;
2474 }
2476 /* Move the current line */
2477 view->lineno += steps;
2478 assert(0 <= view->lineno && view->lineno < view->lines);
2480 /* Check whether the view needs to be scrolled */
2481 if (view->lineno < view->offset ||
2482 view->lineno >= view->offset + view->height) {
2483 scroll_steps = steps;
2484 if (steps < 0 && -steps > view->offset) {
2485 scroll_steps = -view->offset;
2487 } else if (steps > 0) {
2488 if (view->lineno == view->lines - 1 &&
2489 view->lines > view->height) {
2490 scroll_steps = view->lines - view->offset - 1;
2491 if (scroll_steps >= view->height)
2492 scroll_steps -= view->height - 1;
2493 }
2494 }
2495 }
2497 if (!view_is_displayed(view)) {
2498 view->offset += scroll_steps;
2499 assert(0 <= view->offset && view->offset < view->lines);
2500 view->ops->select(view, &view->line[view->lineno]);
2501 return;
2502 }
2504 /* Repaint the old "current" line if we be scrolling */
2505 if (ABS(steps) < view->height)
2506 draw_view_line(view, view->lineno - steps - view->offset);
2508 if (scroll_steps) {
2509 do_scroll_view(view, scroll_steps);
2510 return;
2511 }
2513 /* Draw the current line */
2514 draw_view_line(view, view->lineno - view->offset);
2516 wnoutrefresh(view->win);
2517 report("");
2518 }
2521 /*
2522 * Searching
2523 */
2525 static void search_view(struct view *view, enum request request);
2527 static bool
2528 grep_text(struct view *view, const char *text[])
2529 {
2530 regmatch_t pmatch;
2531 size_t i;
2533 for (i = 0; text[i]; i++)
2534 if (*text[i] &&
2535 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2536 return TRUE;
2537 return FALSE;
2538 }
2540 static void
2541 select_view_line(struct view *view, unsigned long lineno)
2542 {
2543 unsigned long old_lineno = view->lineno;
2544 unsigned long old_offset = view->offset;
2546 if (goto_view_line(view, view->offset, lineno)) {
2547 if (view_is_displayed(view)) {
2548 if (old_offset != view->offset) {
2549 redraw_view(view);
2550 } else {
2551 draw_view_line(view, old_lineno - view->offset);
2552 draw_view_line(view, view->lineno - view->offset);
2553 wnoutrefresh(view->win);
2554 }
2555 } else {
2556 view->ops->select(view, &view->line[view->lineno]);
2557 }
2558 }
2559 }
2561 static void
2562 find_next(struct view *view, enum request request)
2563 {
2564 unsigned long lineno = view->lineno;
2565 int direction;
2567 if (!*view->grep) {
2568 if (!*opt_search)
2569 report("No previous search");
2570 else
2571 search_view(view, request);
2572 return;
2573 }
2575 switch (request) {
2576 case REQ_SEARCH:
2577 case REQ_FIND_NEXT:
2578 direction = 1;
2579 break;
2581 case REQ_SEARCH_BACK:
2582 case REQ_FIND_PREV:
2583 direction = -1;
2584 break;
2586 default:
2587 return;
2588 }
2590 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2591 lineno += direction;
2593 /* Note, lineno is unsigned long so will wrap around in which case it
2594 * will become bigger than view->lines. */
2595 for (; lineno < view->lines; lineno += direction) {
2596 if (view->ops->grep(view, &view->line[lineno])) {
2597 select_view_line(view, lineno);
2598 report("Line %ld matches '%s'", lineno + 1, view->grep);
2599 return;
2600 }
2601 }
2603 report("No match found for '%s'", view->grep);
2604 }
2606 static void
2607 search_view(struct view *view, enum request request)
2608 {
2609 int regex_err;
2611 if (view->regex) {
2612 regfree(view->regex);
2613 *view->grep = 0;
2614 } else {
2615 view->regex = calloc(1, sizeof(*view->regex));
2616 if (!view->regex)
2617 return;
2618 }
2620 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2621 if (regex_err != 0) {
2622 char buf[SIZEOF_STR] = "unknown error";
2624 regerror(regex_err, view->regex, buf, sizeof(buf));
2625 report("Search failed: %s", buf);
2626 return;
2627 }
2629 string_copy(view->grep, opt_search);
2631 find_next(view, request);
2632 }
2634 /*
2635 * Incremental updating
2636 */
2638 static void
2639 reset_view(struct view *view)
2640 {
2641 int i;
2643 for (i = 0; i < view->lines; i++)
2644 free(view->line[i].data);
2645 free(view->line);
2647 view->p_offset = view->offset;
2648 view->p_yoffset = view->yoffset;
2649 view->p_lineno = view->lineno;
2651 view->line = NULL;
2652 view->offset = 0;
2653 view->yoffset = 0;
2654 view->lines = 0;
2655 view->lineno = 0;
2656 view->line_alloc = 0;
2657 view->vid[0] = 0;
2658 view->update_secs = 0;
2659 }
2661 static void
2662 free_argv(const char *argv[])
2663 {
2664 int argc;
2666 for (argc = 0; argv[argc]; argc++)
2667 free((void *) argv[argc]);
2668 }
2670 static bool
2671 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2672 {
2673 char buf[SIZEOF_STR];
2674 int argc;
2675 bool noreplace = flags == FORMAT_NONE;
2677 free_argv(dst_argv);
2679 for (argc = 0; src_argv[argc]; argc++) {
2680 const char *arg = src_argv[argc];
2681 size_t bufpos = 0;
2683 while (arg) {
2684 char *next = strstr(arg, "%(");
2685 int len = next - arg;
2686 const char *value;
2688 if (!next || noreplace) {
2689 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2690 noreplace = TRUE;
2691 len = strlen(arg);
2692 value = "";
2694 } else if (!prefixcmp(next, "%(directory)")) {
2695 value = opt_path;
2697 } else if (!prefixcmp(next, "%(file)")) {
2698 value = opt_file;
2700 } else if (!prefixcmp(next, "%(ref)")) {
2701 value = *opt_ref ? opt_ref : "HEAD";
2703 } else if (!prefixcmp(next, "%(head)")) {
2704 value = ref_head;
2706 } else if (!prefixcmp(next, "%(commit)")) {
2707 value = ref_commit;
2709 } else if (!prefixcmp(next, "%(blob)")) {
2710 value = ref_blob;
2712 } else {
2713 report("Unknown replacement: `%s`", next);
2714 return FALSE;
2715 }
2717 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2718 return FALSE;
2720 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2721 }
2723 dst_argv[argc] = strdup(buf);
2724 if (!dst_argv[argc])
2725 break;
2726 }
2728 dst_argv[argc] = NULL;
2730 return src_argv[argc] == NULL;
2731 }
2733 static bool
2734 restore_view_position(struct view *view)
2735 {
2736 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2737 return FALSE;
2739 /* Changing the view position cancels the restoring. */
2740 /* FIXME: Changing back to the first line is not detected. */
2741 if (view->offset != 0 || view->lineno != 0) {
2742 view->p_restore = FALSE;
2743 return FALSE;
2744 }
2746 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2747 view_is_displayed(view))
2748 werase(view->win);
2750 view->yoffset = view->p_yoffset;
2751 view->p_restore = FALSE;
2753 return TRUE;
2754 }
2756 static void
2757 end_update(struct view *view, bool force)
2758 {
2759 if (!view->pipe)
2760 return;
2761 while (!view->ops->read(view, NULL))
2762 if (!force)
2763 return;
2764 set_nonblocking_input(FALSE);
2765 if (force)
2766 kill_io(view->pipe);
2767 done_io(view->pipe);
2768 view->pipe = NULL;
2769 }
2771 static void
2772 setup_update(struct view *view, const char *vid)
2773 {
2774 set_nonblocking_input(TRUE);
2775 reset_view(view);
2776 string_copy_rev(view->vid, vid);
2777 view->pipe = &view->io;
2778 view->start_time = time(NULL);
2779 }
2781 static bool
2782 prepare_update(struct view *view, const char *argv[], const char *dir,
2783 enum format_flags flags)
2784 {
2785 if (view->pipe)
2786 end_update(view, TRUE);
2787 return init_io_rd(&view->io, argv, dir, flags);
2788 }
2790 static bool
2791 prepare_update_file(struct view *view, const char *name)
2792 {
2793 if (view->pipe)
2794 end_update(view, TRUE);
2795 return io_open(&view->io, name);
2796 }
2798 static bool
2799 begin_update(struct view *view, bool refresh)
2800 {
2801 if (view->pipe)
2802 end_update(view, TRUE);
2804 if (refresh) {
2805 if (!start_io(&view->io))
2806 return FALSE;
2808 } else {
2809 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2810 opt_path[0] = 0;
2812 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2813 return FALSE;
2815 /* Put the current ref_* value to the view title ref
2816 * member. This is needed by the blob view. Most other
2817 * views sets it automatically after loading because the
2818 * first line is a commit line. */
2819 string_copy_rev(view->ref, view->id);
2820 }
2822 setup_update(view, view->id);
2824 return TRUE;
2825 }
2827 static bool
2828 update_view(struct view *view)
2829 {
2830 char out_buffer[BUFSIZ * 2];
2831 char *line;
2832 /* Clear the view and redraw everything since the tree sorting
2833 * might have rearranged things. */
2834 bool redraw = view->lines == 0;
2835 bool can_read = TRUE;
2837 if (!view->pipe)
2838 return TRUE;
2840 if (!io_can_read(view->pipe)) {
2841 if (view->lines == 0 && view_is_displayed(view)) {
2842 time_t secs = time(NULL) - view->start_time;
2844 if (secs > 1 && secs > view->update_secs) {
2845 if (view->update_secs == 0)
2846 redraw_view(view);
2847 update_view_title(view);
2848 view->update_secs = secs;
2849 }
2850 }
2851 return TRUE;
2852 }
2854 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2855 if (opt_iconv != ICONV_NONE) {
2856 ICONV_CONST char *inbuf = line;
2857 size_t inlen = strlen(line) + 1;
2859 char *outbuf = out_buffer;
2860 size_t outlen = sizeof(out_buffer);
2862 size_t ret;
2864 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2865 if (ret != (size_t) -1)
2866 line = out_buffer;
2867 }
2869 if (!view->ops->read(view, line)) {
2870 report("Allocation failure");
2871 end_update(view, TRUE);
2872 return FALSE;
2873 }
2874 }
2876 {
2877 unsigned long lines = view->lines;
2878 int digits;
2880 for (digits = 0; lines; digits++)
2881 lines /= 10;
2883 /* Keep the displayed view in sync with line number scaling. */
2884 if (digits != view->digits) {
2885 view->digits = digits;
2886 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2887 redraw = TRUE;
2888 }
2889 }
2891 if (io_error(view->pipe)) {
2892 report("Failed to read: %s", io_strerror(view->pipe));
2893 end_update(view, TRUE);
2895 } else if (io_eof(view->pipe)) {
2896 report("");
2897 end_update(view, FALSE);
2898 }
2900 if (restore_view_position(view))
2901 redraw = TRUE;
2903 if (!view_is_displayed(view))
2904 return TRUE;
2906 if (redraw)
2907 redraw_view_from(view, 0);
2908 else
2909 redraw_view_dirty(view);
2911 /* Update the title _after_ the redraw so that if the redraw picks up a
2912 * commit reference in view->ref it'll be available here. */
2913 update_view_title(view);
2914 return TRUE;
2915 }
2917 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2919 static struct line *
2920 add_line_data(struct view *view, void *data, enum line_type type)
2921 {
2922 struct line *line;
2924 if (!realloc_lines(&view->line, &view->line_alloc, view->lines + 1))
2925 return NULL;
2927 line = &view->line[view->lines++];
2928 memset(line, 0, sizeof(*line));
2929 line->type = type;
2930 line->data = data;
2931 line->dirty = 1;
2933 return line;
2934 }
2936 static struct line *
2937 add_line_text(struct view *view, const char *text, enum line_type type)
2938 {
2939 char *data = text ? strdup(text) : NULL;
2941 return data ? add_line_data(view, data, type) : NULL;
2942 }
2944 static struct line *
2945 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2946 {
2947 char buf[SIZEOF_STR];
2948 va_list args;
2950 va_start(args, fmt);
2951 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2952 buf[0] = 0;
2953 va_end(args);
2955 return buf[0] ? add_line_text(view, buf, type) : NULL;
2956 }
2958 /*
2959 * View opening
2960 */
2962 enum open_flags {
2963 OPEN_DEFAULT = 0, /* Use default view switching. */
2964 OPEN_SPLIT = 1, /* Split current view. */
2965 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2966 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2967 OPEN_PREPARED = 32, /* Open already prepared command. */
2968 };
2970 static void
2971 open_view(struct view *prev, enum request request, enum open_flags flags)
2972 {
2973 bool split = !!(flags & OPEN_SPLIT);
2974 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2975 bool nomaximize = !!(flags & OPEN_REFRESH);
2976 struct view *view = VIEW(request);
2977 int nviews = displayed_views();
2978 struct view *base_view = display[0];
2980 if (view == prev && nviews == 1 && !reload) {
2981 report("Already in %s view", view->name);
2982 return;
2983 }
2985 if (view->git_dir && !opt_git_dir[0]) {
2986 report("The %s view is disabled in pager view", view->name);
2987 return;
2988 }
2990 if (split) {
2991 display[1] = view;
2992 current_view = 1;
2993 } else if (!nomaximize) {
2994 /* Maximize the current view. */
2995 memset(display, 0, sizeof(display));
2996 current_view = 0;
2997 display[current_view] = view;
2998 }
3000 /* Resize the view when switching between split- and full-screen,
3001 * or when switching between two different full-screen views. */
3002 if (nviews != displayed_views() ||
3003 (nviews == 1 && base_view != display[0]))
3004 resize_display();
3006 if (view->ops->open) {
3007 if (view->pipe)
3008 end_update(view, TRUE);
3009 if (!view->ops->open(view)) {
3010 report("Failed to load %s view", view->name);
3011 return;
3012 }
3013 restore_view_position(view);
3015 } else if ((reload || strcmp(view->vid, view->id)) &&
3016 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3017 report("Failed to load %s view", view->name);
3018 return;
3019 }
3021 if (split && prev->lineno - prev->offset >= prev->height) {
3022 /* Take the title line into account. */
3023 int lines = prev->lineno - prev->offset - prev->height + 1;
3025 /* Scroll the view that was split if the current line is
3026 * outside the new limited view. */
3027 do_scroll_view(prev, lines);
3028 }
3030 if (prev && view != prev) {
3031 if (split) {
3032 /* "Blur" the previous view. */
3033 update_view_title(prev);
3034 }
3036 view->parent = prev;
3037 }
3039 if (view->pipe && view->lines == 0) {
3040 /* Clear the old view and let the incremental updating refill
3041 * the screen. */
3042 werase(view->win);
3043 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3044 report("");
3045 } else if (view_is_displayed(view)) {
3046 redraw_view(view);
3047 report("");
3048 }
3049 }
3051 static void
3052 open_external_viewer(const char *argv[], const char *dir)
3053 {
3054 def_prog_mode(); /* save current tty modes */
3055 endwin(); /* restore original tty modes */
3056 run_io_fg(argv, dir);
3057 fprintf(stderr, "Press Enter to continue");
3058 getc(opt_tty);
3059 reset_prog_mode();
3060 redraw_display(TRUE);
3061 }
3063 static void
3064 open_mergetool(const char *file)
3065 {
3066 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3068 open_external_viewer(mergetool_argv, opt_cdup);
3069 }
3071 static void
3072 open_editor(bool from_root, const char *file)
3073 {
3074 const char *editor_argv[] = { "vi", file, NULL };
3075 const char *editor;
3077 editor = getenv("GIT_EDITOR");
3078 if (!editor && *opt_editor)
3079 editor = opt_editor;
3080 if (!editor)
3081 editor = getenv("VISUAL");
3082 if (!editor)
3083 editor = getenv("EDITOR");
3084 if (!editor)
3085 editor = "vi";
3087 editor_argv[0] = editor;
3088 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3089 }
3091 static void
3092 open_run_request(enum request request)
3093 {
3094 struct run_request *req = get_run_request(request);
3095 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3097 if (!req) {
3098 report("Unknown run request");
3099 return;
3100 }
3102 if (format_argv(argv, req->argv, FORMAT_ALL))
3103 open_external_viewer(argv, NULL);
3104 free_argv(argv);
3105 }
3107 /*
3108 * User request switch noodle
3109 */
3111 static int
3112 view_driver(struct view *view, enum request request)
3113 {
3114 int i;
3116 if (request == REQ_NONE)
3117 return TRUE;
3119 if (request > REQ_NONE) {
3120 open_run_request(request);
3121 /* FIXME: When all views can refresh always do this. */
3122 if (view == VIEW(REQ_VIEW_STATUS) ||
3123 view == VIEW(REQ_VIEW_MAIN) ||
3124 view == VIEW(REQ_VIEW_LOG) ||
3125 view == VIEW(REQ_VIEW_STAGE))
3126 request = REQ_REFRESH;
3127 else
3128 return TRUE;
3129 }
3131 if (view && view->lines) {
3132 request = view->ops->request(view, request, &view->line[view->lineno]);
3133 if (request == REQ_NONE)
3134 return TRUE;
3135 }
3137 switch (request) {
3138 case REQ_MOVE_UP:
3139 case REQ_MOVE_DOWN:
3140 case REQ_MOVE_PAGE_UP:
3141 case REQ_MOVE_PAGE_DOWN:
3142 case REQ_MOVE_FIRST_LINE:
3143 case REQ_MOVE_LAST_LINE:
3144 move_view(view, request);
3145 break;
3147 case REQ_SCROLL_LEFT:
3148 case REQ_SCROLL_RIGHT:
3149 case REQ_SCROLL_LINE_DOWN:
3150 case REQ_SCROLL_LINE_UP:
3151 case REQ_SCROLL_PAGE_DOWN:
3152 case REQ_SCROLL_PAGE_UP:
3153 scroll_view(view, request);
3154 break;
3156 case REQ_VIEW_BLAME:
3157 if (!opt_file[0]) {
3158 report("No file chosen, press %s to open tree view",
3159 get_key(REQ_VIEW_TREE));
3160 break;
3161 }
3162 open_view(view, request, OPEN_DEFAULT);
3163 break;
3165 case REQ_VIEW_BLOB:
3166 if (!ref_blob[0]) {
3167 report("No file chosen, press %s to open tree view",
3168 get_key(REQ_VIEW_TREE));
3169 break;
3170 }
3171 open_view(view, request, OPEN_DEFAULT);
3172 break;
3174 case REQ_VIEW_PAGER:
3175 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3176 report("No pager content, press %s to run command from prompt",
3177 get_key(REQ_PROMPT));
3178 break;
3179 }
3180 open_view(view, request, OPEN_DEFAULT);
3181 break;
3183 case REQ_VIEW_STAGE:
3184 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3185 report("No stage content, press %s to open the status view and choose file",
3186 get_key(REQ_VIEW_STATUS));
3187 break;
3188 }
3189 open_view(view, request, OPEN_DEFAULT);
3190 break;
3192 case REQ_VIEW_STATUS:
3193 if (opt_is_inside_work_tree == FALSE) {
3194 report("The status view requires a working tree");
3195 break;
3196 }
3197 open_view(view, request, OPEN_DEFAULT);
3198 break;
3200 case REQ_VIEW_MAIN:
3201 case REQ_VIEW_DIFF:
3202 case REQ_VIEW_LOG:
3203 case REQ_VIEW_TREE:
3204 case REQ_VIEW_HELP:
3205 open_view(view, request, OPEN_DEFAULT);
3206 break;
3208 case REQ_NEXT:
3209 case REQ_PREVIOUS:
3210 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3212 if ((view == VIEW(REQ_VIEW_DIFF) &&
3213 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3214 (view == VIEW(REQ_VIEW_DIFF) &&
3215 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3216 (view == VIEW(REQ_VIEW_STAGE) &&
3217 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3218 (view == VIEW(REQ_VIEW_BLOB) &&
3219 view->parent == VIEW(REQ_VIEW_TREE))) {
3220 int line;
3222 view = view->parent;
3223 line = view->lineno;
3224 move_view(view, request);
3225 if (view_is_displayed(view))
3226 update_view_title(view);
3227 if (line != view->lineno)
3228 view->ops->request(view, REQ_ENTER,
3229 &view->line[view->lineno]);
3231 } else {
3232 move_view(view, request);
3233 }
3234 break;
3236 case REQ_VIEW_NEXT:
3237 {
3238 int nviews = displayed_views();
3239 int next_view = (current_view + 1) % nviews;
3241 if (next_view == current_view) {
3242 report("Only one view is displayed");
3243 break;
3244 }
3246 current_view = next_view;
3247 /* Blur out the title of the previous view. */
3248 update_view_title(view);
3249 report("");
3250 break;
3251 }
3252 case REQ_REFRESH:
3253 report("Refreshing is not yet supported for the %s view", view->name);
3254 break;
3256 case REQ_MAXIMIZE:
3257 if (displayed_views() == 2)
3258 maximize_view(view);
3259 break;
3261 case REQ_TOGGLE_LINENO:
3262 toggle_view_option(&opt_line_number, "line numbers");
3263 break;
3265 case REQ_TOGGLE_DATE:
3266 toggle_view_option(&opt_date, "date display");
3267 break;
3269 case REQ_TOGGLE_AUTHOR:
3270 toggle_view_option(&opt_author, "author display");
3271 break;
3273 case REQ_TOGGLE_REV_GRAPH:
3274 toggle_view_option(&opt_rev_graph, "revision graph display");
3275 break;
3277 case REQ_TOGGLE_REFS:
3278 toggle_view_option(&opt_show_refs, "reference display");
3279 break;
3281 case REQ_SEARCH:
3282 case REQ_SEARCH_BACK:
3283 search_view(view, request);
3284 break;
3286 case REQ_FIND_NEXT:
3287 case REQ_FIND_PREV:
3288 find_next(view, request);
3289 break;
3291 case REQ_STOP_LOADING:
3292 for (i = 0; i < ARRAY_SIZE(views); i++) {
3293 view = &views[i];
3294 if (view->pipe)
3295 report("Stopped loading the %s view", view->name),
3296 end_update(view, TRUE);
3297 }
3298 break;
3300 case REQ_SHOW_VERSION:
3301 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3302 return TRUE;
3304 case REQ_SCREEN_REDRAW:
3305 redraw_display(TRUE);
3306 break;
3308 case REQ_EDIT:
3309 report("Nothing to edit");
3310 break;
3312 case REQ_ENTER:
3313 report("Nothing to enter");
3314 break;
3316 case REQ_VIEW_CLOSE:
3317 /* XXX: Mark closed views by letting view->parent point to the
3318 * view itself. Parents to closed view should never be
3319 * followed. */
3320 if (view->parent &&
3321 view->parent->parent != view->parent) {
3322 maximize_view(view->parent);
3323 view->parent = view;
3324 break;
3325 }
3326 /* Fall-through */
3327 case REQ_QUIT:
3328 return FALSE;
3330 default:
3331 report("Unknown key, press 'h' for help");
3332 return TRUE;
3333 }
3335 return TRUE;
3336 }
3339 /*
3340 * View backend utilities
3341 */
3343 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3345 /* Small author cache to reduce memory consumption. It uses binary
3346 * search to lookup or find place to position new entries. No entries
3347 * are ever freed. */
3348 static const char *
3349 get_author(const char *name)
3350 {
3351 static const char **authors;
3352 static size_t authors_alloc;
3353 static size_t authors_size;
3354 int from = 0, to = authors_size - 1;
3356 while (from <= to) {
3357 size_t pos = (to + from) / 2;
3358 int cmp = strcmp(name, authors[pos]);
3360 if (!cmp)
3361 return authors[pos];
3363 if (cmp < 0)
3364 to = pos - 1;
3365 else
3366 from = pos + 1;
3367 }
3369 if (!realloc_authors(&authors, &authors_alloc, authors_size + 1))
3370 return NULL;
3371 name = strdup(name);
3372 if (!name)
3373 return NULL;
3375 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3376 authors[from] = name;
3377 authors_size++;
3379 return name;
3380 }
3382 static void
3383 parse_timezone(time_t *time, const char *zone)
3384 {
3385 long tz;
3387 tz = ('0' - zone[1]) * 60 * 60 * 10;
3388 tz += ('0' - zone[2]) * 60 * 60;
3389 tz += ('0' - zone[3]) * 60;
3390 tz += ('0' - zone[4]);
3392 if (zone[0] == '-')
3393 tz = -tz;
3395 *time -= tz;
3396 }
3398 /* Parse author lines where the name may be empty:
3399 * author <email@address.tld> 1138474660 +0100
3400 */
3401 static void
3402 parse_author_line(char *ident, const char **author, time_t *time)
3403 {
3404 char *nameend = strchr(ident, '<');
3405 char *emailend = strchr(ident, '>');
3407 if (nameend && emailend)
3408 *nameend = *emailend = 0;
3409 ident = chomp_string(ident);
3410 if (!*ident) {
3411 if (nameend)
3412 ident = chomp_string(nameend + 1);
3413 if (!*ident)
3414 ident = "Unknown";
3415 }
3417 *author = get_author(ident);
3419 /* Parse epoch and timezone */
3420 if (emailend && emailend[1] == ' ') {
3421 char *secs = emailend + 2;
3422 char *zone = strchr(secs, ' ');
3424 *time = (time_t) atol(secs);
3426 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3427 parse_timezone(time, zone + 1);
3428 }
3429 }
3431 static enum input_status
3432 select_commit_parent_handler(void *data, char *buf, int c)
3433 {
3434 size_t parents = *(size_t *) data;
3435 int parent = 0;
3437 if (!isdigit(c))
3438 return INPUT_SKIP;
3440 if (*buf)
3441 parent = atoi(buf) * 10;
3442 parent += c - '0';
3444 if (parent > parents)
3445 return INPUT_SKIP;
3446 return INPUT_OK;
3447 }
3449 static bool
3450 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3451 {
3452 char buf[SIZEOF_STR * 4];
3453 const char *revlist_argv[] = {
3454 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3455 };
3456 int parents;
3458 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3459 !*chomp_string(buf) ||
3460 (parents = (strlen(buf) / 40) - 1) < 0) {
3461 report("Failed to get parent information");
3462 return FALSE;
3464 } else if (parents == 0) {
3465 if (path)
3466 report("Path '%s' does not exist in the parent", path);
3467 else
3468 report("The selected commit has no parents");
3469 return FALSE;
3470 }
3472 if (parents > 1) {
3473 char prompt[SIZEOF_STR];
3474 char *result;
3476 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3477 return FALSE;
3478 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3479 if (!result)
3480 return FALSE;
3481 parents = atoi(result);
3482 }
3484 string_copy_rev(rev, &buf[41 * parents]);
3485 return TRUE;
3486 }
3488 /*
3489 * Pager backend
3490 */
3492 static bool
3493 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3494 {
3495 char text[SIZEOF_STR];
3497 if (opt_line_number && draw_lineno(view, lineno))
3498 return TRUE;
3500 string_expand(text, sizeof(text), line->data, opt_tab_size);
3501 draw_text(view, line->type, text, TRUE);
3502 return TRUE;
3503 }
3505 static bool
3506 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3507 {
3508 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3509 char refbuf[SIZEOF_STR];
3510 char *ref = NULL;
3512 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3513 ref = chomp_string(refbuf);
3515 if (!ref || !*ref)
3516 return TRUE;
3518 /* This is the only fatal call, since it can "corrupt" the buffer. */
3519 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3520 return FALSE;
3522 return TRUE;
3523 }
3525 static void
3526 add_pager_refs(struct view *view, struct line *line)
3527 {
3528 char buf[SIZEOF_STR];
3529 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3530 struct ref **refs;
3531 size_t bufpos = 0, refpos = 0;
3532 const char *sep = "Refs: ";
3533 bool is_tag = FALSE;
3535 assert(line->type == LINE_COMMIT);
3537 refs = get_refs(commit_id);
3538 if (!refs) {
3539 if (view == VIEW(REQ_VIEW_DIFF))
3540 goto try_add_describe_ref;
3541 return;
3542 }
3544 do {
3545 struct ref *ref = refs[refpos];
3546 const char *fmt = ref->tag ? "%s[%s]" :
3547 ref->remote ? "%s<%s>" : "%s%s";
3549 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3550 return;
3551 sep = ", ";
3552 if (ref->tag)
3553 is_tag = TRUE;
3554 } while (refs[refpos++]->next);
3556 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3557 try_add_describe_ref:
3558 /* Add <tag>-g<commit_id> "fake" reference. */
3559 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3560 return;
3561 }
3563 if (bufpos == 0)
3564 return;
3566 add_line_text(view, buf, LINE_PP_REFS);
3567 }
3569 static bool
3570 pager_read(struct view *view, char *data)
3571 {
3572 struct line *line;
3574 if (!data)
3575 return TRUE;
3577 line = add_line_text(view, data, get_line_type(data));
3578 if (!line)
3579 return FALSE;
3581 if (line->type == LINE_COMMIT &&
3582 (view == VIEW(REQ_VIEW_DIFF) ||
3583 view == VIEW(REQ_VIEW_LOG)))
3584 add_pager_refs(view, line);
3586 return TRUE;
3587 }
3589 static enum request
3590 pager_request(struct view *view, enum request request, struct line *line)
3591 {
3592 int split = 0;
3594 if (request != REQ_ENTER)
3595 return request;
3597 if (line->type == LINE_COMMIT &&
3598 (view == VIEW(REQ_VIEW_LOG) ||
3599 view == VIEW(REQ_VIEW_PAGER))) {
3600 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3601 split = 1;
3602 }
3604 /* Always scroll the view even if it was split. That way
3605 * you can use Enter to scroll through the log view and
3606 * split open each commit diff. */
3607 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3609 /* FIXME: A minor workaround. Scrolling the view will call report("")
3610 * but if we are scrolling a non-current view this won't properly
3611 * update the view title. */
3612 if (split)
3613 update_view_title(view);
3615 return REQ_NONE;
3616 }
3618 static bool
3619 pager_grep(struct view *view, struct line *line)
3620 {
3621 const char *text[] = { line->data, NULL };
3623 return grep_text(view, text);
3624 }
3626 static void
3627 pager_select(struct view *view, struct line *line)
3628 {
3629 if (line->type == LINE_COMMIT) {
3630 char *text = (char *)line->data + STRING_SIZE("commit ");
3632 if (view != VIEW(REQ_VIEW_PAGER))
3633 string_copy_rev(view->ref, text);
3634 string_copy_rev(ref_commit, text);
3635 }
3636 }
3638 static struct view_ops pager_ops = {
3639 "line",
3640 NULL,
3641 NULL,
3642 pager_read,
3643 pager_draw,
3644 pager_request,
3645 pager_grep,
3646 pager_select,
3647 };
3649 static const char *log_argv[SIZEOF_ARG] = {
3650 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3651 };
3653 static enum request
3654 log_request(struct view *view, enum request request, struct line *line)
3655 {
3656 switch (request) {
3657 case REQ_REFRESH:
3658 load_refs();
3659 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3660 return REQ_NONE;
3661 default:
3662 return pager_request(view, request, line);
3663 }
3664 }
3666 static struct view_ops log_ops = {
3667 "line",
3668 log_argv,
3669 NULL,
3670 pager_read,
3671 pager_draw,
3672 log_request,
3673 pager_grep,
3674 pager_select,
3675 };
3677 static const char *diff_argv[SIZEOF_ARG] = {
3678 "git", "show", "--pretty=fuller", "--no-color", "--root",
3679 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3680 };
3682 static struct view_ops diff_ops = {
3683 "line",
3684 diff_argv,
3685 NULL,
3686 pager_read,
3687 pager_draw,
3688 pager_request,
3689 pager_grep,
3690 pager_select,
3691 };
3693 /*
3694 * Help backend
3695 */
3697 static bool
3698 help_open(struct view *view)
3699 {
3700 char buf[SIZEOF_STR];
3701 size_t bufpos;
3702 int i;
3704 if (view->lines > 0)
3705 return TRUE;
3707 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3709 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3710 const char *key;
3712 if (req_info[i].request == REQ_NONE)
3713 continue;
3715 if (!req_info[i].request) {
3716 add_line_text(view, "", LINE_DEFAULT);
3717 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3718 continue;
3719 }
3721 key = get_key(req_info[i].request);
3722 if (!*key)
3723 key = "(no key defined)";
3725 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3726 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3727 if (buf[bufpos] == '_')
3728 buf[bufpos] = '-';
3729 }
3731 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3732 key, buf, req_info[i].help);
3733 }
3735 if (run_requests) {
3736 add_line_text(view, "", LINE_DEFAULT);
3737 add_line_text(view, "External commands:", LINE_DEFAULT);
3738 }
3740 for (i = 0; i < run_requests; i++) {
3741 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3742 const char *key;
3743 int argc;
3745 if (!req)
3746 continue;
3748 key = get_key_name(req->key);
3749 if (!*key)
3750 key = "(no key defined)";
3752 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3753 if (!string_format_from(buf, &bufpos, "%s%s",
3754 argc ? " " : "", req->argv[argc]))
3755 return REQ_NONE;
3757 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3758 keymap_table[req->keymap].name, key, buf);
3759 }
3761 return TRUE;
3762 }
3764 static struct view_ops help_ops = {
3765 "line",
3766 NULL,
3767 help_open,
3768 NULL,
3769 pager_draw,
3770 pager_request,
3771 pager_grep,
3772 pager_select,
3773 };
3776 /*
3777 * Tree backend
3778 */
3780 struct tree_stack_entry {
3781 struct tree_stack_entry *prev; /* Entry below this in the stack */
3782 unsigned long lineno; /* Line number to restore */
3783 char *name; /* Position of name in opt_path */
3784 };
3786 /* The top of the path stack. */
3787 static struct tree_stack_entry *tree_stack = NULL;
3788 unsigned long tree_lineno = 0;
3790 static void
3791 pop_tree_stack_entry(void)
3792 {
3793 struct tree_stack_entry *entry = tree_stack;
3795 tree_lineno = entry->lineno;
3796 entry->name[0] = 0;
3797 tree_stack = entry->prev;
3798 free(entry);
3799 }
3801 static void
3802 push_tree_stack_entry(const char *name, unsigned long lineno)
3803 {
3804 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3805 size_t pathlen = strlen(opt_path);
3807 if (!entry)
3808 return;
3810 entry->prev = tree_stack;
3811 entry->name = opt_path + pathlen;
3812 tree_stack = entry;
3814 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3815 pop_tree_stack_entry();
3816 return;
3817 }
3819 /* Move the current line to the first tree entry. */
3820 tree_lineno = 1;
3821 entry->lineno = lineno;
3822 }
3824 /* Parse output from git-ls-tree(1):
3825 *
3826 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3827 */
3829 #define SIZEOF_TREE_ATTR \
3830 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3832 #define SIZEOF_TREE_MODE \
3833 STRING_SIZE("100644 ")
3835 #define TREE_ID_OFFSET \
3836 STRING_SIZE("100644 blob ")
3838 struct tree_entry {
3839 char id[SIZEOF_REV];
3840 mode_t mode;
3841 time_t time; /* Date from the author ident. */
3842 const char *author; /* Author of the commit. */
3843 char name[1];
3844 };
3846 static const char *
3847 tree_path(struct line *line)
3848 {
3849 return ((struct tree_entry *) line->data)->name;
3850 }
3853 static int
3854 tree_compare_entry(struct line *line1, struct line *line2)
3855 {
3856 if (line1->type != line2->type)
3857 return line1->type == LINE_TREE_DIR ? -1 : 1;
3858 return strcmp(tree_path(line1), tree_path(line2));
3859 }
3861 static struct line *
3862 tree_entry(struct view *view, enum line_type type, const char *path,
3863 const char *mode, const char *id)
3864 {
3865 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3866 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3868 if (!entry || !line) {
3869 free(entry);
3870 return NULL;
3871 }
3873 strncpy(entry->name, path, strlen(path));
3874 if (mode)
3875 entry->mode = strtoul(mode, NULL, 8);
3876 if (id)
3877 string_copy_rev(entry->id, id);
3879 return line;
3880 }
3882 static bool
3883 tree_read_date(struct view *view, char *text, bool *read_date)
3884 {
3885 static const char *author_name;
3886 static time_t author_time;
3888 if (!text && *read_date) {
3889 *read_date = FALSE;
3890 return TRUE;
3892 } else if (!text) {
3893 char *path = *opt_path ? opt_path : ".";
3894 /* Find next entry to process */
3895 const char *log_file[] = {
3896 "git", "log", "--no-color", "--pretty=raw",
3897 "--cc", "--raw", view->id, "--", path, NULL
3898 };
3899 struct io io = {};
3901 if (!view->lines) {
3902 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3903 report("Tree is empty");
3904 return TRUE;
3905 }
3907 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3908 report("Failed to load tree data");
3909 return TRUE;
3910 }
3912 done_io(view->pipe);
3913 view->io = io;
3914 *read_date = TRUE;
3915 return FALSE;
3917 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3918 parse_author_line(text + STRING_SIZE("author "),
3919 &author_name, &author_time);
3921 } else if (*text == ':') {
3922 char *pos;
3923 size_t annotated = 1;
3924 size_t i;
3926 pos = strchr(text, '\t');
3927 if (!pos)
3928 return TRUE;
3929 text = pos + 1;
3930 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3931 text += strlen(opt_prefix);
3932 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3933 text += strlen(opt_path);
3934 pos = strchr(text, '/');
3935 if (pos)
3936 *pos = 0;
3938 for (i = 1; i < view->lines; i++) {
3939 struct line *line = &view->line[i];
3940 struct tree_entry *entry = line->data;
3942 annotated += !!entry->author;
3943 if (entry->author || strcmp(entry->name, text))
3944 continue;
3946 entry->author = author_name;
3947 entry->time = author_time;
3948 line->dirty = 1;
3949 break;
3950 }
3952 if (annotated == view->lines)
3953 kill_io(view->pipe);
3954 }
3955 return TRUE;
3956 }
3958 static bool
3959 tree_read(struct view *view, char *text)
3960 {
3961 static bool read_date = FALSE;
3962 struct tree_entry *data;
3963 struct line *entry, *line;
3964 enum line_type type;
3965 size_t textlen = text ? strlen(text) : 0;
3966 char *path = text + SIZEOF_TREE_ATTR;
3968 if (read_date || !text)
3969 return tree_read_date(view, text, &read_date);
3971 if (textlen <= SIZEOF_TREE_ATTR)
3972 return FALSE;
3973 if (view->lines == 0 &&
3974 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3975 return FALSE;
3977 /* Strip the path part ... */
3978 if (*opt_path) {
3979 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3980 size_t striplen = strlen(opt_path);
3982 if (pathlen > striplen)
3983 memmove(path, path + striplen,
3984 pathlen - striplen + 1);
3986 /* Insert "link" to parent directory. */
3987 if (view->lines == 1 &&
3988 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3989 return FALSE;
3990 }
3992 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3993 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3994 if (!entry)
3995 return FALSE;
3996 data = entry->data;
3998 /* Skip "Directory ..." and ".." line. */
3999 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4000 if (tree_compare_entry(line, entry) <= 0)
4001 continue;
4003 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4005 line->data = data;
4006 line->type = type;
4007 for (; line <= entry; line++)
4008 line->dirty = line->cleareol = 1;
4009 return TRUE;
4010 }
4012 if (tree_lineno > view->lineno) {
4013 view->lineno = tree_lineno;
4014 tree_lineno = 0;
4015 }
4017 return TRUE;
4018 }
4020 static bool
4021 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4022 {
4023 struct tree_entry *entry = line->data;
4025 if (line->type == LINE_TREE_HEAD) {
4026 if (draw_text(view, line->type, "Directory path /", TRUE))
4027 return TRUE;
4028 } else {
4029 if (draw_mode(view, entry->mode))
4030 return TRUE;
4032 if (opt_author && draw_author(view, entry->author))
4033 return TRUE;
4035 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4036 return TRUE;
4037 }
4038 if (draw_text(view, line->type, entry->name, TRUE))
4039 return TRUE;
4040 return TRUE;
4041 }
4043 static void
4044 open_blob_editor()
4045 {
4046 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4047 int fd = mkstemp(file);
4049 if (fd == -1)
4050 report("Failed to create temporary file");
4051 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4052 report("Failed to save blob data to file");
4053 else
4054 open_editor(FALSE, file);
4055 if (fd != -1)
4056 unlink(file);
4057 }
4059 static enum request
4060 tree_request(struct view *view, enum request request, struct line *line)
4061 {
4062 enum open_flags flags;
4064 switch (request) {
4065 case REQ_VIEW_BLAME:
4066 if (line->type != LINE_TREE_FILE) {
4067 report("Blame only supported for files");
4068 return REQ_NONE;
4069 }
4071 string_copy(opt_ref, view->vid);
4072 return request;
4074 case REQ_EDIT:
4075 if (line->type != LINE_TREE_FILE) {
4076 report("Edit only supported for files");
4077 } else if (!is_head_commit(view->vid)) {
4078 open_blob_editor();
4079 } else {
4080 open_editor(TRUE, opt_file);
4081 }
4082 return REQ_NONE;
4084 case REQ_PARENT:
4085 if (!*opt_path) {
4086 /* quit view if at top of tree */
4087 return REQ_VIEW_CLOSE;
4088 }
4089 /* fake 'cd ..' */
4090 line = &view->line[1];
4091 break;
4093 case REQ_ENTER:
4094 break;
4096 default:
4097 return request;
4098 }
4100 /* Cleanup the stack if the tree view is at a different tree. */
4101 while (!*opt_path && tree_stack)
4102 pop_tree_stack_entry();
4104 switch (line->type) {
4105 case LINE_TREE_DIR:
4106 /* Depending on whether it is a subdirectory or parent link
4107 * mangle the path buffer. */
4108 if (line == &view->line[1] && *opt_path) {
4109 pop_tree_stack_entry();
4111 } else {
4112 const char *basename = tree_path(line);
4114 push_tree_stack_entry(basename, view->lineno);
4115 }
4117 /* Trees and subtrees share the same ID, so they are not not
4118 * unique like blobs. */
4119 flags = OPEN_RELOAD;
4120 request = REQ_VIEW_TREE;
4121 break;
4123 case LINE_TREE_FILE:
4124 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4125 request = REQ_VIEW_BLOB;
4126 break;
4128 default:
4129 return REQ_NONE;
4130 }
4132 open_view(view, request, flags);
4133 if (request == REQ_VIEW_TREE)
4134 view->lineno = tree_lineno;
4136 return REQ_NONE;
4137 }
4139 static bool
4140 tree_grep(struct view *view, struct line *line)
4141 {
4142 struct tree_entry *entry = line->data;
4143 const char *text[] = {
4144 entry->name,
4145 opt_author ? entry->author : "",
4146 opt_date ? mkdate(&entry->time) : "",
4147 NULL
4148 };
4150 return grep_text(view, text);
4151 }
4153 static void
4154 tree_select(struct view *view, struct line *line)
4155 {
4156 struct tree_entry *entry = line->data;
4158 if (line->type == LINE_TREE_FILE) {
4159 string_copy_rev(ref_blob, entry->id);
4160 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4162 } else if (line->type != LINE_TREE_DIR) {
4163 return;
4164 }
4166 string_copy_rev(view->ref, entry->id);
4167 }
4169 static const char *tree_argv[SIZEOF_ARG] = {
4170 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4171 };
4173 static struct view_ops tree_ops = {
4174 "file",
4175 tree_argv,
4176 NULL,
4177 tree_read,
4178 tree_draw,
4179 tree_request,
4180 tree_grep,
4181 tree_select,
4182 };
4184 static bool
4185 blob_read(struct view *view, char *line)
4186 {
4187 if (!line)
4188 return TRUE;
4189 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4190 }
4192 static enum request
4193 blob_request(struct view *view, enum request request, struct line *line)
4194 {
4195 switch (request) {
4196 case REQ_EDIT:
4197 open_blob_editor();
4198 return REQ_NONE;
4199 default:
4200 return pager_request(view, request, line);
4201 }
4202 }
4204 static const char *blob_argv[SIZEOF_ARG] = {
4205 "git", "cat-file", "blob", "%(blob)", NULL
4206 };
4208 static struct view_ops blob_ops = {
4209 "line",
4210 blob_argv,
4211 NULL,
4212 blob_read,
4213 pager_draw,
4214 blob_request,
4215 pager_grep,
4216 pager_select,
4217 };
4219 /*
4220 * Blame backend
4221 *
4222 * Loading the blame view is a two phase job:
4223 *
4224 * 1. File content is read either using opt_file from the
4225 * filesystem or using git-cat-file.
4226 * 2. Then blame information is incrementally added by
4227 * reading output from git-blame.
4228 */
4230 static const char *blame_head_argv[] = {
4231 "git", "blame", "--incremental", "--", "%(file)", NULL
4232 };
4234 static const char *blame_ref_argv[] = {
4235 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4236 };
4238 static const char *blame_cat_file_argv[] = {
4239 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4240 };
4242 struct blame_commit {
4243 char id[SIZEOF_REV]; /* SHA1 ID. */
4244 char title[128]; /* First line of the commit message. */
4245 const char *author; /* Author of the commit. */
4246 time_t time; /* Date from the author ident. */
4247 char filename[128]; /* Name of file. */
4248 bool has_previous; /* Was a "previous" line detected. */
4249 };
4251 struct blame {
4252 struct blame_commit *commit;
4253 unsigned long lineno;
4254 char text[1];
4255 };
4257 static bool
4258 blame_open(struct view *view)
4259 {
4260 if (*opt_ref || !io_open(&view->io, opt_file)) {
4261 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4262 return FALSE;
4263 }
4265 setup_update(view, opt_file);
4266 string_format(view->ref, "%s ...", opt_file);
4268 return TRUE;
4269 }
4271 static struct blame_commit *
4272 get_blame_commit(struct view *view, const char *id)
4273 {
4274 size_t i;
4276 for (i = 0; i < view->lines; i++) {
4277 struct blame *blame = view->line[i].data;
4279 if (!blame->commit)
4280 continue;
4282 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4283 return blame->commit;
4284 }
4286 {
4287 struct blame_commit *commit = calloc(1, sizeof(*commit));
4289 if (commit)
4290 string_ncopy(commit->id, id, SIZEOF_REV);
4291 return commit;
4292 }
4293 }
4295 static bool
4296 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4297 {
4298 const char *pos = *posref;
4300 *posref = NULL;
4301 pos = strchr(pos + 1, ' ');
4302 if (!pos || !isdigit(pos[1]))
4303 return FALSE;
4304 *number = atoi(pos + 1);
4305 if (*number < min || *number > max)
4306 return FALSE;
4308 *posref = pos;
4309 return TRUE;
4310 }
4312 static struct blame_commit *
4313 parse_blame_commit(struct view *view, const char *text, int *blamed)
4314 {
4315 struct blame_commit *commit;
4316 struct blame *blame;
4317 const char *pos = text + SIZEOF_REV - 2;
4318 size_t orig_lineno = 0;
4319 size_t lineno;
4320 size_t group;
4322 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4323 return NULL;
4325 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4326 !parse_number(&pos, &lineno, 1, view->lines) ||
4327 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4328 return NULL;
4330 commit = get_blame_commit(view, text);
4331 if (!commit)
4332 return NULL;
4334 *blamed += group;
4335 while (group--) {
4336 struct line *line = &view->line[lineno + group - 1];
4338 blame = line->data;
4339 blame->commit = commit;
4340 blame->lineno = orig_lineno + group - 1;
4341 line->dirty = 1;
4342 }
4344 return commit;
4345 }
4347 static bool
4348 blame_read_file(struct view *view, const char *line, bool *read_file)
4349 {
4350 if (!line) {
4351 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4352 struct io io = {};
4354 if (view->lines == 0 && !view->parent)
4355 die("No blame exist for %s", view->vid);
4357 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4358 report("Failed to load blame data");
4359 return TRUE;
4360 }
4362 done_io(view->pipe);
4363 view->io = io;
4364 *read_file = FALSE;
4365 return FALSE;
4367 } else {
4368 size_t linelen = strlen(line);
4369 struct blame *blame = malloc(sizeof(*blame) + linelen);
4371 if (!blame)
4372 return FALSE;
4374 blame->commit = NULL;
4375 strncpy(blame->text, line, linelen);
4376 blame->text[linelen] = 0;
4377 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4378 }
4379 }
4381 static bool
4382 match_blame_header(const char *name, char **line)
4383 {
4384 size_t namelen = strlen(name);
4385 bool matched = !strncmp(name, *line, namelen);
4387 if (matched)
4388 *line += namelen;
4390 return matched;
4391 }
4393 static bool
4394 blame_read(struct view *view, char *line)
4395 {
4396 static struct blame_commit *commit = NULL;
4397 static int blamed = 0;
4398 static bool read_file = TRUE;
4400 if (read_file)
4401 return blame_read_file(view, line, &read_file);
4403 if (!line) {
4404 /* Reset all! */
4405 commit = NULL;
4406 blamed = 0;
4407 read_file = TRUE;
4408 string_format(view->ref, "%s", view->vid);
4409 if (view_is_displayed(view)) {
4410 update_view_title(view);
4411 redraw_view_from(view, 0);
4412 }
4413 return TRUE;
4414 }
4416 if (!commit) {
4417 commit = parse_blame_commit(view, line, &blamed);
4418 string_format(view->ref, "%s %2d%%", view->vid,
4419 view->lines ? blamed * 100 / view->lines : 0);
4421 } else if (match_blame_header("author ", &line)) {
4422 commit->author = get_author(line);
4424 } else if (match_blame_header("author-time ", &line)) {
4425 commit->time = (time_t) atol(line);
4427 } else if (match_blame_header("author-tz ", &line)) {
4428 parse_timezone(&commit->time, line);
4430 } else if (match_blame_header("summary ", &line)) {
4431 string_ncopy(commit->title, line, strlen(line));
4433 } else if (match_blame_header("previous ", &line)) {
4434 commit->has_previous = TRUE;
4436 } else if (match_blame_header("filename ", &line)) {
4437 string_ncopy(commit->filename, line, strlen(line));
4438 commit = NULL;
4439 }
4441 return TRUE;
4442 }
4444 static bool
4445 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4446 {
4447 struct blame *blame = line->data;
4448 time_t *time = NULL;
4449 const char *id = NULL, *author = NULL;
4450 char text[SIZEOF_STR];
4452 if (blame->commit && *blame->commit->filename) {
4453 id = blame->commit->id;
4454 author = blame->commit->author;
4455 time = &blame->commit->time;
4456 }
4458 if (opt_date && draw_date(view, time))
4459 return TRUE;
4461 if (opt_author && draw_author(view, author))
4462 return TRUE;
4464 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4465 return TRUE;
4467 if (draw_lineno(view, lineno))
4468 return TRUE;
4470 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4471 draw_text(view, LINE_DEFAULT, text, TRUE);
4472 return TRUE;
4473 }
4475 static bool
4476 check_blame_commit(struct blame *blame, bool check_null_id)
4477 {
4478 if (!blame->commit)
4479 report("Commit data not loaded yet");
4480 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4481 report("No commit exist for the selected line");
4482 else
4483 return TRUE;
4484 return FALSE;
4485 }
4487 static void
4488 setup_blame_parent_line(struct view *view, struct blame *blame)
4489 {
4490 const char *diff_tree_argv[] = {
4491 "git", "diff-tree", "-U0", blame->commit->id,
4492 "--", blame->commit->filename, NULL
4493 };
4494 struct io io = {};
4495 int parent_lineno = -1;
4496 int blamed_lineno = -1;
4497 char *line;
4499 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4500 return;
4502 while ((line = io_get(&io, '\n', TRUE))) {
4503 if (*line == '@') {
4504 char *pos = strchr(line, '+');
4506 parent_lineno = atoi(line + 4);
4507 if (pos)
4508 blamed_lineno = atoi(pos + 1);
4510 } else if (*line == '+' && parent_lineno != -1) {
4511 if (blame->lineno == blamed_lineno - 1 &&
4512 !strcmp(blame->text, line + 1)) {
4513 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4514 break;
4515 }
4516 blamed_lineno++;
4517 }
4518 }
4520 done_io(&io);
4521 }
4523 static enum request
4524 blame_request(struct view *view, enum request request, struct line *line)
4525 {
4526 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4527 struct blame *blame = line->data;
4529 switch (request) {
4530 case REQ_VIEW_BLAME:
4531 if (check_blame_commit(blame, TRUE)) {
4532 string_copy(opt_ref, blame->commit->id);
4533 string_copy(opt_file, blame->commit->filename);
4534 if (blame->lineno)
4535 view->lineno = blame->lineno;
4536 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4537 }
4538 break;
4540 case REQ_PARENT:
4541 if (check_blame_commit(blame, TRUE) &&
4542 select_commit_parent(blame->commit->id, opt_ref,
4543 blame->commit->filename)) {
4544 string_copy(opt_file, blame->commit->filename);
4545 setup_blame_parent_line(view, blame);
4546 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4547 }
4548 break;
4550 case REQ_ENTER:
4551 if (!check_blame_commit(blame, FALSE))
4552 break;
4554 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4555 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4556 break;
4558 if (!strcmp(blame->commit->id, NULL_ID)) {
4559 struct view *diff = VIEW(REQ_VIEW_DIFF);
4560 const char *diff_index_argv[] = {
4561 "git", "diff-index", "--root", "--patch-with-stat",
4562 "-C", "-M", "HEAD", "--", view->vid, NULL
4563 };
4565 if (!blame->commit->has_previous) {
4566 diff_index_argv[1] = "diff";
4567 diff_index_argv[2] = "--no-color";
4568 diff_index_argv[6] = "--";
4569 diff_index_argv[7] = "/dev/null";
4570 }
4572 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4573 report("Failed to allocate diff command");
4574 break;
4575 }
4576 flags |= OPEN_PREPARED;
4577 }
4579 open_view(view, REQ_VIEW_DIFF, flags);
4580 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4581 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4582 break;
4584 default:
4585 return request;
4586 }
4588 return REQ_NONE;
4589 }
4591 static bool
4592 blame_grep(struct view *view, struct line *line)
4593 {
4594 struct blame *blame = line->data;
4595 struct blame_commit *commit = blame->commit;
4596 const char *text[] = {
4597 blame->text,
4598 commit ? commit->title : "",
4599 commit ? commit->id : "",
4600 commit && opt_author ? commit->author : "",
4601 commit && opt_date ? mkdate(&commit->time) : "",
4602 };
4604 return grep_text(view, text);
4605 }
4607 static void
4608 blame_select(struct view *view, struct line *line)
4609 {
4610 struct blame *blame = line->data;
4611 struct blame_commit *commit = blame->commit;
4613 if (!commit)
4614 return;
4616 if (!strcmp(commit->id, NULL_ID))
4617 string_ncopy(ref_commit, "HEAD", 4);
4618 else
4619 string_copy_rev(ref_commit, commit->id);
4620 }
4622 static struct view_ops blame_ops = {
4623 "line",
4624 NULL,
4625 blame_open,
4626 blame_read,
4627 blame_draw,
4628 blame_request,
4629 blame_grep,
4630 blame_select,
4631 };
4633 /*
4634 * Status backend
4635 */
4637 struct status {
4638 char status;
4639 struct {
4640 mode_t mode;
4641 char rev[SIZEOF_REV];
4642 char name[SIZEOF_STR];
4643 } old;
4644 struct {
4645 mode_t mode;
4646 char rev[SIZEOF_REV];
4647 char name[SIZEOF_STR];
4648 } new;
4649 };
4651 static char status_onbranch[SIZEOF_STR];
4652 static struct status stage_status;
4653 static enum line_type stage_line_type;
4654 static size_t stage_chunks;
4655 static int *stage_chunk;
4657 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4659 /* This should work even for the "On branch" line. */
4660 static inline bool
4661 status_has_none(struct view *view, struct line *line)
4662 {
4663 return line < view->line + view->lines && !line[1].data;
4664 }
4666 /* Get fields from the diff line:
4667 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4668 */
4669 static inline bool
4670 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4671 {
4672 const char *old_mode = buf + 1;
4673 const char *new_mode = buf + 8;
4674 const char *old_rev = buf + 15;
4675 const char *new_rev = buf + 56;
4676 const char *status = buf + 97;
4678 if (bufsize < 98 ||
4679 old_mode[-1] != ':' ||
4680 new_mode[-1] != ' ' ||
4681 old_rev[-1] != ' ' ||
4682 new_rev[-1] != ' ' ||
4683 status[-1] != ' ')
4684 return FALSE;
4686 file->status = *status;
4688 string_copy_rev(file->old.rev, old_rev);
4689 string_copy_rev(file->new.rev, new_rev);
4691 file->old.mode = strtoul(old_mode, NULL, 8);
4692 file->new.mode = strtoul(new_mode, NULL, 8);
4694 file->old.name[0] = file->new.name[0] = 0;
4696 return TRUE;
4697 }
4699 static bool
4700 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4701 {
4702 struct status *unmerged = NULL;
4703 char *buf;
4704 struct io io = {};
4706 if (!run_io(&io, argv, NULL, IO_RD))
4707 return FALSE;
4709 add_line_data(view, NULL, type);
4711 while ((buf = io_get(&io, 0, TRUE))) {
4712 struct status *file = unmerged;
4714 if (!file) {
4715 file = calloc(1, sizeof(*file));
4716 if (!file || !add_line_data(view, file, type))
4717 goto error_out;
4718 }
4720 /* Parse diff info part. */
4721 if (status) {
4722 file->status = status;
4723 if (status == 'A')
4724 string_copy(file->old.rev, NULL_ID);
4726 } else if (!file->status || file == unmerged) {
4727 if (!status_get_diff(file, buf, strlen(buf)))
4728 goto error_out;
4730 buf = io_get(&io, 0, TRUE);
4731 if (!buf)
4732 break;
4734 /* Collapse all modified entries that follow an
4735 * associated unmerged entry. */
4736 if (unmerged == file) {
4737 unmerged->status = 'U';
4738 unmerged = NULL;
4739 } else if (file->status == 'U') {
4740 unmerged = file;
4741 }
4742 }
4744 /* Grab the old name for rename/copy. */
4745 if (!*file->old.name &&
4746 (file->status == 'R' || file->status == 'C')) {
4747 string_ncopy(file->old.name, buf, strlen(buf));
4749 buf = io_get(&io, 0, TRUE);
4750 if (!buf)
4751 break;
4752 }
4754 /* git-ls-files just delivers a NUL separated list of
4755 * file names similar to the second half of the
4756 * git-diff-* output. */
4757 string_ncopy(file->new.name, buf, strlen(buf));
4758 if (!*file->old.name)
4759 string_copy(file->old.name, file->new.name);
4760 file = NULL;
4761 }
4763 if (io_error(&io)) {
4764 error_out:
4765 done_io(&io);
4766 return FALSE;
4767 }
4769 if (!view->line[view->lines - 1].data)
4770 add_line_data(view, NULL, LINE_STAT_NONE);
4772 done_io(&io);
4773 return TRUE;
4774 }
4776 /* Don't show unmerged entries in the staged section. */
4777 static const char *status_diff_index_argv[] = {
4778 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4779 "--cached", "-M", "HEAD", NULL
4780 };
4782 static const char *status_diff_files_argv[] = {
4783 "git", "diff-files", "-z", NULL
4784 };
4786 static const char *status_list_other_argv[] = {
4787 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4788 };
4790 static const char *status_list_no_head_argv[] = {
4791 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4792 };
4794 static const char *update_index_argv[] = {
4795 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4796 };
4798 /* Restore the previous line number to stay in the context or select a
4799 * line with something that can be updated. */
4800 static void
4801 status_restore(struct view *view)
4802 {
4803 if (view->p_lineno >= view->lines)
4804 view->p_lineno = view->lines - 1;
4805 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4806 view->p_lineno++;
4807 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4808 view->p_lineno--;
4810 /* If the above fails, always skip the "On branch" line. */
4811 if (view->p_lineno < view->lines)
4812 view->lineno = view->p_lineno;
4813 else
4814 view->lineno = 1;
4816 if (view->lineno < view->offset)
4817 view->offset = view->lineno;
4818 else if (view->offset + view->height <= view->lineno)
4819 view->offset = view->lineno - view->height + 1;
4821 view->p_restore = FALSE;
4822 }
4824 static void
4825 status_update_onbranch(void)
4826 {
4827 static const char *paths[][2] = {
4828 { "rebase-apply/rebasing", "Rebasing" },
4829 { "rebase-apply/applying", "Applying mailbox" },
4830 { "rebase-apply/", "Rebasing mailbox" },
4831 { "rebase-merge/interactive", "Interactive rebase" },
4832 { "rebase-merge/", "Rebase merge" },
4833 { "MERGE_HEAD", "Merging" },
4834 { "BISECT_LOG", "Bisecting" },
4835 { "HEAD", "On branch" },
4836 };
4837 char buf[SIZEOF_STR];
4838 struct stat stat;
4839 int i;
4841 if (is_initial_commit()) {
4842 string_copy(status_onbranch, "Initial commit");
4843 return;
4844 }
4846 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4847 char *head = opt_head;
4849 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4850 lstat(buf, &stat) < 0)
4851 continue;
4853 if (!*opt_head) {
4854 struct io io = {};
4856 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4857 io_open(&io, buf) &&
4858 io_read_buf(&io, buf, sizeof(buf))) {
4859 head = chomp_string(buf);
4860 if (!prefixcmp(head, "refs/heads/"))
4861 head += STRING_SIZE("refs/heads/");
4862 }
4863 }
4865 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4866 string_copy(status_onbranch, opt_head);
4867 return;
4868 }
4870 string_copy(status_onbranch, "Not currently on any branch");
4871 }
4873 /* First parse staged info using git-diff-index(1), then parse unstaged
4874 * info using git-diff-files(1), and finally untracked files using
4875 * git-ls-files(1). */
4876 static bool
4877 status_open(struct view *view)
4878 {
4879 reset_view(view);
4881 add_line_data(view, NULL, LINE_STAT_HEAD);
4882 status_update_onbranch();
4884 run_io_bg(update_index_argv);
4886 if (is_initial_commit()) {
4887 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4888 return FALSE;
4889 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4890 return FALSE;
4891 }
4893 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4894 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4895 return FALSE;
4897 /* Restore the exact position or use the specialized restore
4898 * mode? */
4899 if (!view->p_restore)
4900 status_restore(view);
4901 return TRUE;
4902 }
4904 static bool
4905 status_draw(struct view *view, struct line *line, unsigned int lineno)
4906 {
4907 struct status *status = line->data;
4908 enum line_type type;
4909 const char *text;
4911 if (!status) {
4912 switch (line->type) {
4913 case LINE_STAT_STAGED:
4914 type = LINE_STAT_SECTION;
4915 text = "Changes to be committed:";
4916 break;
4918 case LINE_STAT_UNSTAGED:
4919 type = LINE_STAT_SECTION;
4920 text = "Changed but not updated:";
4921 break;
4923 case LINE_STAT_UNTRACKED:
4924 type = LINE_STAT_SECTION;
4925 text = "Untracked files:";
4926 break;
4928 case LINE_STAT_NONE:
4929 type = LINE_DEFAULT;
4930 text = " (no files)";
4931 break;
4933 case LINE_STAT_HEAD:
4934 type = LINE_STAT_HEAD;
4935 text = status_onbranch;
4936 break;
4938 default:
4939 return FALSE;
4940 }
4941 } else {
4942 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4944 buf[0] = status->status;
4945 if (draw_text(view, line->type, buf, TRUE))
4946 return TRUE;
4947 type = LINE_DEFAULT;
4948 text = status->new.name;
4949 }
4951 draw_text(view, type, text, TRUE);
4952 return TRUE;
4953 }
4955 static enum request
4956 status_load_error(struct view *view, struct view *stage, const char *path)
4957 {
4958 if (displayed_views() == 2 || display[current_view] != view)
4959 maximize_view(view);
4960 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4961 return REQ_NONE;
4962 }
4964 static enum request
4965 status_enter(struct view *view, struct line *line)
4966 {
4967 struct status *status = line->data;
4968 const char *oldpath = status ? status->old.name : NULL;
4969 /* Diffs for unmerged entries are empty when passing the new
4970 * path, so leave it empty. */
4971 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4972 const char *info;
4973 enum open_flags split;
4974 struct view *stage = VIEW(REQ_VIEW_STAGE);
4976 if (line->type == LINE_STAT_NONE ||
4977 (!status && line[1].type == LINE_STAT_NONE)) {
4978 report("No file to diff");
4979 return REQ_NONE;
4980 }
4982 switch (line->type) {
4983 case LINE_STAT_STAGED:
4984 if (is_initial_commit()) {
4985 const char *no_head_diff_argv[] = {
4986 "git", "diff", "--no-color", "--patch-with-stat",
4987 "--", "/dev/null", newpath, NULL
4988 };
4990 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4991 return status_load_error(view, stage, newpath);
4992 } else {
4993 const char *index_show_argv[] = {
4994 "git", "diff-index", "--root", "--patch-with-stat",
4995 "-C", "-M", "--cached", "HEAD", "--",
4996 oldpath, newpath, NULL
4997 };
4999 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5000 return status_load_error(view, stage, newpath);
5001 }
5003 if (status)
5004 info = "Staged changes to %s";
5005 else
5006 info = "Staged changes";
5007 break;
5009 case LINE_STAT_UNSTAGED:
5010 {
5011 const char *files_show_argv[] = {
5012 "git", "diff-files", "--root", "--patch-with-stat",
5013 "-C", "-M", "--", oldpath, newpath, NULL
5014 };
5016 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5017 return status_load_error(view, stage, newpath);
5018 if (status)
5019 info = "Unstaged changes to %s";
5020 else
5021 info = "Unstaged changes";
5022 break;
5023 }
5024 case LINE_STAT_UNTRACKED:
5025 if (!newpath) {
5026 report("No file to show");
5027 return REQ_NONE;
5028 }
5030 if (!suffixcmp(status->new.name, -1, "/")) {
5031 report("Cannot display a directory");
5032 return REQ_NONE;
5033 }
5035 if (!prepare_update_file(stage, newpath))
5036 return status_load_error(view, stage, newpath);
5037 info = "Untracked file %s";
5038 break;
5040 case LINE_STAT_HEAD:
5041 return REQ_NONE;
5043 default:
5044 die("line type %d not handled in switch", line->type);
5045 }
5047 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5048 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5049 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5050 if (status) {
5051 stage_status = *status;
5052 } else {
5053 memset(&stage_status, 0, sizeof(stage_status));
5054 }
5056 stage_line_type = line->type;
5057 stage_chunks = 0;
5058 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5059 }
5061 return REQ_NONE;
5062 }
5064 static bool
5065 status_exists(struct status *status, enum line_type type)
5066 {
5067 struct view *view = VIEW(REQ_VIEW_STATUS);
5068 unsigned long lineno;
5070 for (lineno = 0; lineno < view->lines; lineno++) {
5071 struct line *line = &view->line[lineno];
5072 struct status *pos = line->data;
5074 if (line->type != type)
5075 continue;
5076 if (!pos && (!status || !status->status) && line[1].data) {
5077 select_view_line(view, lineno);
5078 return TRUE;
5079 }
5080 if (pos && !strcmp(status->new.name, pos->new.name)) {
5081 select_view_line(view, lineno);
5082 return TRUE;
5083 }
5084 }
5086 return FALSE;
5087 }
5090 static bool
5091 status_update_prepare(struct io *io, enum line_type type)
5092 {
5093 const char *staged_argv[] = {
5094 "git", "update-index", "-z", "--index-info", NULL
5095 };
5096 const char *others_argv[] = {
5097 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5098 };
5100 switch (type) {
5101 case LINE_STAT_STAGED:
5102 return run_io(io, staged_argv, opt_cdup, IO_WR);
5104 case LINE_STAT_UNSTAGED:
5105 return run_io(io, others_argv, opt_cdup, IO_WR);
5107 case LINE_STAT_UNTRACKED:
5108 return run_io(io, others_argv, NULL, IO_WR);
5110 default:
5111 die("line type %d not handled in switch", type);
5112 return FALSE;
5113 }
5114 }
5116 static bool
5117 status_update_write(struct io *io, struct status *status, enum line_type type)
5118 {
5119 char buf[SIZEOF_STR];
5120 size_t bufsize = 0;
5122 switch (type) {
5123 case LINE_STAT_STAGED:
5124 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5125 status->old.mode,
5126 status->old.rev,
5127 status->old.name, 0))
5128 return FALSE;
5129 break;
5131 case LINE_STAT_UNSTAGED:
5132 case LINE_STAT_UNTRACKED:
5133 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5134 return FALSE;
5135 break;
5137 default:
5138 die("line type %d not handled in switch", type);
5139 }
5141 return io_write(io, buf, bufsize);
5142 }
5144 static bool
5145 status_update_file(struct status *status, enum line_type type)
5146 {
5147 struct io io = {};
5148 bool result;
5150 if (!status_update_prepare(&io, type))
5151 return FALSE;
5153 result = status_update_write(&io, status, type);
5154 return done_io(&io) && result;
5155 }
5157 static bool
5158 status_update_files(struct view *view, struct line *line)
5159 {
5160 char buf[sizeof(view->ref)];
5161 struct io io = {};
5162 bool result = TRUE;
5163 struct line *pos = view->line + view->lines;
5164 int files = 0;
5165 int file, done;
5166 int cursor_y, cursor_x;
5168 if (!status_update_prepare(&io, line->type))
5169 return FALSE;
5171 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5172 files++;
5174 string_copy(buf, view->ref);
5175 getsyx(cursor_y, cursor_x);
5176 for (file = 0, done = 5; result && file < files; line++, file++) {
5177 int almost_done = file * 100 / files;
5179 if (almost_done > done) {
5180 done = almost_done;
5181 string_format(view->ref, "updating file %u of %u (%d%% done)",
5182 file, files, done);
5183 update_view_title(view);
5184 setsyx(cursor_y, cursor_x);
5185 doupdate();
5186 }
5187 result = status_update_write(&io, line->data, line->type);
5188 }
5189 string_copy(view->ref, buf);
5191 return done_io(&io) && result;
5192 }
5194 static bool
5195 status_update(struct view *view)
5196 {
5197 struct line *line = &view->line[view->lineno];
5199 assert(view->lines);
5201 if (!line->data) {
5202 /* This should work even for the "On branch" line. */
5203 if (line < view->line + view->lines && !line[1].data) {
5204 report("Nothing to update");
5205 return FALSE;
5206 }
5208 if (!status_update_files(view, line + 1)) {
5209 report("Failed to update file status");
5210 return FALSE;
5211 }
5213 } else if (!status_update_file(line->data, line->type)) {
5214 report("Failed to update file status");
5215 return FALSE;
5216 }
5218 return TRUE;
5219 }
5221 static bool
5222 status_revert(struct status *status, enum line_type type, bool has_none)
5223 {
5224 if (!status || type != LINE_STAT_UNSTAGED) {
5225 if (type == LINE_STAT_STAGED) {
5226 report("Cannot revert changes to staged files");
5227 } else if (type == LINE_STAT_UNTRACKED) {
5228 report("Cannot revert changes to untracked files");
5229 } else if (has_none) {
5230 report("Nothing to revert");
5231 } else {
5232 report("Cannot revert changes to multiple files");
5233 }
5234 return FALSE;
5236 } else {
5237 char mode[10] = "100644";
5238 const char *reset_argv[] = {
5239 "git", "update-index", "--cacheinfo", mode,
5240 status->old.rev, status->old.name, NULL
5241 };
5242 const char *checkout_argv[] = {
5243 "git", "checkout", "--", status->old.name, NULL
5244 };
5246 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5247 return FALSE;
5248 string_format(mode, "%o", status->old.mode);
5249 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5250 run_io_fg(checkout_argv, opt_cdup);
5251 }
5252 }
5254 static enum request
5255 status_request(struct view *view, enum request request, struct line *line)
5256 {
5257 struct status *status = line->data;
5259 switch (request) {
5260 case REQ_STATUS_UPDATE:
5261 if (!status_update(view))
5262 return REQ_NONE;
5263 break;
5265 case REQ_STATUS_REVERT:
5266 if (!status_revert(status, line->type, status_has_none(view, line)))
5267 return REQ_NONE;
5268 break;
5270 case REQ_STATUS_MERGE:
5271 if (!status || status->status != 'U') {
5272 report("Merging only possible for files with unmerged status ('U').");
5273 return REQ_NONE;
5274 }
5275 open_mergetool(status->new.name);
5276 break;
5278 case REQ_EDIT:
5279 if (!status)
5280 return request;
5281 if (status->status == 'D') {
5282 report("File has been deleted.");
5283 return REQ_NONE;
5284 }
5286 open_editor(status->status != '?', status->new.name);
5287 break;
5289 case REQ_VIEW_BLAME:
5290 if (status) {
5291 string_copy(opt_file, status->new.name);
5292 opt_ref[0] = 0;
5293 }
5294 return request;
5296 case REQ_ENTER:
5297 /* After returning the status view has been split to
5298 * show the stage view. No further reloading is
5299 * necessary. */
5300 return status_enter(view, line);
5302 case REQ_REFRESH:
5303 /* Simply reload the view. */
5304 break;
5306 default:
5307 return request;
5308 }
5310 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5312 return REQ_NONE;
5313 }
5315 static void
5316 status_select(struct view *view, struct line *line)
5317 {
5318 struct status *status = line->data;
5319 char file[SIZEOF_STR] = "all files";
5320 const char *text;
5321 const char *key;
5323 if (status && !string_format(file, "'%s'", status->new.name))
5324 return;
5326 if (!status && line[1].type == LINE_STAT_NONE)
5327 line++;
5329 switch (line->type) {
5330 case LINE_STAT_STAGED:
5331 text = "Press %s to unstage %s for commit";
5332 break;
5334 case LINE_STAT_UNSTAGED:
5335 text = "Press %s to stage %s for commit";
5336 break;
5338 case LINE_STAT_UNTRACKED:
5339 text = "Press %s to stage %s for addition";
5340 break;
5342 case LINE_STAT_HEAD:
5343 case LINE_STAT_NONE:
5344 text = "Nothing to update";
5345 break;
5347 default:
5348 die("line type %d not handled in switch", line->type);
5349 }
5351 if (status && status->status == 'U') {
5352 text = "Press %s to resolve conflict in %s";
5353 key = get_key(REQ_STATUS_MERGE);
5355 } else {
5356 key = get_key(REQ_STATUS_UPDATE);
5357 }
5359 string_format(view->ref, text, key, file);
5360 }
5362 static bool
5363 status_grep(struct view *view, struct line *line)
5364 {
5365 struct status *status = line->data;
5367 if (status) {
5368 const char buf[2] = { status->status, 0 };
5369 const char *text[] = { status->new.name, buf, NULL };
5371 return grep_text(view, text);
5372 }
5374 return FALSE;
5375 }
5377 static struct view_ops status_ops = {
5378 "file",
5379 NULL,
5380 status_open,
5381 NULL,
5382 status_draw,
5383 status_request,
5384 status_grep,
5385 status_select,
5386 };
5389 static bool
5390 stage_diff_write(struct io *io, struct line *line, struct line *end)
5391 {
5392 while (line < end) {
5393 if (!io_write(io, line->data, strlen(line->data)) ||
5394 !io_write(io, "\n", 1))
5395 return FALSE;
5396 line++;
5397 if (line->type == LINE_DIFF_CHUNK ||
5398 line->type == LINE_DIFF_HEADER)
5399 break;
5400 }
5402 return TRUE;
5403 }
5405 static struct line *
5406 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5407 {
5408 for (; view->line < line; line--)
5409 if (line->type == type)
5410 return line;
5412 return NULL;
5413 }
5415 static bool
5416 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5417 {
5418 const char *apply_argv[SIZEOF_ARG] = {
5419 "git", "apply", "--whitespace=nowarn", NULL
5420 };
5421 struct line *diff_hdr;
5422 struct io io = {};
5423 int argc = 3;
5425 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5426 if (!diff_hdr)
5427 return FALSE;
5429 if (!revert)
5430 apply_argv[argc++] = "--cached";
5431 if (revert || stage_line_type == LINE_STAT_STAGED)
5432 apply_argv[argc++] = "-R";
5433 apply_argv[argc++] = "-";
5434 apply_argv[argc++] = NULL;
5435 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5436 return FALSE;
5438 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5439 !stage_diff_write(&io, chunk, view->line + view->lines))
5440 chunk = NULL;
5442 done_io(&io);
5443 run_io_bg(update_index_argv);
5445 return chunk ? TRUE : FALSE;
5446 }
5448 static bool
5449 stage_update(struct view *view, struct line *line)
5450 {
5451 struct line *chunk = NULL;
5453 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5454 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5456 if (chunk) {
5457 if (!stage_apply_chunk(view, chunk, FALSE)) {
5458 report("Failed to apply chunk");
5459 return FALSE;
5460 }
5462 } else if (!stage_status.status) {
5463 view = VIEW(REQ_VIEW_STATUS);
5465 for (line = view->line; line < view->line + view->lines; line++)
5466 if (line->type == stage_line_type)
5467 break;
5469 if (!status_update_files(view, line + 1)) {
5470 report("Failed to update files");
5471 return FALSE;
5472 }
5474 } else if (!status_update_file(&stage_status, stage_line_type)) {
5475 report("Failed to update file");
5476 return FALSE;
5477 }
5479 return TRUE;
5480 }
5482 static bool
5483 stage_revert(struct view *view, struct line *line)
5484 {
5485 struct line *chunk = NULL;
5487 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5488 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5490 if (chunk) {
5491 if (!prompt_yesno("Are you sure you want to revert changes?"))
5492 return FALSE;
5494 if (!stage_apply_chunk(view, chunk, TRUE)) {
5495 report("Failed to revert chunk");
5496 return FALSE;
5497 }
5498 return TRUE;
5500 } else {
5501 return status_revert(stage_status.status ? &stage_status : NULL,
5502 stage_line_type, FALSE);
5503 }
5504 }
5507 static void
5508 stage_next(struct view *view, struct line *line)
5509 {
5510 int i;
5512 if (!stage_chunks) {
5513 static size_t alloc = 0;
5515 for (line = view->line; line < view->line + view->lines; line++) {
5516 if (line->type != LINE_DIFF_CHUNK)
5517 continue;
5519 if (!realloc_ints(&stage_chunk, &alloc, stage_chunks + 1)) {
5520 report("Allocation failure");
5521 return;
5522 }
5524 stage_chunk[stage_chunks++] = line - view->line;
5525 }
5526 }
5528 for (i = 0; i < stage_chunks; i++) {
5529 if (stage_chunk[i] > view->lineno) {
5530 do_scroll_view(view, stage_chunk[i] - view->lineno);
5531 report("Chunk %d of %d", i + 1, stage_chunks);
5532 return;
5533 }
5534 }
5536 report("No next chunk found");
5537 }
5539 static enum request
5540 stage_request(struct view *view, enum request request, struct line *line)
5541 {
5542 switch (request) {
5543 case REQ_STATUS_UPDATE:
5544 if (!stage_update(view, line))
5545 return REQ_NONE;
5546 break;
5548 case REQ_STATUS_REVERT:
5549 if (!stage_revert(view, line))
5550 return REQ_NONE;
5551 break;
5553 case REQ_STAGE_NEXT:
5554 if (stage_line_type == LINE_STAT_UNTRACKED) {
5555 report("File is untracked; press %s to add",
5556 get_key(REQ_STATUS_UPDATE));
5557 return REQ_NONE;
5558 }
5559 stage_next(view, line);
5560 return REQ_NONE;
5562 case REQ_EDIT:
5563 if (!stage_status.new.name[0])
5564 return request;
5565 if (stage_status.status == 'D') {
5566 report("File has been deleted.");
5567 return REQ_NONE;
5568 }
5570 open_editor(stage_status.status != '?', stage_status.new.name);
5571 break;
5573 case REQ_REFRESH:
5574 /* Reload everything ... */
5575 break;
5577 case REQ_VIEW_BLAME:
5578 if (stage_status.new.name[0]) {
5579 string_copy(opt_file, stage_status.new.name);
5580 opt_ref[0] = 0;
5581 }
5582 return request;
5584 case REQ_ENTER:
5585 return pager_request(view, request, line);
5587 default:
5588 return request;
5589 }
5591 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5592 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5594 /* Check whether the staged entry still exists, and close the
5595 * stage view if it doesn't. */
5596 if (!status_exists(&stage_status, stage_line_type)) {
5597 status_restore(VIEW(REQ_VIEW_STATUS));
5598 return REQ_VIEW_CLOSE;
5599 }
5601 if (stage_line_type == LINE_STAT_UNTRACKED) {
5602 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5603 report("Cannot display a directory");
5604 return REQ_NONE;
5605 }
5607 if (!prepare_update_file(view, stage_status.new.name)) {
5608 report("Failed to open file: %s", strerror(errno));
5609 return REQ_NONE;
5610 }
5611 }
5612 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5614 return REQ_NONE;
5615 }
5617 static struct view_ops stage_ops = {
5618 "line",
5619 NULL,
5620 NULL,
5621 pager_read,
5622 pager_draw,
5623 stage_request,
5624 pager_grep,
5625 pager_select,
5626 };
5629 /*
5630 * Revision graph
5631 */
5633 struct commit {
5634 char id[SIZEOF_REV]; /* SHA1 ID. */
5635 char title[128]; /* First line of the commit message. */
5636 const char *author; /* Author of the commit. */
5637 time_t time; /* Date from the author ident. */
5638 struct ref **refs; /* Repository references. */
5639 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5640 size_t graph_size; /* The width of the graph array. */
5641 bool has_parents; /* Rewritten --parents seen. */
5642 };
5644 /* Size of rev graph with no "padding" columns */
5645 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5647 struct rev_graph {
5648 struct rev_graph *prev, *next, *parents;
5649 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5650 size_t size;
5651 struct commit *commit;
5652 size_t pos;
5653 unsigned int boundary:1;
5654 };
5656 /* Parents of the commit being visualized. */
5657 static struct rev_graph graph_parents[4];
5659 /* The current stack of revisions on the graph. */
5660 static struct rev_graph graph_stacks[4] = {
5661 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5662 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5663 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5664 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5665 };
5667 static inline bool
5668 graph_parent_is_merge(struct rev_graph *graph)
5669 {
5670 return graph->parents->size > 1;
5671 }
5673 static inline void
5674 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5675 {
5676 struct commit *commit = graph->commit;
5678 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5679 commit->graph[commit->graph_size++] = symbol;
5680 }
5682 static void
5683 clear_rev_graph(struct rev_graph *graph)
5684 {
5685 graph->boundary = 0;
5686 graph->size = graph->pos = 0;
5687 graph->commit = NULL;
5688 memset(graph->parents, 0, sizeof(*graph->parents));
5689 }
5691 static void
5692 done_rev_graph(struct rev_graph *graph)
5693 {
5694 if (graph_parent_is_merge(graph) &&
5695 graph->pos < graph->size - 1 &&
5696 graph->next->size == graph->size + graph->parents->size - 1) {
5697 size_t i = graph->pos + graph->parents->size - 1;
5699 graph->commit->graph_size = i * 2;
5700 while (i < graph->next->size - 1) {
5701 append_to_rev_graph(graph, ' ');
5702 append_to_rev_graph(graph, '\\');
5703 i++;
5704 }
5705 }
5707 clear_rev_graph(graph);
5708 }
5710 static void
5711 push_rev_graph(struct rev_graph *graph, const char *parent)
5712 {
5713 int i;
5715 /* "Collapse" duplicate parents lines.
5716 *
5717 * FIXME: This needs to also update update the drawn graph but
5718 * for now it just serves as a method for pruning graph lines. */
5719 for (i = 0; i < graph->size; i++)
5720 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5721 return;
5723 if (graph->size < SIZEOF_REVITEMS) {
5724 string_copy_rev(graph->rev[graph->size++], parent);
5725 }
5726 }
5728 static chtype
5729 get_rev_graph_symbol(struct rev_graph *graph)
5730 {
5731 chtype symbol;
5733 if (graph->boundary)
5734 symbol = REVGRAPH_BOUND;
5735 else if (graph->parents->size == 0)
5736 symbol = REVGRAPH_INIT;
5737 else if (graph_parent_is_merge(graph))
5738 symbol = REVGRAPH_MERGE;
5739 else if (graph->pos >= graph->size)
5740 symbol = REVGRAPH_BRANCH;
5741 else
5742 symbol = REVGRAPH_COMMIT;
5744 return symbol;
5745 }
5747 static void
5748 draw_rev_graph(struct rev_graph *graph)
5749 {
5750 struct rev_filler {
5751 chtype separator, line;
5752 };
5753 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5754 static struct rev_filler fillers[] = {
5755 { ' ', '|' },
5756 { '`', '.' },
5757 { '\'', ' ' },
5758 { '/', ' ' },
5759 };
5760 chtype symbol = get_rev_graph_symbol(graph);
5761 struct rev_filler *filler;
5762 size_t i;
5764 if (opt_line_graphics)
5765 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5767 filler = &fillers[DEFAULT];
5769 for (i = 0; i < graph->pos; i++) {
5770 append_to_rev_graph(graph, filler->line);
5771 if (graph_parent_is_merge(graph->prev) &&
5772 graph->prev->pos == i)
5773 filler = &fillers[RSHARP];
5775 append_to_rev_graph(graph, filler->separator);
5776 }
5778 /* Place the symbol for this revision. */
5779 append_to_rev_graph(graph, symbol);
5781 if (graph->prev->size > graph->size)
5782 filler = &fillers[RDIAG];
5783 else
5784 filler = &fillers[DEFAULT];
5786 i++;
5788 for (; i < graph->size; i++) {
5789 append_to_rev_graph(graph, filler->separator);
5790 append_to_rev_graph(graph, filler->line);
5791 if (graph_parent_is_merge(graph->prev) &&
5792 i < graph->prev->pos + graph->parents->size)
5793 filler = &fillers[RSHARP];
5794 if (graph->prev->size > graph->size)
5795 filler = &fillers[LDIAG];
5796 }
5798 if (graph->prev->size > graph->size) {
5799 append_to_rev_graph(graph, filler->separator);
5800 if (filler->line != ' ')
5801 append_to_rev_graph(graph, filler->line);
5802 }
5803 }
5805 /* Prepare the next rev graph */
5806 static void
5807 prepare_rev_graph(struct rev_graph *graph)
5808 {
5809 size_t i;
5811 /* First, traverse all lines of revisions up to the active one. */
5812 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5813 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5814 break;
5816 push_rev_graph(graph->next, graph->rev[graph->pos]);
5817 }
5819 /* Interleave the new revision parent(s). */
5820 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5821 push_rev_graph(graph->next, graph->parents->rev[i]);
5823 /* Lastly, put any remaining revisions. */
5824 for (i = graph->pos + 1; i < graph->size; i++)
5825 push_rev_graph(graph->next, graph->rev[i]);
5826 }
5828 static void
5829 update_rev_graph(struct view *view, struct rev_graph *graph)
5830 {
5831 /* If this is the finalizing update ... */
5832 if (graph->commit)
5833 prepare_rev_graph(graph);
5835 /* Graph visualization needs a one rev look-ahead,
5836 * so the first update doesn't visualize anything. */
5837 if (!graph->prev->commit)
5838 return;
5840 if (view->lines > 2)
5841 view->line[view->lines - 3].dirty = 1;
5842 if (view->lines > 1)
5843 view->line[view->lines - 2].dirty = 1;
5844 draw_rev_graph(graph->prev);
5845 done_rev_graph(graph->prev->prev);
5846 }
5849 /*
5850 * Main view backend
5851 */
5853 static const char *main_argv[SIZEOF_ARG] = {
5854 "git", "log", "--no-color", "--pretty=raw", "--parents",
5855 "--topo-order", "%(head)", NULL
5856 };
5858 static bool
5859 main_draw(struct view *view, struct line *line, unsigned int lineno)
5860 {
5861 struct commit *commit = line->data;
5863 if (!commit->author)
5864 return FALSE;
5866 if (opt_date && draw_date(view, &commit->time))
5867 return TRUE;
5869 if (opt_author && draw_author(view, commit->author))
5870 return TRUE;
5872 if (opt_rev_graph && commit->graph_size &&
5873 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5874 return TRUE;
5876 if (opt_show_refs && commit->refs) {
5877 size_t i = 0;
5879 do {
5880 enum line_type type;
5882 if (commit->refs[i]->head)
5883 type = LINE_MAIN_HEAD;
5884 else if (commit->refs[i]->ltag)
5885 type = LINE_MAIN_LOCAL_TAG;
5886 else if (commit->refs[i]->tag)
5887 type = LINE_MAIN_TAG;
5888 else if (commit->refs[i]->tracked)
5889 type = LINE_MAIN_TRACKED;
5890 else if (commit->refs[i]->remote)
5891 type = LINE_MAIN_REMOTE;
5892 else
5893 type = LINE_MAIN_REF;
5895 if (draw_text(view, type, "[", TRUE) ||
5896 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5897 draw_text(view, type, "]", TRUE))
5898 return TRUE;
5900 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5901 return TRUE;
5902 } while (commit->refs[i++]->next);
5903 }
5905 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5906 return TRUE;
5907 }
5909 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5910 static bool
5911 main_read(struct view *view, char *line)
5912 {
5913 static struct rev_graph *graph = graph_stacks;
5914 enum line_type type;
5915 struct commit *commit;
5917 if (!line) {
5918 int i;
5920 if (!view->lines && !view->parent)
5921 die("No revisions match the given arguments.");
5922 if (view->lines > 0) {
5923 commit = view->line[view->lines - 1].data;
5924 view->line[view->lines - 1].dirty = 1;
5925 if (!commit->author) {
5926 view->lines--;
5927 free(commit);
5928 graph->commit = NULL;
5929 }
5930 }
5931 update_rev_graph(view, graph);
5933 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5934 clear_rev_graph(&graph_stacks[i]);
5935 return TRUE;
5936 }
5938 type = get_line_type(line);
5939 if (type == LINE_COMMIT) {
5940 commit = calloc(1, sizeof(struct commit));
5941 if (!commit)
5942 return FALSE;
5944 line += STRING_SIZE("commit ");
5945 if (*line == '-') {
5946 graph->boundary = 1;
5947 line++;
5948 }
5950 string_copy_rev(commit->id, line);
5951 commit->refs = get_refs(commit->id);
5952 graph->commit = commit;
5953 add_line_data(view, commit, LINE_MAIN_COMMIT);
5955 while ((line = strchr(line, ' '))) {
5956 line++;
5957 push_rev_graph(graph->parents, line);
5958 commit->has_parents = TRUE;
5959 }
5960 return TRUE;
5961 }
5963 if (!view->lines)
5964 return TRUE;
5965 commit = view->line[view->lines - 1].data;
5967 switch (type) {
5968 case LINE_PARENT:
5969 if (commit->has_parents)
5970 break;
5971 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5972 break;
5974 case LINE_AUTHOR:
5975 parse_author_line(line + STRING_SIZE("author "),
5976 &commit->author, &commit->time);
5977 update_rev_graph(view, graph);
5978 graph = graph->next;
5979 break;
5981 default:
5982 /* Fill in the commit title if it has not already been set. */
5983 if (commit->title[0])
5984 break;
5986 /* Require titles to start with a non-space character at the
5987 * offset used by git log. */
5988 if (strncmp(line, " ", 4))
5989 break;
5990 line += 4;
5991 /* Well, if the title starts with a whitespace character,
5992 * try to be forgiving. Otherwise we end up with no title. */
5993 while (isspace(*line))
5994 line++;
5995 if (*line == '\0')
5996 break;
5997 /* FIXME: More graceful handling of titles; append "..." to
5998 * shortened titles, etc. */
6000 string_expand(commit->title, sizeof(commit->title), line, 1);
6001 view->line[view->lines - 1].dirty = 1;
6002 }
6004 return TRUE;
6005 }
6007 static enum request
6008 main_request(struct view *view, enum request request, struct line *line)
6009 {
6010 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6012 switch (request) {
6013 case REQ_ENTER:
6014 open_view(view, REQ_VIEW_DIFF, flags);
6015 break;
6016 case REQ_REFRESH:
6017 load_refs();
6018 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6019 break;
6020 default:
6021 return request;
6022 }
6024 return REQ_NONE;
6025 }
6027 static bool
6028 grep_refs(struct ref **refs, regex_t *regex)
6029 {
6030 regmatch_t pmatch;
6031 size_t i = 0;
6033 if (!opt_show_refs || !refs)
6034 return FALSE;
6035 do {
6036 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6037 return TRUE;
6038 } while (refs[i++]->next);
6040 return FALSE;
6041 }
6043 static bool
6044 main_grep(struct view *view, struct line *line)
6045 {
6046 struct commit *commit = line->data;
6047 const char *text[] = {
6048 commit->title,
6049 opt_author ? commit->author : "",
6050 opt_date ? mkdate(&commit->time) : "",
6051 NULL
6052 };
6054 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6055 }
6057 static void
6058 main_select(struct view *view, struct line *line)
6059 {
6060 struct commit *commit = line->data;
6062 string_copy_rev(view->ref, commit->id);
6063 string_copy_rev(ref_commit, view->ref);
6064 }
6066 static struct view_ops main_ops = {
6067 "commit",
6068 main_argv,
6069 NULL,
6070 main_read,
6071 main_draw,
6072 main_request,
6073 main_grep,
6074 main_select,
6075 };
6078 /*
6079 * Unicode / UTF-8 handling
6080 *
6081 * NOTE: Much of the following code for dealing with Unicode is derived from
6082 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6083 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6084 */
6086 static inline int
6087 unicode_width(unsigned long c)
6088 {
6089 if (c >= 0x1100 &&
6090 (c <= 0x115f /* Hangul Jamo */
6091 || c == 0x2329
6092 || c == 0x232a
6093 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6094 /* CJK ... Yi */
6095 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6096 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6097 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6098 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6099 || (c >= 0xffe0 && c <= 0xffe6)
6100 || (c >= 0x20000 && c <= 0x2fffd)
6101 || (c >= 0x30000 && c <= 0x3fffd)))
6102 return 2;
6104 if (c == '\t')
6105 return opt_tab_size;
6107 return 1;
6108 }
6110 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6111 * Illegal bytes are set one. */
6112 static const unsigned char utf8_bytes[256] = {
6113 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,
6114 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,
6115 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,
6116 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,
6117 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,
6118 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,
6119 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,
6120 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,
6121 };
6123 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6124 static inline unsigned long
6125 utf8_to_unicode(const char *string, size_t length)
6126 {
6127 unsigned long unicode;
6129 switch (length) {
6130 case 1:
6131 unicode = string[0];
6132 break;
6133 case 2:
6134 unicode = (string[0] & 0x1f) << 6;
6135 unicode += (string[1] & 0x3f);
6136 break;
6137 case 3:
6138 unicode = (string[0] & 0x0f) << 12;
6139 unicode += ((string[1] & 0x3f) << 6);
6140 unicode += (string[2] & 0x3f);
6141 break;
6142 case 4:
6143 unicode = (string[0] & 0x0f) << 18;
6144 unicode += ((string[1] & 0x3f) << 12);
6145 unicode += ((string[2] & 0x3f) << 6);
6146 unicode += (string[3] & 0x3f);
6147 break;
6148 case 5:
6149 unicode = (string[0] & 0x0f) << 24;
6150 unicode += ((string[1] & 0x3f) << 18);
6151 unicode += ((string[2] & 0x3f) << 12);
6152 unicode += ((string[3] & 0x3f) << 6);
6153 unicode += (string[4] & 0x3f);
6154 break;
6155 case 6:
6156 unicode = (string[0] & 0x01) << 30;
6157 unicode += ((string[1] & 0x3f) << 24);
6158 unicode += ((string[2] & 0x3f) << 18);
6159 unicode += ((string[3] & 0x3f) << 12);
6160 unicode += ((string[4] & 0x3f) << 6);
6161 unicode += (string[5] & 0x3f);
6162 break;
6163 default:
6164 die("Invalid Unicode length");
6165 }
6167 /* Invalid characters could return the special 0xfffd value but NUL
6168 * should be just as good. */
6169 return unicode > 0xffff ? 0 : unicode;
6170 }
6172 /* Calculates how much of string can be shown within the given maximum width
6173 * and sets trimmed parameter to non-zero value if all of string could not be
6174 * shown. If the reserve flag is TRUE, it will reserve at least one
6175 * trailing character, which can be useful when drawing a delimiter.
6176 *
6177 * Returns the number of bytes to output from string to satisfy max_width. */
6178 static size_t
6179 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6180 {
6181 const char *string = *start;
6182 const char *end = strchr(string, '\0');
6183 unsigned char last_bytes = 0;
6184 size_t last_ucwidth = 0;
6186 *width = 0;
6187 *trimmed = 0;
6189 while (string < end) {
6190 int c = *(unsigned char *) string;
6191 unsigned char bytes = utf8_bytes[c];
6192 size_t ucwidth;
6193 unsigned long unicode;
6195 if (string + bytes > end)
6196 break;
6198 /* Change representation to figure out whether
6199 * it is a single- or double-width character. */
6201 unicode = utf8_to_unicode(string, bytes);
6202 /* FIXME: Graceful handling of invalid Unicode character. */
6203 if (!unicode)
6204 break;
6206 ucwidth = unicode_width(unicode);
6207 if (skip > 0) {
6208 skip -= ucwidth <= skip ? ucwidth : skip;
6209 *start += bytes;
6210 }
6211 *width += ucwidth;
6212 if (*width > max_width) {
6213 *trimmed = 1;
6214 *width -= ucwidth;
6215 if (reserve && *width == max_width) {
6216 string -= last_bytes;
6217 *width -= last_ucwidth;
6218 }
6219 break;
6220 }
6222 string += bytes;
6223 last_bytes = ucwidth ? bytes : 0;
6224 last_ucwidth = ucwidth;
6225 }
6227 return string - *start;
6228 }
6231 /*
6232 * Status management
6233 */
6235 /* Whether or not the curses interface has been initialized. */
6236 static bool cursed = FALSE;
6238 /* Terminal hacks and workarounds. */
6239 static bool use_scroll_redrawwin;
6240 static bool use_scroll_status_wclear;
6242 /* The status window is used for polling keystrokes. */
6243 static WINDOW *status_win;
6245 /* Reading from the prompt? */
6246 static bool input_mode = FALSE;
6248 static bool status_empty = FALSE;
6250 /* Update status and title window. */
6251 static void
6252 report(const char *msg, ...)
6253 {
6254 struct view *view = display[current_view];
6256 if (input_mode)
6257 return;
6259 if (!view) {
6260 char buf[SIZEOF_STR];
6261 va_list args;
6263 va_start(args, msg);
6264 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6265 buf[sizeof(buf) - 1] = 0;
6266 buf[sizeof(buf) - 2] = '.';
6267 buf[sizeof(buf) - 3] = '.';
6268 buf[sizeof(buf) - 4] = '.';
6269 }
6270 va_end(args);
6271 die("%s", buf);
6272 }
6274 if (!status_empty || *msg) {
6275 va_list args;
6277 va_start(args, msg);
6279 wmove(status_win, 0, 0);
6280 if (view->has_scrolled && use_scroll_status_wclear)
6281 wclear(status_win);
6282 if (*msg) {
6283 vwprintw(status_win, msg, args);
6284 status_empty = FALSE;
6285 } else {
6286 status_empty = TRUE;
6287 }
6288 wclrtoeol(status_win);
6289 wnoutrefresh(status_win);
6291 va_end(args);
6292 }
6294 update_view_title(view);
6295 }
6297 /* Controls when nodelay should be in effect when polling user input. */
6298 static void
6299 set_nonblocking_input(bool loading)
6300 {
6301 static unsigned int loading_views;
6303 if ((loading == FALSE && loading_views-- == 1) ||
6304 (loading == TRUE && loading_views++ == 0))
6305 nodelay(status_win, loading);
6306 }
6308 static void
6309 init_display(void)
6310 {
6311 const char *term;
6312 int x, y;
6314 /* Initialize the curses library */
6315 if (isatty(STDIN_FILENO)) {
6316 cursed = !!initscr();
6317 opt_tty = stdin;
6318 } else {
6319 /* Leave stdin and stdout alone when acting as a pager. */
6320 opt_tty = fopen("/dev/tty", "r+");
6321 if (!opt_tty)
6322 die("Failed to open /dev/tty");
6323 cursed = !!newterm(NULL, opt_tty, opt_tty);
6324 }
6326 if (!cursed)
6327 die("Failed to initialize curses");
6329 nonl(); /* Disable conversion and detect newlines from input. */
6330 cbreak(); /* Take input chars one at a time, no wait for \n */
6331 noecho(); /* Don't echo input */
6332 leaveok(stdscr, FALSE);
6334 if (has_colors())
6335 init_colors();
6337 getmaxyx(stdscr, y, x);
6338 status_win = newwin(1, 0, y - 1, 0);
6339 if (!status_win)
6340 die("Failed to create status window");
6342 /* Enable keyboard mapping */
6343 keypad(status_win, TRUE);
6344 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6346 TABSIZE = opt_tab_size;
6347 if (opt_line_graphics) {
6348 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6349 }
6351 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6352 if (term && !strcmp(term, "gnome-terminal")) {
6353 /* In the gnome-terminal-emulator, the message from
6354 * scrolling up one line when impossible followed by
6355 * scrolling down one line causes corruption of the
6356 * status line. This is fixed by calling wclear. */
6357 use_scroll_status_wclear = TRUE;
6358 use_scroll_redrawwin = FALSE;
6360 } else if (term && !strcmp(term, "xrvt-xpm")) {
6361 /* No problems with full optimizations in xrvt-(unicode)
6362 * and aterm. */
6363 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6365 } else {
6366 /* When scrolling in (u)xterm the last line in the
6367 * scrolling direction will update slowly. */
6368 use_scroll_redrawwin = TRUE;
6369 use_scroll_status_wclear = FALSE;
6370 }
6371 }
6373 static int
6374 get_input(int prompt_position)
6375 {
6376 struct view *view;
6377 int i, key, cursor_y, cursor_x;
6379 if (prompt_position)
6380 input_mode = TRUE;
6382 while (TRUE) {
6383 foreach_view (view, i) {
6384 update_view(view);
6385 if (view_is_displayed(view) && view->has_scrolled &&
6386 use_scroll_redrawwin)
6387 redrawwin(view->win);
6388 view->has_scrolled = FALSE;
6389 }
6391 /* Update the cursor position. */
6392 if (prompt_position) {
6393 getbegyx(status_win, cursor_y, cursor_x);
6394 cursor_x = prompt_position;
6395 } else {
6396 view = display[current_view];
6397 getbegyx(view->win, cursor_y, cursor_x);
6398 cursor_x = view->width - 1;
6399 cursor_y += view->lineno - view->offset;
6400 }
6401 setsyx(cursor_y, cursor_x);
6403 /* Refresh, accept single keystroke of input */
6404 doupdate();
6405 key = wgetch(status_win);
6407 /* wgetch() with nodelay() enabled returns ERR when
6408 * there's no input. */
6409 if (key == ERR) {
6411 } else if (key == KEY_RESIZE) {
6412 int height, width;
6414 getmaxyx(stdscr, height, width);
6416 wresize(status_win, 1, width);
6417 mvwin(status_win, height - 1, 0);
6418 wnoutrefresh(status_win);
6419 resize_display();
6420 redraw_display(TRUE);
6422 } else {
6423 input_mode = FALSE;
6424 return key;
6425 }
6426 }
6427 }
6429 static char *
6430 prompt_input(const char *prompt, input_handler handler, void *data)
6431 {
6432 enum input_status status = INPUT_OK;
6433 static char buf[SIZEOF_STR];
6434 size_t pos = 0;
6436 buf[pos] = 0;
6438 while (status == INPUT_OK || status == INPUT_SKIP) {
6439 int key;
6441 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6442 wclrtoeol(status_win);
6444 key = get_input(pos + 1);
6445 switch (key) {
6446 case KEY_RETURN:
6447 case KEY_ENTER:
6448 case '\n':
6449 status = pos ? INPUT_STOP : INPUT_CANCEL;
6450 break;
6452 case KEY_BACKSPACE:
6453 if (pos > 0)
6454 buf[--pos] = 0;
6455 else
6456 status = INPUT_CANCEL;
6457 break;
6459 case KEY_ESC:
6460 status = INPUT_CANCEL;
6461 break;
6463 default:
6464 if (pos >= sizeof(buf)) {
6465 report("Input string too long");
6466 return NULL;
6467 }
6469 status = handler(data, buf, key);
6470 if (status == INPUT_OK)
6471 buf[pos++] = (char) key;
6472 }
6473 }
6475 /* Clear the status window */
6476 status_empty = FALSE;
6477 report("");
6479 if (status == INPUT_CANCEL)
6480 return NULL;
6482 buf[pos++] = 0;
6484 return buf;
6485 }
6487 static enum input_status
6488 prompt_yesno_handler(void *data, char *buf, int c)
6489 {
6490 if (c == 'y' || c == 'Y')
6491 return INPUT_STOP;
6492 if (c == 'n' || c == 'N')
6493 return INPUT_CANCEL;
6494 return INPUT_SKIP;
6495 }
6497 static bool
6498 prompt_yesno(const char *prompt)
6499 {
6500 char prompt2[SIZEOF_STR];
6502 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6503 return FALSE;
6505 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6506 }
6508 static enum input_status
6509 read_prompt_handler(void *data, char *buf, int c)
6510 {
6511 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6512 }
6514 static char *
6515 read_prompt(const char *prompt)
6516 {
6517 return prompt_input(prompt, read_prompt_handler, NULL);
6518 }
6520 /*
6521 * Repository properties
6522 */
6524 static struct ref *refs = NULL;
6525 static size_t refs_alloc = 0;
6526 static size_t refs_size = 0;
6528 /* Id <-> ref store */
6529 static struct ref ***id_refs = NULL;
6530 static size_t id_refs_alloc = 0;
6531 static size_t id_refs_size = 0;
6533 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6534 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6535 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6537 static int
6538 compare_refs(const void *ref1_, const void *ref2_)
6539 {
6540 const struct ref *ref1 = *(const struct ref **)ref1_;
6541 const struct ref *ref2 = *(const struct ref **)ref2_;
6543 if (ref1->tag != ref2->tag)
6544 return ref2->tag - ref1->tag;
6545 if (ref1->ltag != ref2->ltag)
6546 return ref2->ltag - ref2->ltag;
6547 if (ref1->head != ref2->head)
6548 return ref2->head - ref1->head;
6549 if (ref1->tracked != ref2->tracked)
6550 return ref2->tracked - ref1->tracked;
6551 if (ref1->remote != ref2->remote)
6552 return ref2->remote - ref1->remote;
6553 return strcmp(ref1->name, ref2->name);
6554 }
6556 static struct ref **
6557 get_refs(const char *id)
6558 {
6559 struct ref **ref_list = NULL;
6560 size_t ref_list_alloc = 0;
6561 size_t ref_list_size = 0;
6562 size_t i;
6564 for (i = 0; i < id_refs_size; i++)
6565 if (!strcmp(id, id_refs[i][0]->id))
6566 return id_refs[i];
6568 if (!realloc_refs_lists(&id_refs, &id_refs_alloc, id_refs_size + 1))
6569 return NULL;
6571 for (i = 0; i < refs_size; i++) {
6572 if (strcmp(id, refs[i].id))
6573 continue;
6575 if (!realloc_refs_list(&ref_list, &ref_list_alloc, ref_list_size + 1))
6576 return ref_list;
6578 ref_list[ref_list_size] = &refs[i];
6579 /* XXX: The properties of the commit chains ensures that we can
6580 * safely modify the shared ref. The repo references will
6581 * always be similar for the same id. */
6582 ref_list[ref_list_size]->next = 1;
6583 ref_list_size++;
6584 }
6586 if (ref_list) {
6587 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6588 ref_list[ref_list_size - 1]->next = 0;
6589 id_refs[id_refs_size++] = ref_list;
6590 }
6592 return ref_list;
6593 }
6595 static int
6596 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6597 {
6598 struct ref *ref;
6599 bool tag = FALSE;
6600 bool ltag = FALSE;
6601 bool remote = FALSE;
6602 bool tracked = FALSE;
6603 bool check_replace = FALSE;
6604 bool head = FALSE;
6606 if (!prefixcmp(name, "refs/tags/")) {
6607 if (!suffixcmp(name, namelen, "^{}")) {
6608 namelen -= 3;
6609 name[namelen] = 0;
6610 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6611 check_replace = TRUE;
6612 } else {
6613 ltag = TRUE;
6614 }
6616 tag = TRUE;
6617 namelen -= STRING_SIZE("refs/tags/");
6618 name += STRING_SIZE("refs/tags/");
6620 } else if (!prefixcmp(name, "refs/remotes/")) {
6621 remote = TRUE;
6622 namelen -= STRING_SIZE("refs/remotes/");
6623 name += STRING_SIZE("refs/remotes/");
6624 tracked = !strcmp(opt_remote, name);
6626 } else if (!prefixcmp(name, "refs/heads/")) {
6627 namelen -= STRING_SIZE("refs/heads/");
6628 name += STRING_SIZE("refs/heads/");
6629 head = !strncmp(opt_head, name, namelen);
6631 } else if (!strcmp(name, "HEAD")) {
6632 string_ncopy(opt_head_rev, id, idlen);
6633 return OK;
6634 }
6636 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6637 /* it's an annotated tag, replace the previous SHA1 with the
6638 * resolved commit id; relies on the fact git-ls-remote lists
6639 * the commit id of an annotated tag right before the commit id
6640 * it points to. */
6641 refs[refs_size - 1].ltag = ltag;
6642 string_copy_rev(refs[refs_size - 1].id, id);
6644 return OK;
6645 }
6647 if (!realloc_refs(&refs, &refs_alloc, refs_size + 1))
6648 return ERR;
6650 ref = &refs[refs_size++];
6651 ref->name = malloc(namelen + 1);
6652 if (!ref->name)
6653 return ERR;
6655 strncpy(ref->name, name, namelen);
6656 ref->name[namelen] = 0;
6657 ref->head = head;
6658 ref->tag = tag;
6659 ref->ltag = ltag;
6660 ref->remote = remote;
6661 ref->tracked = tracked;
6662 string_copy_rev(ref->id, id);
6664 return OK;
6665 }
6667 static int
6668 load_refs(void)
6669 {
6670 static const char *ls_remote_argv[SIZEOF_ARG] = {
6671 "git", "ls-remote", opt_git_dir, NULL
6672 };
6673 static bool init = FALSE;
6675 if (!init) {
6676 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6677 init = TRUE;
6678 }
6680 if (!*opt_git_dir)
6681 return OK;
6683 while (refs_size > 0)
6684 free(refs[--refs_size].name);
6685 while (id_refs_size > 0)
6686 free(id_refs[--id_refs_size]);
6688 return run_io_load(ls_remote_argv, "\t", read_ref);
6689 }
6691 static void
6692 set_remote_branch(const char *name, const char *value, size_t valuelen)
6693 {
6694 if (!strcmp(name, ".remote")) {
6695 string_ncopy(opt_remote, value, valuelen);
6697 } else if (*opt_remote && !strcmp(name, ".merge")) {
6698 size_t from = strlen(opt_remote);
6700 if (!prefixcmp(value, "refs/heads/"))
6701 value += STRING_SIZE("refs/heads/");
6703 if (!string_format_from(opt_remote, &from, "/%s", value))
6704 opt_remote[0] = 0;
6705 }
6706 }
6708 static void
6709 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6710 {
6711 const char *argv[SIZEOF_ARG] = { name, "=" };
6712 int argc = 1 + (cmd == option_set_command);
6713 int error = ERR;
6715 if (!argv_from_string(argv, &argc, value))
6716 config_msg = "Too many option arguments";
6717 else
6718 error = cmd(argc, argv);
6720 if (error == ERR)
6721 warn("Option 'tig.%s': %s", name, config_msg);
6722 }
6724 static bool
6725 set_environment_variable(const char *name, const char *value)
6726 {
6727 size_t len = strlen(name) + 1 + strlen(value) + 1;
6728 char *env = malloc(len);
6730 if (env &&
6731 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6732 putenv(env) == 0)
6733 return TRUE;
6734 free(env);
6735 return FALSE;
6736 }
6738 static void
6739 set_work_tree(const char *value)
6740 {
6741 char cwd[SIZEOF_STR];
6743 if (!getcwd(cwd, sizeof(cwd)))
6744 die("Failed to get cwd path: %s", strerror(errno));
6745 if (chdir(opt_git_dir) < 0)
6746 die("Failed to chdir(%s): %s", strerror(errno));
6747 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6748 die("Failed to get git path: %s", strerror(errno));
6749 if (chdir(cwd) < 0)
6750 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6751 if (chdir(value) < 0)
6752 die("Failed to chdir(%s): %s", value, strerror(errno));
6753 if (!getcwd(cwd, sizeof(cwd)))
6754 die("Failed to get cwd path: %s", strerror(errno));
6755 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6756 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6757 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6758 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6759 opt_is_inside_work_tree = TRUE;
6760 }
6762 static int
6763 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6764 {
6765 if (!strcmp(name, "i18n.commitencoding"))
6766 string_ncopy(opt_encoding, value, valuelen);
6768 else if (!strcmp(name, "core.editor"))
6769 string_ncopy(opt_editor, value, valuelen);
6771 else if (!strcmp(name, "core.worktree"))
6772 set_work_tree(value);
6774 else if (!prefixcmp(name, "tig.color."))
6775 set_repo_config_option(name + 10, value, option_color_command);
6777 else if (!prefixcmp(name, "tig.bind."))
6778 set_repo_config_option(name + 9, value, option_bind_command);
6780 else if (!prefixcmp(name, "tig."))
6781 set_repo_config_option(name + 4, value, option_set_command);
6783 else if (*opt_head && !prefixcmp(name, "branch.") &&
6784 !strncmp(name + 7, opt_head, strlen(opt_head)))
6785 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6787 return OK;
6788 }
6790 static int
6791 load_git_config(void)
6792 {
6793 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6795 return run_io_load(config_list_argv, "=", read_repo_config_option);
6796 }
6798 static int
6799 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6800 {
6801 if (!opt_git_dir[0]) {
6802 string_ncopy(opt_git_dir, name, namelen);
6804 } else if (opt_is_inside_work_tree == -1) {
6805 /* This can be 3 different values depending on the
6806 * version of git being used. If git-rev-parse does not
6807 * understand --is-inside-work-tree it will simply echo
6808 * the option else either "true" or "false" is printed.
6809 * Default to true for the unknown case. */
6810 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6812 } else if (*name == '.') {
6813 string_ncopy(opt_cdup, name, namelen);
6815 } else {
6816 string_ncopy(opt_prefix, name, namelen);
6817 }
6819 return OK;
6820 }
6822 static int
6823 load_repo_info(void)
6824 {
6825 const char *head_argv[] = {
6826 "git", "symbolic-ref", "HEAD", NULL
6827 };
6828 const char *rev_parse_argv[] = {
6829 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6830 "--show-cdup", "--show-prefix", NULL
6831 };
6833 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6834 chomp_string(opt_head);
6835 if (!prefixcmp(opt_head, "refs/heads/")) {
6836 char *offset = opt_head + STRING_SIZE("refs/heads/");
6838 memmove(opt_head, offset, strlen(offset) + 1);
6839 }
6840 }
6842 return run_io_load(rev_parse_argv, "=", read_repo_info);
6843 }
6846 /*
6847 * Main
6848 */
6850 static const char usage[] =
6851 "tig " TIG_VERSION " (" __DATE__ ")\n"
6852 "\n"
6853 "Usage: tig [options] [revs] [--] [paths]\n"
6854 " or: tig show [options] [revs] [--] [paths]\n"
6855 " or: tig blame [rev] path\n"
6856 " or: tig status\n"
6857 " or: tig < [git command output]\n"
6858 "\n"
6859 "Options:\n"
6860 " -v, --version Show version and exit\n"
6861 " -h, --help Show help message and exit";
6863 static void __NORETURN
6864 quit(int sig)
6865 {
6866 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6867 if (cursed)
6868 endwin();
6869 exit(0);
6870 }
6872 static void __NORETURN
6873 die(const char *err, ...)
6874 {
6875 va_list args;
6877 endwin();
6879 va_start(args, err);
6880 fputs("tig: ", stderr);
6881 vfprintf(stderr, err, args);
6882 fputs("\n", stderr);
6883 va_end(args);
6885 exit(1);
6886 }
6888 static void
6889 warn(const char *msg, ...)
6890 {
6891 va_list args;
6893 va_start(args, msg);
6894 fputs("tig warning: ", stderr);
6895 vfprintf(stderr, msg, args);
6896 fputs("\n", stderr);
6897 va_end(args);
6898 }
6900 static enum request
6901 parse_options(int argc, const char *argv[])
6902 {
6903 enum request request = REQ_VIEW_MAIN;
6904 const char *subcommand;
6905 bool seen_dashdash = FALSE;
6906 /* XXX: This is vulnerable to the user overriding options
6907 * required for the main view parser. */
6908 const char *custom_argv[SIZEOF_ARG] = {
6909 "git", "log", "--no-color", "--pretty=raw", "--parents",
6910 "--topo-order", NULL
6911 };
6912 int i, j = 6;
6914 if (!isatty(STDIN_FILENO)) {
6915 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6916 return REQ_VIEW_PAGER;
6917 }
6919 if (argc <= 1)
6920 return REQ_NONE;
6922 subcommand = argv[1];
6923 if (!strcmp(subcommand, "status")) {
6924 if (argc > 2)
6925 warn("ignoring arguments after `%s'", subcommand);
6926 return REQ_VIEW_STATUS;
6928 } else if (!strcmp(subcommand, "blame")) {
6929 if (argc <= 2 || argc > 4)
6930 die("invalid number of options to blame\n\n%s", usage);
6932 i = 2;
6933 if (argc == 4) {
6934 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6935 i++;
6936 }
6938 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6939 return REQ_VIEW_BLAME;
6941 } else if (!strcmp(subcommand, "show")) {
6942 request = REQ_VIEW_DIFF;
6944 } else {
6945 subcommand = NULL;
6946 }
6948 if (subcommand) {
6949 custom_argv[1] = subcommand;
6950 j = 2;
6951 }
6953 for (i = 1 + !!subcommand; i < argc; i++) {
6954 const char *opt = argv[i];
6956 if (seen_dashdash || !strcmp(opt, "--")) {
6957 seen_dashdash = TRUE;
6959 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6960 printf("tig version %s\n", TIG_VERSION);
6961 quit(0);
6963 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6964 printf("%s\n", usage);
6965 quit(0);
6966 }
6968 custom_argv[j++] = opt;
6969 if (j >= ARRAY_SIZE(custom_argv))
6970 die("command too long");
6971 }
6973 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6974 die("Failed to format arguments");
6976 return request;
6977 }
6979 int
6980 main(int argc, const char *argv[])
6981 {
6982 enum request request = parse_options(argc, argv);
6983 struct view *view;
6984 size_t i;
6986 signal(SIGINT, quit);
6987 signal(SIGPIPE, SIG_IGN);
6989 if (setlocale(LC_ALL, "")) {
6990 char *codeset = nl_langinfo(CODESET);
6992 string_ncopy(opt_codeset, codeset, strlen(codeset));
6993 }
6995 if (load_repo_info() == ERR)
6996 die("Failed to load repo info.");
6998 if (load_options() == ERR)
6999 die("Failed to load user config.");
7001 if (load_git_config() == ERR)
7002 die("Failed to load repo config.");
7004 /* Require a git repository unless when running in pager mode. */
7005 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7006 die("Not a git repository");
7008 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7009 opt_utf8 = FALSE;
7011 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7012 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7013 if (opt_iconv == ICONV_NONE)
7014 die("Failed to initialize character set conversion");
7015 }
7017 if (load_refs() == ERR)
7018 die("Failed to load refs.");
7020 foreach_view (view, i)
7021 argv_from_env(view->ops->argv, view->cmd_env);
7023 init_display();
7025 if (request != REQ_NONE)
7026 open_view(NULL, request, OPEN_PREPARED);
7027 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7029 while (view_driver(display[current_view], request)) {
7030 int key = get_input(0);
7032 view = display[current_view];
7033 request = get_keybinding(view->keymap, key);
7035 /* Some low-level request handling. This keeps access to
7036 * status_win restricted. */
7037 switch (request) {
7038 case REQ_PROMPT:
7039 {
7040 char *cmd = read_prompt(":");
7042 if (cmd && isdigit(*cmd)) {
7043 int lineno = view->lineno + 1;
7045 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7046 select_view_line(view, lineno - 1);
7047 report("");
7048 } else {
7049 report("Unable to parse '%s' as a line number", cmd);
7050 }
7052 } else if (cmd) {
7053 struct view *next = VIEW(REQ_VIEW_PAGER);
7054 const char *argv[SIZEOF_ARG] = { "git" };
7055 int argc = 1;
7057 /* When running random commands, initially show the
7058 * command in the title. However, it maybe later be
7059 * overwritten if a commit line is selected. */
7060 string_ncopy(next->ref, cmd, strlen(cmd));
7062 if (!argv_from_string(argv, &argc, cmd)) {
7063 report("Too many arguments");
7064 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7065 report("Failed to format command");
7066 } else {
7067 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7068 }
7069 }
7071 request = REQ_NONE;
7072 break;
7073 }
7074 case REQ_SEARCH:
7075 case REQ_SEARCH_BACK:
7076 {
7077 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7078 char *search = read_prompt(prompt);
7080 if (search)
7081 string_ncopy(opt_search, search, strlen(search));
7082 else if (*opt_search)
7083 request = request == REQ_SEARCH ?
7084 REQ_FIND_NEXT :
7085 REQ_FIND_PREV;
7086 else
7087 request = REQ_NONE;
7088 break;
7089 }
7090 default:
7091 break;
7092 }
7093 }
7095 quit(0);
7097 return 0;
7098 }