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
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 /*
164 * String helpers
165 */
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
169 {
170 if (srclen > dstlen - 1)
171 srclen = dstlen - 1;
173 strncpy(dst, src, srclen);
174 dst[srclen] = 0;
175 }
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static void
192 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
193 {
194 size_t size, pos;
196 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
197 if (src[pos] == '\t') {
198 size_t expanded = tabsize - (size % tabsize);
200 if (expanded + size >= dstlen - 1)
201 expanded = dstlen - size - 1;
202 memcpy(dst + size, " ", expanded);
203 size += expanded;
204 } else {
205 dst[size++] = src[pos];
206 }
207 }
209 dst[size] = 0;
210 }
212 static char *
213 chomp_string(char *name)
214 {
215 int namelen;
217 while (isspace(*name))
218 name++;
220 namelen = strlen(name) - 1;
221 while (namelen > 0 && isspace(name[namelen]))
222 name[namelen--] = 0;
224 return name;
225 }
227 static bool
228 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
229 {
230 va_list args;
231 size_t pos = bufpos ? *bufpos : 0;
233 va_start(args, fmt);
234 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
235 va_end(args);
237 if (bufpos)
238 *bufpos = pos;
240 return pos >= bufsize ? FALSE : TRUE;
241 }
243 #define string_format(buf, fmt, args...) \
244 string_nformat(buf, sizeof(buf), NULL, fmt, args)
246 #define string_format_from(buf, from, fmt, args...) \
247 string_nformat(buf, sizeof(buf), from, fmt, args)
249 static int
250 string_enum_compare(const char *str1, const char *str2, int len)
251 {
252 size_t i;
254 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
256 /* Diff-Header == DIFF_HEADER */
257 for (i = 0; i < len; i++) {
258 if (toupper(str1[i]) == toupper(str2[i]))
259 continue;
261 if (string_enum_sep(str1[i]) &&
262 string_enum_sep(str2[i]))
263 continue;
265 return str1[i] - str2[i];
266 }
268 return 0;
269 }
271 struct enum_map {
272 const char *name;
273 int namelen;
274 int value;
275 };
277 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
279 static bool
280 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
281 {
282 size_t namelen = strlen(name);
283 int i;
285 for (i = 0; i < map_size; i++)
286 if (namelen == map[i].namelen &&
287 !string_enum_compare(name, map[i].name, namelen)) {
288 *value = map[i].value;
289 return TRUE;
290 }
292 return FALSE;
293 }
295 #define map_enum(attr, map, name) \
296 map_enum_do(map, ARRAY_SIZE(map), attr, name)
298 #define prefixcmp(str1, str2) \
299 strncmp(str1, str2, STRING_SIZE(str2))
301 static inline int
302 suffixcmp(const char *str, int slen, const char *suffix)
303 {
304 size_t len = slen >= 0 ? slen : strlen(str);
305 size_t suffixlen = strlen(suffix);
307 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
308 }
311 static bool
312 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
313 {
314 int valuelen;
316 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
317 bool advance = cmd[valuelen] != 0;
319 cmd[valuelen] = 0;
320 argv[(*argc)++] = chomp_string(cmd);
321 cmd = chomp_string(cmd + valuelen + advance);
322 }
324 if (*argc < SIZEOF_ARG)
325 argv[*argc] = NULL;
326 return *argc < SIZEOF_ARG;
327 }
329 static void
330 argv_from_env(const char **argv, const char *name)
331 {
332 char *env = argv ? getenv(name) : NULL;
333 int argc = 0;
335 if (env && *env)
336 env = strdup(env);
337 if (env && !argv_from_string(argv, &argc, env))
338 die("Too many arguments in the `%s` environment variable", name);
339 }
342 /*
343 * Executing external commands.
344 */
346 enum io_type {
347 IO_FD, /* File descriptor based IO. */
348 IO_BG, /* Execute command in the background. */
349 IO_FG, /* Execute command with same std{in,out,err}. */
350 IO_RD, /* Read only fork+exec IO. */
351 IO_WR, /* Write only fork+exec IO. */
352 IO_AP, /* Append fork+exec output to file. */
353 };
355 struct io {
356 enum io_type type; /* The requested type of pipe. */
357 const char *dir; /* Directory from which to execute. */
358 pid_t pid; /* Pipe for reading or writing. */
359 int pipe; /* Pipe end for reading or writing. */
360 int error; /* Error status. */
361 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
362 char *buf; /* Read buffer. */
363 size_t bufalloc; /* Allocated buffer size. */
364 size_t bufsize; /* Buffer content size. */
365 char *bufpos; /* Current buffer position. */
366 unsigned int eof:1; /* Has end of file been reached. */
367 };
369 static void
370 reset_io(struct io *io)
371 {
372 io->pipe = -1;
373 io->pid = 0;
374 io->buf = io->bufpos = NULL;
375 io->bufalloc = io->bufsize = 0;
376 io->error = 0;
377 io->eof = 0;
378 }
380 static void
381 init_io(struct io *io, const char *dir, enum io_type type)
382 {
383 reset_io(io);
384 io->type = type;
385 io->dir = dir;
386 }
388 static bool
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
391 {
392 init_io(io, dir, IO_RD);
393 return format_argv(io->argv, argv, flags);
394 }
396 static bool
397 io_open(struct io *io, const char *name)
398 {
399 init_io(io, NULL, IO_FD);
400 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
401 return io->pipe != -1;
402 }
404 static bool
405 kill_io(struct io *io)
406 {
407 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
408 }
410 static bool
411 done_io(struct io *io)
412 {
413 pid_t pid = io->pid;
415 if (io->pipe != -1)
416 close(io->pipe);
417 free(io->buf);
418 reset_io(io);
420 while (pid > 0) {
421 int status;
422 pid_t waiting = waitpid(pid, &status, 0);
424 if (waiting < 0) {
425 if (errno == EINTR)
426 continue;
427 report("waitpid failed (%s)", strerror(errno));
428 return FALSE;
429 }
431 return waiting == pid &&
432 !WIFSIGNALED(status) &&
433 WIFEXITED(status) &&
434 !WEXITSTATUS(status);
435 }
437 return TRUE;
438 }
440 static bool
441 start_io(struct io *io)
442 {
443 int pipefds[2] = { -1, -1 };
445 if (io->type == IO_FD)
446 return TRUE;
448 if ((io->type == IO_RD || io->type == IO_WR) &&
449 pipe(pipefds) < 0)
450 return FALSE;
451 else if (io->type == IO_AP)
452 pipefds[1] = io->pipe;
454 if ((io->pid = fork())) {
455 if (pipefds[!(io->type == IO_WR)] != -1)
456 close(pipefds[!(io->type == IO_WR)]);
457 if (io->pid != -1) {
458 io->pipe = pipefds[!!(io->type == IO_WR)];
459 return TRUE;
460 }
462 } else {
463 if (io->type != IO_FG) {
464 int devnull = open("/dev/null", O_RDWR);
465 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
466 int writefd = (io->type == IO_RD || io->type == IO_AP)
467 ? pipefds[1] : devnull;
469 dup2(readfd, STDIN_FILENO);
470 dup2(writefd, STDOUT_FILENO);
471 dup2(devnull, STDERR_FILENO);
473 close(devnull);
474 if (pipefds[0] != -1)
475 close(pipefds[0]);
476 if (pipefds[1] != -1)
477 close(pipefds[1]);
478 }
480 if (io->dir && *io->dir && chdir(io->dir) == -1)
481 die("Failed to change directory: %s", strerror(errno));
483 execvp(io->argv[0], (char *const*) io->argv);
484 die("Failed to execute program: %s", strerror(errno));
485 }
487 if (pipefds[!!(io->type == IO_WR)] != -1)
488 close(pipefds[!!(io->type == IO_WR)]);
489 return FALSE;
490 }
492 static bool
493 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
494 {
495 init_io(io, dir, type);
496 if (!format_argv(io->argv, argv, FORMAT_NONE))
497 return FALSE;
498 return start_io(io);
499 }
501 static int
502 run_io_do(struct io *io)
503 {
504 return start_io(io) && done_io(io);
505 }
507 static int
508 run_io_bg(const char **argv)
509 {
510 struct io io = {};
512 init_io(&io, NULL, IO_BG);
513 if (!format_argv(io.argv, argv, FORMAT_NONE))
514 return FALSE;
515 return run_io_do(&io);
516 }
518 static bool
519 run_io_fg(const char **argv, const char *dir)
520 {
521 struct io io = {};
523 init_io(&io, dir, IO_FG);
524 if (!format_argv(io.argv, argv, FORMAT_NONE))
525 return FALSE;
526 return run_io_do(&io);
527 }
529 static bool
530 run_io_append(const char **argv, enum format_flags flags, int fd)
531 {
532 struct io io = {};
534 init_io(&io, NULL, IO_AP);
535 io.pipe = fd;
536 if (format_argv(io.argv, argv, flags))
537 return run_io_do(&io);
538 close(fd);
539 return FALSE;
540 }
542 static bool
543 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
544 {
545 return init_io_rd(io, argv, NULL, flags) && start_io(io);
546 }
548 static bool
549 io_eof(struct io *io)
550 {
551 return io->eof;
552 }
554 static int
555 io_error(struct io *io)
556 {
557 return io->error;
558 }
560 static char *
561 io_strerror(struct io *io)
562 {
563 return strerror(io->error);
564 }
566 static bool
567 io_can_read(struct io *io)
568 {
569 struct timeval tv = { 0, 500 };
570 fd_set fds;
572 FD_ZERO(&fds);
573 FD_SET(io->pipe, &fds);
575 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
576 }
578 static ssize_t
579 io_read(struct io *io, void *buf, size_t bufsize)
580 {
581 do {
582 ssize_t readsize = read(io->pipe, buf, bufsize);
584 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
585 continue;
586 else if (readsize == -1)
587 io->error = errno;
588 else if (readsize == 0)
589 io->eof = 1;
590 return readsize;
591 } while (1);
592 }
594 static char *
595 io_get(struct io *io, int c, bool can_read)
596 {
597 char *eol;
598 ssize_t readsize;
600 if (!io->buf) {
601 io->buf = io->bufpos = malloc(BUFSIZ);
602 if (!io->buf)
603 return NULL;
604 io->bufalloc = BUFSIZ;
605 io->bufsize = 0;
606 }
608 while (TRUE) {
609 if (io->bufsize > 0) {
610 eol = memchr(io->bufpos, c, io->bufsize);
611 if (eol) {
612 char *line = io->bufpos;
614 *eol = 0;
615 io->bufpos = eol + 1;
616 io->bufsize -= io->bufpos - line;
617 return line;
618 }
619 }
621 if (io_eof(io)) {
622 if (io->bufsize) {
623 io->bufpos[io->bufsize] = 0;
624 io->bufsize = 0;
625 return io->bufpos;
626 }
627 return NULL;
628 }
630 if (!can_read)
631 return NULL;
633 if (io->bufsize > 0 && io->bufpos > io->buf)
634 memmove(io->buf, io->bufpos, io->bufsize);
636 io->bufpos = io->buf;
637 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
638 if (io_error(io))
639 return NULL;
640 io->bufsize += readsize;
641 }
642 }
644 static bool
645 io_write(struct io *io, const void *buf, size_t bufsize)
646 {
647 size_t written = 0;
649 while (!io_error(io) && written < bufsize) {
650 ssize_t size;
652 size = write(io->pipe, buf + written, bufsize - written);
653 if (size < 0 && (errno == EAGAIN || errno == EINTR))
654 continue;
655 else if (size == -1)
656 io->error = errno;
657 else
658 written += size;
659 }
661 return written == bufsize;
662 }
664 static bool
665 io_read_buf(struct io *io, char buf[], size_t bufsize)
666 {
667 bool error;
669 io->buf = io->bufpos = buf;
670 io->bufalloc = bufsize;
671 error = !io_get(io, '\n', TRUE) && io_error(io);
672 io->buf = NULL;
674 return done_io(io) || error;
675 }
677 static bool
678 run_io_buf(const char **argv, char buf[], size_t bufsize)
679 {
680 struct io io = {};
682 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
683 }
685 static int
686 io_load(struct io *io, const char *separators,
687 int (*read_property)(char *, size_t, char *, size_t))
688 {
689 char *name;
690 int state = OK;
692 if (!start_io(io))
693 return ERR;
695 while (state == OK && (name = io_get(io, '\n', TRUE))) {
696 char *value;
697 size_t namelen;
698 size_t valuelen;
700 name = chomp_string(name);
701 namelen = strcspn(name, separators);
703 if (name[namelen]) {
704 name[namelen] = 0;
705 value = chomp_string(name + namelen + 1);
706 valuelen = strlen(value);
708 } else {
709 value = "";
710 valuelen = 0;
711 }
713 state = read_property(name, namelen, value, valuelen);
714 }
716 if (state != ERR && io_error(io))
717 state = ERR;
718 done_io(io);
720 return state;
721 }
723 static int
724 run_io_load(const char **argv, const char *separators,
725 int (*read_property)(char *, size_t, char *, size_t))
726 {
727 struct io io = {};
729 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
730 ? io_load(&io, separators, read_property) : ERR;
731 }
734 /*
735 * User requests
736 */
738 #define REQ_INFO \
739 /* XXX: Keep the view request first and in sync with views[]. */ \
740 REQ_GROUP("View switching") \
741 REQ_(VIEW_MAIN, "Show main view"), \
742 REQ_(VIEW_DIFF, "Show diff view"), \
743 REQ_(VIEW_LOG, "Show log view"), \
744 REQ_(VIEW_TREE, "Show tree view"), \
745 REQ_(VIEW_BLOB, "Show blob view"), \
746 REQ_(VIEW_BLAME, "Show blame view"), \
747 REQ_(VIEW_HELP, "Show help page"), \
748 REQ_(VIEW_PAGER, "Show pager view"), \
749 REQ_(VIEW_STATUS, "Show status view"), \
750 REQ_(VIEW_STAGE, "Show stage view"), \
751 \
752 REQ_GROUP("View manipulation") \
753 REQ_(ENTER, "Enter current line and scroll"), \
754 REQ_(NEXT, "Move to next"), \
755 REQ_(PREVIOUS, "Move to previous"), \
756 REQ_(PARENT, "Move to parent"), \
757 REQ_(VIEW_NEXT, "Move focus to next view"), \
758 REQ_(REFRESH, "Reload and refresh"), \
759 REQ_(MAXIMIZE, "Maximize the current view"), \
760 REQ_(VIEW_CLOSE, "Close the current view"), \
761 REQ_(QUIT, "Close all views and quit"), \
762 \
763 REQ_GROUP("View specific requests") \
764 REQ_(STATUS_UPDATE, "Update file status"), \
765 REQ_(STATUS_REVERT, "Revert file changes"), \
766 REQ_(STATUS_MERGE, "Merge file using external tool"), \
767 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
768 \
769 REQ_GROUP("Cursor navigation") \
770 REQ_(MOVE_UP, "Move cursor one line up"), \
771 REQ_(MOVE_DOWN, "Move cursor one line down"), \
772 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
773 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
774 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
775 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
776 \
777 REQ_GROUP("Scrolling") \
778 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
779 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
780 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
781 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
782 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
783 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
784 \
785 REQ_GROUP("Searching") \
786 REQ_(SEARCH, "Search the view"), \
787 REQ_(SEARCH_BACK, "Search backwards in the view"), \
788 REQ_(FIND_NEXT, "Find next search match"), \
789 REQ_(FIND_PREV, "Find previous search match"), \
790 \
791 REQ_GROUP("Option manipulation") \
792 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
793 REQ_(TOGGLE_DATE, "Toggle date display"), \
794 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
795 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
796 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
797 \
798 REQ_GROUP("Misc") \
799 REQ_(PROMPT, "Bring up the prompt"), \
800 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
801 REQ_(SHOW_VERSION, "Show version information"), \
802 REQ_(STOP_LOADING, "Stop all loading views"), \
803 REQ_(EDIT, "Open in editor"), \
804 REQ_(NONE, "Do nothing")
807 /* User action requests. */
808 enum request {
809 #define REQ_GROUP(help)
810 #define REQ_(req, help) REQ_##req
812 /* Offset all requests to avoid conflicts with ncurses getch values. */
813 REQ_OFFSET = KEY_MAX + 1,
814 REQ_INFO
816 #undef REQ_GROUP
817 #undef REQ_
818 };
820 struct request_info {
821 enum request request;
822 const char *name;
823 int namelen;
824 const char *help;
825 };
827 static const struct request_info req_info[] = {
828 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
829 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
830 REQ_INFO
831 #undef REQ_GROUP
832 #undef REQ_
833 };
835 static enum request
836 get_request(const char *name)
837 {
838 int namelen = strlen(name);
839 int i;
841 for (i = 0; i < ARRAY_SIZE(req_info); i++)
842 if (req_info[i].namelen == namelen &&
843 !string_enum_compare(req_info[i].name, name, namelen))
844 return req_info[i].request;
846 return REQ_NONE;
847 }
850 /*
851 * Options
852 */
854 /* Option and state variables. */
855 static bool opt_date = TRUE;
856 static bool opt_author = TRUE;
857 static bool opt_line_number = FALSE;
858 static bool opt_line_graphics = TRUE;
859 static bool opt_rev_graph = FALSE;
860 static bool opt_show_refs = TRUE;
861 static int opt_num_interval = NUMBER_INTERVAL;
862 static int opt_tab_size = TAB_SIZE;
863 static int opt_author_cols = AUTHOR_COLS-1;
864 static char opt_path[SIZEOF_STR] = "";
865 static char opt_file[SIZEOF_STR] = "";
866 static char opt_ref[SIZEOF_REF] = "";
867 static char opt_head[SIZEOF_REF] = "";
868 static char opt_head_rev[SIZEOF_REV] = "";
869 static char opt_remote[SIZEOF_REF] = "";
870 static char opt_encoding[20] = "UTF-8";
871 static bool opt_utf8 = TRUE;
872 static char opt_codeset[20] = "UTF-8";
873 static iconv_t opt_iconv = ICONV_NONE;
874 static char opt_search[SIZEOF_STR] = "";
875 static char opt_cdup[SIZEOF_STR] = "";
876 static char opt_prefix[SIZEOF_STR] = "";
877 static char opt_git_dir[SIZEOF_STR] = "";
878 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
879 static char opt_editor[SIZEOF_STR] = "";
880 static FILE *opt_tty = NULL;
882 #define is_initial_commit() (!*opt_head_rev)
883 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
886 /*
887 * Line-oriented content detection.
888 */
890 #define LINE_INFO \
891 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
892 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
893 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
894 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
895 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
896 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
897 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
898 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
900 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
905 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
906 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
907 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
908 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
913 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
914 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
916 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
920 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
921 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
922 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
923 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
925 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
926 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
927 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
928 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
929 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
930 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
933 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
934 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
935 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
936 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
937 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
938 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
939 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
941 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
942 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
944 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
947 enum line_type {
948 #define LINE(type, line, fg, bg, attr) \
949 LINE_##type
950 LINE_INFO,
951 LINE_NONE
952 #undef LINE
953 };
955 struct line_info {
956 const char *name; /* Option name. */
957 int namelen; /* Size of option name. */
958 const char *line; /* The start of line to match. */
959 int linelen; /* Size of string to match. */
960 int fg, bg, attr; /* Color and text attributes for the lines. */
961 };
963 static struct line_info line_info[] = {
964 #define LINE(type, line, fg, bg, attr) \
965 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
966 LINE_INFO
967 #undef LINE
968 };
970 static enum line_type
971 get_line_type(const char *line)
972 {
973 int linelen = strlen(line);
974 enum line_type type;
976 for (type = 0; type < ARRAY_SIZE(line_info); type++)
977 /* Case insensitive search matches Signed-off-by lines better. */
978 if (linelen >= line_info[type].linelen &&
979 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
980 return type;
982 return LINE_DEFAULT;
983 }
985 static inline int
986 get_line_attr(enum line_type type)
987 {
988 assert(type < ARRAY_SIZE(line_info));
989 return COLOR_PAIR(type) | line_info[type].attr;
990 }
992 static struct line_info *
993 get_line_info(const char *name)
994 {
995 size_t namelen = strlen(name);
996 enum line_type type;
998 for (type = 0; type < ARRAY_SIZE(line_info); type++)
999 if (namelen == line_info[type].namelen &&
1000 !string_enum_compare(line_info[type].name, name, namelen))
1001 return &line_info[type];
1003 return NULL;
1004 }
1006 static void
1007 init_colors(void)
1008 {
1009 int default_bg = line_info[LINE_DEFAULT].bg;
1010 int default_fg = line_info[LINE_DEFAULT].fg;
1011 enum line_type type;
1013 start_color();
1015 if (assume_default_colors(default_fg, default_bg) == ERR) {
1016 default_bg = COLOR_BLACK;
1017 default_fg = COLOR_WHITE;
1018 }
1020 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1021 struct line_info *info = &line_info[type];
1022 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1023 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1025 init_pair(type, fg, bg);
1026 }
1027 }
1029 struct line {
1030 enum line_type type;
1032 /* State flags */
1033 unsigned int selected:1;
1034 unsigned int dirty:1;
1035 unsigned int cleareol:1;
1037 void *data; /* User data */
1038 };
1041 /*
1042 * Keys
1043 */
1045 struct keybinding {
1046 int alias;
1047 enum request request;
1048 };
1050 static const struct keybinding default_keybindings[] = {
1051 /* View switching */
1052 { 'm', REQ_VIEW_MAIN },
1053 { 'd', REQ_VIEW_DIFF },
1054 { 'l', REQ_VIEW_LOG },
1055 { 't', REQ_VIEW_TREE },
1056 { 'f', REQ_VIEW_BLOB },
1057 { 'B', REQ_VIEW_BLAME },
1058 { 'p', REQ_VIEW_PAGER },
1059 { 'h', REQ_VIEW_HELP },
1060 { 'S', REQ_VIEW_STATUS },
1061 { 'c', REQ_VIEW_STAGE },
1063 /* View manipulation */
1064 { 'q', REQ_VIEW_CLOSE },
1065 { KEY_TAB, REQ_VIEW_NEXT },
1066 { KEY_RETURN, REQ_ENTER },
1067 { KEY_UP, REQ_PREVIOUS },
1068 { KEY_DOWN, REQ_NEXT },
1069 { 'R', REQ_REFRESH },
1070 { KEY_F(5), REQ_REFRESH },
1071 { 'O', REQ_MAXIMIZE },
1073 /* Cursor navigation */
1074 { 'k', REQ_MOVE_UP },
1075 { 'j', REQ_MOVE_DOWN },
1076 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1077 { KEY_END, REQ_MOVE_LAST_LINE },
1078 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1079 { ' ', REQ_MOVE_PAGE_DOWN },
1080 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1081 { 'b', REQ_MOVE_PAGE_UP },
1082 { '-', REQ_MOVE_PAGE_UP },
1084 /* Scrolling */
1085 { KEY_LEFT, REQ_SCROLL_LEFT },
1086 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1087 { KEY_IC, REQ_SCROLL_LINE_UP },
1088 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1089 { 'w', REQ_SCROLL_PAGE_UP },
1090 { 's', REQ_SCROLL_PAGE_DOWN },
1092 /* Searching */
1093 { '/', REQ_SEARCH },
1094 { '?', REQ_SEARCH_BACK },
1095 { 'n', REQ_FIND_NEXT },
1096 { 'N', REQ_FIND_PREV },
1098 /* Misc */
1099 { 'Q', REQ_QUIT },
1100 { 'z', REQ_STOP_LOADING },
1101 { 'v', REQ_SHOW_VERSION },
1102 { 'r', REQ_SCREEN_REDRAW },
1103 { '.', REQ_TOGGLE_LINENO },
1104 { 'D', REQ_TOGGLE_DATE },
1105 { 'A', REQ_TOGGLE_AUTHOR },
1106 { 'g', REQ_TOGGLE_REV_GRAPH },
1107 { 'F', REQ_TOGGLE_REFS },
1108 { ':', REQ_PROMPT },
1109 { 'u', REQ_STATUS_UPDATE },
1110 { '!', REQ_STATUS_REVERT },
1111 { 'M', REQ_STATUS_MERGE },
1112 { '@', REQ_STAGE_NEXT },
1113 { ',', REQ_PARENT },
1114 { 'e', REQ_EDIT },
1115 };
1117 #define KEYMAP_INFO \
1118 KEYMAP_(GENERIC), \
1119 KEYMAP_(MAIN), \
1120 KEYMAP_(DIFF), \
1121 KEYMAP_(LOG), \
1122 KEYMAP_(TREE), \
1123 KEYMAP_(BLOB), \
1124 KEYMAP_(BLAME), \
1125 KEYMAP_(PAGER), \
1126 KEYMAP_(HELP), \
1127 KEYMAP_(STATUS), \
1128 KEYMAP_(STAGE)
1130 enum keymap {
1131 #define KEYMAP_(name) KEYMAP_##name
1132 KEYMAP_INFO
1133 #undef KEYMAP_
1134 };
1136 static const struct enum_map keymap_table[] = {
1137 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1138 KEYMAP_INFO
1139 #undef KEYMAP_
1140 };
1142 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1144 struct keybinding_table {
1145 struct keybinding *data;
1146 size_t size;
1147 };
1149 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1151 static void
1152 add_keybinding(enum keymap keymap, enum request request, int key)
1153 {
1154 struct keybinding_table *table = &keybindings[keymap];
1156 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1157 if (!table->data)
1158 die("Failed to allocate keybinding");
1159 table->data[table->size].alias = key;
1160 table->data[table->size++].request = request;
1161 }
1163 /* Looks for a key binding first in the given map, then in the generic map, and
1164 * lastly in the default keybindings. */
1165 static enum request
1166 get_keybinding(enum keymap keymap, int key)
1167 {
1168 size_t i;
1170 for (i = 0; i < keybindings[keymap].size; i++)
1171 if (keybindings[keymap].data[i].alias == key)
1172 return keybindings[keymap].data[i].request;
1174 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1175 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1176 return keybindings[KEYMAP_GENERIC].data[i].request;
1178 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1179 if (default_keybindings[i].alias == key)
1180 return default_keybindings[i].request;
1182 return (enum request) key;
1183 }
1186 struct key {
1187 const char *name;
1188 int value;
1189 };
1191 static const struct key key_table[] = {
1192 { "Enter", KEY_RETURN },
1193 { "Space", ' ' },
1194 { "Backspace", KEY_BACKSPACE },
1195 { "Tab", KEY_TAB },
1196 { "Escape", KEY_ESC },
1197 { "Left", KEY_LEFT },
1198 { "Right", KEY_RIGHT },
1199 { "Up", KEY_UP },
1200 { "Down", KEY_DOWN },
1201 { "Insert", KEY_IC },
1202 { "Delete", KEY_DC },
1203 { "Hash", '#' },
1204 { "Home", KEY_HOME },
1205 { "End", KEY_END },
1206 { "PageUp", KEY_PPAGE },
1207 { "PageDown", KEY_NPAGE },
1208 { "F1", KEY_F(1) },
1209 { "F2", KEY_F(2) },
1210 { "F3", KEY_F(3) },
1211 { "F4", KEY_F(4) },
1212 { "F5", KEY_F(5) },
1213 { "F6", KEY_F(6) },
1214 { "F7", KEY_F(7) },
1215 { "F8", KEY_F(8) },
1216 { "F9", KEY_F(9) },
1217 { "F10", KEY_F(10) },
1218 { "F11", KEY_F(11) },
1219 { "F12", KEY_F(12) },
1220 };
1222 static int
1223 get_key_value(const char *name)
1224 {
1225 int i;
1227 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1228 if (!strcasecmp(key_table[i].name, name))
1229 return key_table[i].value;
1231 if (strlen(name) == 1 && isprint(*name))
1232 return (int) *name;
1234 return ERR;
1235 }
1237 static const char *
1238 get_key_name(int key_value)
1239 {
1240 static char key_char[] = "'X'";
1241 const char *seq = NULL;
1242 int key;
1244 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1245 if (key_table[key].value == key_value)
1246 seq = key_table[key].name;
1248 if (seq == NULL &&
1249 key_value < 127 &&
1250 isprint(key_value)) {
1251 key_char[1] = (char) key_value;
1252 seq = key_char;
1253 }
1255 return seq ? seq : "(no key)";
1256 }
1258 static const char *
1259 get_key(enum request request)
1260 {
1261 static char buf[BUFSIZ];
1262 size_t pos = 0;
1263 char *sep = "";
1264 int i;
1266 buf[pos] = 0;
1268 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1269 const struct keybinding *keybinding = &default_keybindings[i];
1271 if (keybinding->request != request)
1272 continue;
1274 if (!string_format_from(buf, &pos, "%s%s", sep,
1275 get_key_name(keybinding->alias)))
1276 return "Too many keybindings!";
1277 sep = ", ";
1278 }
1280 return buf;
1281 }
1283 struct run_request {
1284 enum keymap keymap;
1285 int key;
1286 const char *argv[SIZEOF_ARG];
1287 };
1289 static struct run_request *run_request;
1290 static size_t run_requests;
1292 static enum request
1293 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1294 {
1295 struct run_request *req;
1297 if (argc >= ARRAY_SIZE(req->argv) - 1)
1298 return REQ_NONE;
1300 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1301 if (!req)
1302 return REQ_NONE;
1304 run_request = req;
1305 req = &run_request[run_requests];
1306 req->keymap = keymap;
1307 req->key = key;
1308 req->argv[0] = NULL;
1310 if (!format_argv(req->argv, argv, FORMAT_NONE))
1311 return REQ_NONE;
1313 return REQ_NONE + ++run_requests;
1314 }
1316 static struct run_request *
1317 get_run_request(enum request request)
1318 {
1319 if (request <= REQ_NONE)
1320 return NULL;
1321 return &run_request[request - REQ_NONE - 1];
1322 }
1324 static void
1325 add_builtin_run_requests(void)
1326 {
1327 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1328 const char *gc[] = { "git", "gc", NULL };
1329 struct {
1330 enum keymap keymap;
1331 int key;
1332 int argc;
1333 const char **argv;
1334 } reqs[] = {
1335 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1336 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1337 };
1338 int i;
1340 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1341 enum request req;
1343 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1344 if (req != REQ_NONE)
1345 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1346 }
1347 }
1349 /*
1350 * User config file handling.
1351 */
1353 static int config_lineno;
1354 static bool config_errors;
1355 static const char *config_msg;
1357 static const struct enum_map color_map[] = {
1358 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1359 COLOR_MAP(DEFAULT),
1360 COLOR_MAP(BLACK),
1361 COLOR_MAP(BLUE),
1362 COLOR_MAP(CYAN),
1363 COLOR_MAP(GREEN),
1364 COLOR_MAP(MAGENTA),
1365 COLOR_MAP(RED),
1366 COLOR_MAP(WHITE),
1367 COLOR_MAP(YELLOW),
1368 };
1370 static const struct enum_map attr_map[] = {
1371 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1372 ATTR_MAP(NORMAL),
1373 ATTR_MAP(BLINK),
1374 ATTR_MAP(BOLD),
1375 ATTR_MAP(DIM),
1376 ATTR_MAP(REVERSE),
1377 ATTR_MAP(STANDOUT),
1378 ATTR_MAP(UNDERLINE),
1379 };
1381 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1383 static int
1384 parse_int(int *opt, const char *arg, int min, int max)
1385 {
1386 int value = atoi(arg);
1388 if (min <= value && value <= max) {
1389 *opt = value;
1390 return OK;
1391 }
1393 config_msg = "Integer value out of bound";
1394 return ERR;
1395 }
1397 static bool
1398 set_color(int *color, const char *name)
1399 {
1400 if (map_enum(color, color_map, name))
1401 return TRUE;
1402 if (!prefixcmp(name, "color"))
1403 return parse_int(color, name + 5, 0, 255) == OK;
1404 return FALSE;
1405 }
1407 /* Wants: object fgcolor bgcolor [attribute] */
1408 static int
1409 option_color_command(int argc, const char *argv[])
1410 {
1411 struct line_info *info;
1413 if (argc != 3 && argc != 4) {
1414 config_msg = "Wrong number of arguments given to color command";
1415 return ERR;
1416 }
1418 info = get_line_info(argv[0]);
1419 if (!info) {
1420 static const struct enum_map obsolete[] = {
1421 ENUM_MAP("main-delim", LINE_DELIMITER),
1422 ENUM_MAP("main-date", LINE_DATE),
1423 ENUM_MAP("main-author", LINE_AUTHOR),
1424 };
1425 int index;
1427 if (!map_enum(&index, obsolete, argv[0])) {
1428 config_msg = "Unknown color name";
1429 return ERR;
1430 }
1431 info = &line_info[index];
1432 }
1434 if (!set_color(&info->fg, argv[1]) ||
1435 !set_color(&info->bg, argv[2])) {
1436 config_msg = "Unknown color";
1437 return ERR;
1438 }
1440 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1441 config_msg = "Unknown attribute";
1442 return ERR;
1443 }
1445 return OK;
1446 }
1448 static int parse_bool(bool *opt, const char *arg)
1449 {
1450 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1451 ? TRUE : FALSE;
1452 return OK;
1453 }
1455 static int
1456 parse_string(char *opt, const char *arg, size_t optsize)
1457 {
1458 int arglen = strlen(arg);
1460 switch (arg[0]) {
1461 case '\"':
1462 case '\'':
1463 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1464 config_msg = "Unmatched quotation";
1465 return ERR;
1466 }
1467 arg += 1; arglen -= 2;
1468 default:
1469 string_ncopy_do(opt, optsize, arg, strlen(arg));
1470 return OK;
1471 }
1472 }
1474 /* Wants: name = value */
1475 static int
1476 option_set_command(int argc, const char *argv[])
1477 {
1478 if (argc != 3) {
1479 config_msg = "Wrong number of arguments given to set command";
1480 return ERR;
1481 }
1483 if (strcmp(argv[1], "=")) {
1484 config_msg = "No value assigned";
1485 return ERR;
1486 }
1488 if (!strcmp(argv[0], "show-author"))
1489 return parse_bool(&opt_author, argv[2]);
1491 if (!strcmp(argv[0], "show-date"))
1492 return parse_bool(&opt_date, argv[2]);
1494 if (!strcmp(argv[0], "show-rev-graph"))
1495 return parse_bool(&opt_rev_graph, argv[2]);
1497 if (!strcmp(argv[0], "show-refs"))
1498 return parse_bool(&opt_show_refs, argv[2]);
1500 if (!strcmp(argv[0], "show-line-numbers"))
1501 return parse_bool(&opt_line_number, argv[2]);
1503 if (!strcmp(argv[0], "line-graphics"))
1504 return parse_bool(&opt_line_graphics, argv[2]);
1506 if (!strcmp(argv[0], "line-number-interval"))
1507 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1509 if (!strcmp(argv[0], "author-width"))
1510 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1512 if (!strcmp(argv[0], "tab-size"))
1513 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1515 if (!strcmp(argv[0], "commit-encoding"))
1516 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1518 config_msg = "Unknown variable name";
1519 return ERR;
1520 }
1522 /* Wants: mode request key */
1523 static int
1524 option_bind_command(int argc, const char *argv[])
1525 {
1526 enum request request;
1527 int keymap;
1528 int key;
1530 if (argc < 3) {
1531 config_msg = "Wrong number of arguments given to bind command";
1532 return ERR;
1533 }
1535 if (set_keymap(&keymap, argv[0]) == ERR) {
1536 config_msg = "Unknown key map";
1537 return ERR;
1538 }
1540 key = get_key_value(argv[1]);
1541 if (key == ERR) {
1542 config_msg = "Unknown key";
1543 return ERR;
1544 }
1546 request = get_request(argv[2]);
1547 if (request == REQ_NONE) {
1548 static const struct enum_map obsolete[] = {
1549 ENUM_MAP("cherry-pick", REQ_NONE),
1550 ENUM_MAP("screen-resize", REQ_NONE),
1551 ENUM_MAP("tree-parent", REQ_PARENT),
1552 };
1553 int alias;
1555 if (map_enum(&alias, obsolete, argv[2])) {
1556 if (alias != REQ_NONE)
1557 add_keybinding(keymap, alias, key);
1558 config_msg = "Obsolete request name";
1559 return ERR;
1560 }
1561 }
1562 if (request == REQ_NONE && *argv[2]++ == '!')
1563 request = add_run_request(keymap, key, argc - 2, argv + 2);
1564 if (request == REQ_NONE) {
1565 config_msg = "Unknown request name";
1566 return ERR;
1567 }
1569 add_keybinding(keymap, request, key);
1571 return OK;
1572 }
1574 static int
1575 set_option(const char *opt, char *value)
1576 {
1577 const char *argv[SIZEOF_ARG];
1578 int argc = 0;
1580 if (!argv_from_string(argv, &argc, value)) {
1581 config_msg = "Too many option arguments";
1582 return ERR;
1583 }
1585 if (!strcmp(opt, "color"))
1586 return option_color_command(argc, argv);
1588 if (!strcmp(opt, "set"))
1589 return option_set_command(argc, argv);
1591 if (!strcmp(opt, "bind"))
1592 return option_bind_command(argc, argv);
1594 config_msg = "Unknown option command";
1595 return ERR;
1596 }
1598 static int
1599 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1600 {
1601 int status = OK;
1603 config_lineno++;
1604 config_msg = "Internal error";
1606 /* Check for comment markers, since read_properties() will
1607 * only ensure opt and value are split at first " \t". */
1608 optlen = strcspn(opt, "#");
1609 if (optlen == 0)
1610 return OK;
1612 if (opt[optlen] != 0) {
1613 config_msg = "No option value";
1614 status = ERR;
1616 } else {
1617 /* Look for comment endings in the value. */
1618 size_t len = strcspn(value, "#");
1620 if (len < valuelen) {
1621 valuelen = len;
1622 value[valuelen] = 0;
1623 }
1625 status = set_option(opt, value);
1626 }
1628 if (status == ERR) {
1629 warn("Error on line %d, near '%.*s': %s",
1630 config_lineno, (int) optlen, opt, config_msg);
1631 config_errors = TRUE;
1632 }
1634 /* Always keep going if errors are encountered. */
1635 return OK;
1636 }
1638 static void
1639 load_option_file(const char *path)
1640 {
1641 struct io io = {};
1643 /* It's OK that the file doesn't exist. */
1644 if (!io_open(&io, path))
1645 return;
1647 config_lineno = 0;
1648 config_errors = FALSE;
1650 if (io_load(&io, " \t", read_option) == ERR ||
1651 config_errors == TRUE)
1652 warn("Errors while loading %s.", path);
1653 }
1655 static int
1656 load_options(void)
1657 {
1658 const char *home = getenv("HOME");
1659 const char *tigrc_user = getenv("TIGRC_USER");
1660 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1661 char buf[SIZEOF_STR];
1663 add_builtin_run_requests();
1665 if (!tigrc_system)
1666 tigrc_system = SYSCONFDIR "/tigrc";
1667 load_option_file(tigrc_system);
1669 if (!tigrc_user) {
1670 if (!home || !string_format(buf, "%s/.tigrc", home))
1671 return ERR;
1672 tigrc_user = buf;
1673 }
1674 load_option_file(tigrc_user);
1676 return OK;
1677 }
1680 /*
1681 * The viewer
1682 */
1684 struct view;
1685 struct view_ops;
1687 /* The display array of active views and the index of the current view. */
1688 static struct view *display[2];
1689 static unsigned int current_view;
1691 #define foreach_displayed_view(view, i) \
1692 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1694 #define displayed_views() (display[1] != NULL ? 2 : 1)
1696 /* Current head and commit ID */
1697 static char ref_blob[SIZEOF_REF] = "";
1698 static char ref_commit[SIZEOF_REF] = "HEAD";
1699 static char ref_head[SIZEOF_REF] = "HEAD";
1701 struct view {
1702 const char *name; /* View name */
1703 const char *cmd_env; /* Command line set via environment */
1704 const char *id; /* Points to either of ref_{head,commit,blob} */
1706 struct view_ops *ops; /* View operations */
1708 enum keymap keymap; /* What keymap does this view have */
1709 bool git_dir; /* Whether the view requires a git directory. */
1711 char ref[SIZEOF_REF]; /* Hovered commit reference */
1712 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1714 int height, width; /* The width and height of the main window */
1715 WINDOW *win; /* The main window */
1716 WINDOW *title; /* The title window living below the main window */
1718 /* Navigation */
1719 unsigned long offset; /* Offset of the window top */
1720 unsigned long yoffset; /* Offset from the window side. */
1721 unsigned long lineno; /* Current line number */
1722 unsigned long p_offset; /* Previous offset of the window top */
1723 unsigned long p_yoffset;/* Previous offset from the window side */
1724 unsigned long p_lineno; /* Previous current line number */
1725 bool p_restore; /* Should the previous position be restored. */
1727 /* Searching */
1728 char grep[SIZEOF_STR]; /* Search string */
1729 regex_t *regex; /* Pre-compiled regexp */
1731 /* If non-NULL, points to the view that opened this view. If this view
1732 * is closed tig will switch back to the parent view. */
1733 struct view *parent;
1735 /* Buffering */
1736 size_t lines; /* Total number of lines */
1737 struct line *line; /* Line index */
1738 size_t line_alloc; /* Total number of allocated lines */
1739 unsigned int digits; /* Number of digits in the lines member. */
1741 /* Drawing */
1742 struct line *curline; /* Line currently being drawn. */
1743 enum line_type curtype; /* Attribute currently used for drawing. */
1744 unsigned long col; /* Column when drawing. */
1745 bool has_scrolled; /* View was scrolled. */
1746 bool can_hscroll; /* View can be scrolled horizontally. */
1748 /* Loading */
1749 struct io io;
1750 struct io *pipe;
1751 time_t start_time;
1752 time_t update_secs;
1753 };
1755 struct view_ops {
1756 /* What type of content being displayed. Used in the title bar. */
1757 const char *type;
1758 /* Default command arguments. */
1759 const char **argv;
1760 /* Open and reads in all view content. */
1761 bool (*open)(struct view *view);
1762 /* Read one line; updates view->line. */
1763 bool (*read)(struct view *view, char *data);
1764 /* Draw one line; @lineno must be < view->height. */
1765 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766 /* Depending on view handle a special requests. */
1767 enum request (*request)(struct view *view, enum request request, struct line *line);
1768 /* Search for regexp in a line. */
1769 bool (*grep)(struct view *view, struct line *line);
1770 /* Select line */
1771 void (*select)(struct view *view, struct line *line);
1772 };
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786 { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1794 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1795 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1796 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1797 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1798 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1799 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1800 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1801 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1802 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1803 };
1805 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812 (view == display[0] || view == display[1])
1815 enum line_graphic {
1816 LINE_GRAPHIC_VLINE
1817 };
1819 static int line_graphics[] = {
1820 /* LINE_GRAPHIC_VLINE: */ '|'
1821 };
1823 static inline void
1824 set_view_attr(struct view *view, enum line_type type)
1825 {
1826 if (!view->curline->selected && view->curtype != type) {
1827 wattrset(view->win, get_line_attr(type));
1828 wchgat(view->win, -1, 0, type, NULL);
1829 view->curtype = type;
1830 }
1831 }
1833 static int
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835 int max_len, bool use_tilde)
1836 {
1837 int len = 0;
1838 int col = 0;
1839 int trimmed = FALSE;
1840 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1842 if (max_len <= 0)
1843 return 0;
1845 if (opt_utf8) {
1846 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1847 } else {
1848 col = len = strlen(string);
1849 if (len > max_len) {
1850 if (use_tilde) {
1851 max_len -= 1;
1852 }
1853 col = len = max_len;
1854 trimmed = TRUE;
1855 }
1856 }
1858 set_view_attr(view, type);
1859 if (len > 0)
1860 waddnstr(view->win, string, len);
1861 if (trimmed && use_tilde) {
1862 set_view_attr(view, LINE_DELIMITER);
1863 waddch(view->win, '~');
1864 col++;
1865 }
1867 if (view->col + col >= view->width + view->yoffset)
1868 view->can_hscroll = TRUE;
1870 return col;
1871 }
1873 static int
1874 draw_space(struct view *view, enum line_type type, int max, int spaces)
1875 {
1876 static char space[] = " ";
1877 int col = 0;
1879 spaces = MIN(max, spaces);
1881 while (spaces > 0) {
1882 int len = MIN(spaces, sizeof(space) - 1);
1884 col += draw_chars(view, type, space, spaces, FALSE);
1885 spaces -= len;
1886 }
1888 return col;
1889 }
1891 static bool
1892 draw_lineno(struct view *view, unsigned int lineno)
1893 {
1894 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1895 char number[10];
1896 int digits3 = view->digits < 3 ? 3 : view->digits;
1897 int max_number = MIN(digits3, STRING_SIZE(number));
1898 int max = view->width - view->col;
1899 int col;
1901 if (max < max_number)
1902 max_number = max;
1904 lineno += view->offset + 1;
1905 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906 static char fmt[] = "%1ld";
1908 if (view->digits <= 9)
1909 fmt[1] = '0' + digits3;
1911 if (!string_format(number, fmt, lineno))
1912 number[0] = 0;
1913 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1914 } else {
1915 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1916 }
1918 if (col < max && skip <= col) {
1919 set_view_attr(view, LINE_DEFAULT);
1920 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1921 }
1922 col++;
1924 view->col += col;
1925 if (col < max && skip <= col)
1926 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1927 view->col++;
1929 return view->width + view->yoffset <= view->col;
1930 }
1932 static bool
1933 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1934 {
1935 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1936 return view->width - view->col <= 0;
1937 }
1939 static bool
1940 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1941 {
1942 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1943 int max = view->width - view->col;
1944 int i;
1946 if (max < size)
1947 size = max;
1949 set_view_attr(view, type);
1950 /* Using waddch() instead of waddnstr() ensures that
1951 * they'll be rendered correctly for the cursor line. */
1952 for (i = skip; i < size; i++)
1953 waddch(view->win, graphic[i]);
1955 view->col += size;
1956 if (size < max && skip <= size)
1957 waddch(view->win, ' ');
1958 view->col++;
1960 return view->width - view->col <= 0;
1961 }
1963 static bool
1964 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1965 {
1966 int max = MIN(view->width - view->col, len);
1967 int col;
1969 if (text)
1970 col = draw_chars(view, type, text, max - 1, trim);
1971 else
1972 col = draw_space(view, type, max - 1, max - 1);
1974 view->col += col;
1975 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1976 return view->width + view->yoffset <= view->col;
1977 }
1979 static bool
1980 draw_date(struct view *view, struct tm *time)
1981 {
1982 char buf[DATE_COLS];
1983 char *date;
1984 int timelen = 0;
1986 if (time)
1987 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1988 date = timelen ? buf : NULL;
1990 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1991 }
1993 static bool
1994 draw_author(struct view *view, const char *author)
1995 {
1996 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1998 if (!trim) {
1999 static char initials[10];
2000 size_t pos;
2002 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2004 memset(initials, 0, sizeof(initials));
2005 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2006 while (is_initial_sep(*author))
2007 author++;
2008 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2009 while (*author && !is_initial_sep(author[1]))
2010 author++;
2011 }
2013 author = initials;
2014 }
2016 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2017 }
2019 static bool
2020 draw_mode(struct view *view, mode_t mode)
2021 {
2022 static const char dir_mode[] = "drwxr-xr-x";
2023 static const char link_mode[] = "lrwxrwxrwx";
2024 static const char exe_mode[] = "-rwxr-xr-x";
2025 static const char file_mode[] = "-rw-r--r--";
2026 const char *str;
2028 if (S_ISDIR(mode))
2029 str = dir_mode;
2030 else if (S_ISLNK(mode))
2031 str = link_mode;
2032 else if (mode & S_IXUSR)
2033 str = exe_mode;
2034 else
2035 str = file_mode;
2037 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2038 }
2040 static bool
2041 draw_view_line(struct view *view, unsigned int lineno)
2042 {
2043 struct line *line;
2044 bool selected = (view->offset + lineno == view->lineno);
2046 assert(view_is_displayed(view));
2048 if (view->offset + lineno >= view->lines)
2049 return FALSE;
2051 line = &view->line[view->offset + lineno];
2053 wmove(view->win, lineno, 0);
2054 if (line->cleareol)
2055 wclrtoeol(view->win);
2056 view->col = 0;
2057 view->curline = line;
2058 view->curtype = LINE_NONE;
2059 line->selected = FALSE;
2060 line->dirty = line->cleareol = 0;
2062 if (selected) {
2063 set_view_attr(view, LINE_CURSOR);
2064 line->selected = TRUE;
2065 view->ops->select(view, line);
2066 }
2068 return view->ops->draw(view, line, lineno);
2069 }
2071 static void
2072 redraw_view_dirty(struct view *view)
2073 {
2074 bool dirty = FALSE;
2075 int lineno;
2077 for (lineno = 0; lineno < view->height; lineno++) {
2078 if (view->offset + lineno >= view->lines)
2079 break;
2080 if (!view->line[view->offset + lineno].dirty)
2081 continue;
2082 dirty = TRUE;
2083 if (!draw_view_line(view, lineno))
2084 break;
2085 }
2087 if (!dirty)
2088 return;
2089 wnoutrefresh(view->win);
2090 }
2092 static void
2093 redraw_view_from(struct view *view, int lineno)
2094 {
2095 assert(0 <= lineno && lineno < view->height);
2097 if (lineno == 0)
2098 view->can_hscroll = FALSE;
2100 for (; lineno < view->height; lineno++) {
2101 if (!draw_view_line(view, lineno))
2102 break;
2103 }
2105 wnoutrefresh(view->win);
2106 }
2108 static void
2109 redraw_view(struct view *view)
2110 {
2111 werase(view->win);
2112 redraw_view_from(view, 0);
2113 }
2116 static void
2117 update_view_title(struct view *view)
2118 {
2119 char buf[SIZEOF_STR];
2120 char state[SIZEOF_STR];
2121 size_t bufpos = 0, statelen = 0;
2123 assert(view_is_displayed(view));
2125 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2126 unsigned int view_lines = view->offset + view->height;
2127 unsigned int lines = view->lines
2128 ? MIN(view_lines, view->lines) * 100 / view->lines
2129 : 0;
2131 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2132 view->ops->type,
2133 view->lineno + 1,
2134 view->lines,
2135 lines);
2137 }
2139 if (view->pipe) {
2140 time_t secs = time(NULL) - view->start_time;
2142 /* Three git seconds are a long time ... */
2143 if (secs > 2)
2144 string_format_from(state, &statelen, " loading %lds", secs);
2145 }
2147 string_format_from(buf, &bufpos, "[%s]", view->name);
2148 if (*view->ref && bufpos < view->width) {
2149 size_t refsize = strlen(view->ref);
2150 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2152 if (minsize < view->width)
2153 refsize = view->width - minsize + 7;
2154 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2155 }
2157 if (statelen && bufpos < view->width) {
2158 string_format_from(buf, &bufpos, "%s", state);
2159 }
2161 if (view == display[current_view])
2162 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2163 else
2164 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2166 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2167 wclrtoeol(view->title);
2168 wnoutrefresh(view->title);
2169 }
2171 static void
2172 resize_display(void)
2173 {
2174 int offset, i;
2175 struct view *base = display[0];
2176 struct view *view = display[1] ? display[1] : display[0];
2178 /* Setup window dimensions */
2180 getmaxyx(stdscr, base->height, base->width);
2182 /* Make room for the status window. */
2183 base->height -= 1;
2185 if (view != base) {
2186 /* Horizontal split. */
2187 view->width = base->width;
2188 view->height = SCALE_SPLIT_VIEW(base->height);
2189 base->height -= view->height;
2191 /* Make room for the title bar. */
2192 view->height -= 1;
2193 }
2195 /* Make room for the title bar. */
2196 base->height -= 1;
2198 offset = 0;
2200 foreach_displayed_view (view, i) {
2201 if (!view->win) {
2202 view->win = newwin(view->height, 0, offset, 0);
2203 if (!view->win)
2204 die("Failed to create %s view", view->name);
2206 scrollok(view->win, FALSE);
2208 view->title = newwin(1, 0, offset + view->height, 0);
2209 if (!view->title)
2210 die("Failed to create title window");
2212 } else {
2213 wresize(view->win, view->height, view->width);
2214 mvwin(view->win, offset, 0);
2215 mvwin(view->title, offset + view->height, 0);
2216 }
2218 offset += view->height + 1;
2219 }
2220 }
2222 static void
2223 redraw_display(bool clear)
2224 {
2225 struct view *view;
2226 int i;
2228 foreach_displayed_view (view, i) {
2229 if (clear)
2230 wclear(view->win);
2231 redraw_view(view);
2232 update_view_title(view);
2233 }
2234 }
2236 static void
2237 toggle_view_option(bool *option, const char *help)
2238 {
2239 *option = !*option;
2240 redraw_display(FALSE);
2241 report("%sabling %s", *option ? "En" : "Dis", help);
2242 }
2244 static void
2245 maximize_view(struct view *view)
2246 {
2247 memset(display, 0, sizeof(display));
2248 current_view = 0;
2249 display[current_view] = view;
2250 resize_display();
2251 redraw_display(FALSE);
2252 report("");
2253 }
2256 /*
2257 * Navigation
2258 */
2260 static bool
2261 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2262 {
2263 if (lineno >= view->lines)
2264 lineno = view->lines > 0 ? view->lines - 1 : 0;
2266 if (offset > lineno || offset + view->height <= lineno) {
2267 unsigned long half = view->height / 2;
2269 if (lineno > half)
2270 offset = lineno - half;
2271 else
2272 offset = 0;
2273 }
2275 if (offset != view->offset || lineno != view->lineno) {
2276 view->offset = offset;
2277 view->lineno = lineno;
2278 return TRUE;
2279 }
2281 return FALSE;
2282 }
2284 /* Scrolling backend */
2285 static void
2286 do_scroll_view(struct view *view, int lines)
2287 {
2288 bool redraw_current_line = FALSE;
2290 /* The rendering expects the new offset. */
2291 view->offset += lines;
2293 assert(0 <= view->offset && view->offset < view->lines);
2294 assert(lines);
2296 /* Move current line into the view. */
2297 if (view->lineno < view->offset) {
2298 view->lineno = view->offset;
2299 redraw_current_line = TRUE;
2300 } else if (view->lineno >= view->offset + view->height) {
2301 view->lineno = view->offset + view->height - 1;
2302 redraw_current_line = TRUE;
2303 }
2305 assert(view->offset <= view->lineno && view->lineno < view->lines);
2307 /* Redraw the whole screen if scrolling is pointless. */
2308 if (view->height < ABS(lines)) {
2309 redraw_view(view);
2311 } else {
2312 int line = lines > 0 ? view->height - lines : 0;
2313 int end = line + ABS(lines);
2315 scrollok(view->win, TRUE);
2316 wscrl(view->win, lines);
2317 scrollok(view->win, FALSE);
2319 while (line < end && draw_view_line(view, line))
2320 line++;
2322 if (redraw_current_line)
2323 draw_view_line(view, view->lineno - view->offset);
2324 wnoutrefresh(view->win);
2325 }
2327 view->has_scrolled = TRUE;
2328 report("");
2329 }
2331 /* Scroll frontend */
2332 static void
2333 scroll_view(struct view *view, enum request request)
2334 {
2335 int lines = 1;
2337 assert(view_is_displayed(view));
2339 switch (request) {
2340 case REQ_SCROLL_LEFT:
2341 if (view->yoffset == 0) {
2342 report("Cannot scroll beyond the first column");
2343 return;
2344 }
2345 if (view->yoffset <= SCROLL_INTERVAL)
2346 view->yoffset = 0;
2347 else
2348 view->yoffset -= SCROLL_INTERVAL;
2349 redraw_view_from(view, 0);
2350 report("");
2351 return;
2352 case REQ_SCROLL_RIGHT:
2353 if (!view->can_hscroll) {
2354 report("Cannot scroll beyond the last column");
2355 return;
2356 }
2357 view->yoffset += SCROLL_INTERVAL;
2358 redraw_view(view);
2359 report("");
2360 return;
2361 case REQ_SCROLL_PAGE_DOWN:
2362 lines = view->height;
2363 case REQ_SCROLL_LINE_DOWN:
2364 if (view->offset + lines > view->lines)
2365 lines = view->lines - view->offset;
2367 if (lines == 0 || view->offset + view->height >= view->lines) {
2368 report("Cannot scroll beyond the last line");
2369 return;
2370 }
2371 break;
2373 case REQ_SCROLL_PAGE_UP:
2374 lines = view->height;
2375 case REQ_SCROLL_LINE_UP:
2376 if (lines > view->offset)
2377 lines = view->offset;
2379 if (lines == 0) {
2380 report("Cannot scroll beyond the first line");
2381 return;
2382 }
2384 lines = -lines;
2385 break;
2387 default:
2388 die("request %d not handled in switch", request);
2389 }
2391 do_scroll_view(view, lines);
2392 }
2394 /* Cursor moving */
2395 static void
2396 move_view(struct view *view, enum request request)
2397 {
2398 int scroll_steps = 0;
2399 int steps;
2401 switch (request) {
2402 case REQ_MOVE_FIRST_LINE:
2403 steps = -view->lineno;
2404 break;
2406 case REQ_MOVE_LAST_LINE:
2407 steps = view->lines - view->lineno - 1;
2408 break;
2410 case REQ_MOVE_PAGE_UP:
2411 steps = view->height > view->lineno
2412 ? -view->lineno : -view->height;
2413 break;
2415 case REQ_MOVE_PAGE_DOWN:
2416 steps = view->lineno + view->height >= view->lines
2417 ? view->lines - view->lineno - 1 : view->height;
2418 break;
2420 case REQ_MOVE_UP:
2421 steps = -1;
2422 break;
2424 case REQ_MOVE_DOWN:
2425 steps = 1;
2426 break;
2428 default:
2429 die("request %d not handled in switch", request);
2430 }
2432 if (steps <= 0 && view->lineno == 0) {
2433 report("Cannot move beyond the first line");
2434 return;
2436 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2437 report("Cannot move beyond the last line");
2438 return;
2439 }
2441 /* Move the current line */
2442 view->lineno += steps;
2443 assert(0 <= view->lineno && view->lineno < view->lines);
2445 /* Check whether the view needs to be scrolled */
2446 if (view->lineno < view->offset ||
2447 view->lineno >= view->offset + view->height) {
2448 scroll_steps = steps;
2449 if (steps < 0 && -steps > view->offset) {
2450 scroll_steps = -view->offset;
2452 } else if (steps > 0) {
2453 if (view->lineno == view->lines - 1 &&
2454 view->lines > view->height) {
2455 scroll_steps = view->lines - view->offset - 1;
2456 if (scroll_steps >= view->height)
2457 scroll_steps -= view->height - 1;
2458 }
2459 }
2460 }
2462 if (!view_is_displayed(view)) {
2463 view->offset += scroll_steps;
2464 assert(0 <= view->offset && view->offset < view->lines);
2465 view->ops->select(view, &view->line[view->lineno]);
2466 return;
2467 }
2469 /* Repaint the old "current" line if we be scrolling */
2470 if (ABS(steps) < view->height)
2471 draw_view_line(view, view->lineno - steps - view->offset);
2473 if (scroll_steps) {
2474 do_scroll_view(view, scroll_steps);
2475 return;
2476 }
2478 /* Draw the current line */
2479 draw_view_line(view, view->lineno - view->offset);
2481 wnoutrefresh(view->win);
2482 report("");
2483 }
2486 /*
2487 * Searching
2488 */
2490 static void search_view(struct view *view, enum request request);
2492 static void
2493 select_view_line(struct view *view, unsigned long lineno)
2494 {
2495 unsigned long old_lineno = view->lineno;
2496 unsigned long old_offset = view->offset;
2498 if (goto_view_line(view, view->offset, lineno)) {
2499 if (view_is_displayed(view)) {
2500 if (old_offset != view->offset) {
2501 redraw_view(view);
2502 } else {
2503 draw_view_line(view, old_lineno - view->offset);
2504 draw_view_line(view, view->lineno - view->offset);
2505 wnoutrefresh(view->win);
2506 }
2507 } else {
2508 view->ops->select(view, &view->line[view->lineno]);
2509 }
2510 }
2511 }
2513 static void
2514 find_next(struct view *view, enum request request)
2515 {
2516 unsigned long lineno = view->lineno;
2517 int direction;
2519 if (!*view->grep) {
2520 if (!*opt_search)
2521 report("No previous search");
2522 else
2523 search_view(view, request);
2524 return;
2525 }
2527 switch (request) {
2528 case REQ_SEARCH:
2529 case REQ_FIND_NEXT:
2530 direction = 1;
2531 break;
2533 case REQ_SEARCH_BACK:
2534 case REQ_FIND_PREV:
2535 direction = -1;
2536 break;
2538 default:
2539 return;
2540 }
2542 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2543 lineno += direction;
2545 /* Note, lineno is unsigned long so will wrap around in which case it
2546 * will become bigger than view->lines. */
2547 for (; lineno < view->lines; lineno += direction) {
2548 if (view->ops->grep(view, &view->line[lineno])) {
2549 select_view_line(view, lineno);
2550 report("Line %ld matches '%s'", lineno + 1, view->grep);
2551 return;
2552 }
2553 }
2555 report("No match found for '%s'", view->grep);
2556 }
2558 static void
2559 search_view(struct view *view, enum request request)
2560 {
2561 int regex_err;
2563 if (view->regex) {
2564 regfree(view->regex);
2565 *view->grep = 0;
2566 } else {
2567 view->regex = calloc(1, sizeof(*view->regex));
2568 if (!view->regex)
2569 return;
2570 }
2572 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2573 if (regex_err != 0) {
2574 char buf[SIZEOF_STR] = "unknown error";
2576 regerror(regex_err, view->regex, buf, sizeof(buf));
2577 report("Search failed: %s", buf);
2578 return;
2579 }
2581 string_copy(view->grep, opt_search);
2583 find_next(view, request);
2584 }
2586 /*
2587 * Incremental updating
2588 */
2590 static void
2591 reset_view(struct view *view)
2592 {
2593 int i;
2595 for (i = 0; i < view->lines; i++)
2596 free(view->line[i].data);
2597 free(view->line);
2599 view->p_offset = view->offset;
2600 view->p_yoffset = view->yoffset;
2601 view->p_lineno = view->lineno;
2603 view->line = NULL;
2604 view->offset = 0;
2605 view->yoffset = 0;
2606 view->lines = 0;
2607 view->lineno = 0;
2608 view->line_alloc = 0;
2609 view->vid[0] = 0;
2610 view->update_secs = 0;
2611 }
2613 static void
2614 free_argv(const char *argv[])
2615 {
2616 int argc;
2618 for (argc = 0; argv[argc]; argc++)
2619 free((void *) argv[argc]);
2620 }
2622 static bool
2623 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2624 {
2625 char buf[SIZEOF_STR];
2626 int argc;
2627 bool noreplace = flags == FORMAT_NONE;
2629 free_argv(dst_argv);
2631 for (argc = 0; src_argv[argc]; argc++) {
2632 const char *arg = src_argv[argc];
2633 size_t bufpos = 0;
2635 while (arg) {
2636 char *next = strstr(arg, "%(");
2637 int len = next - arg;
2638 const char *value;
2640 if (!next || noreplace) {
2641 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2642 noreplace = TRUE;
2643 len = strlen(arg);
2644 value = "";
2646 } else if (!prefixcmp(next, "%(directory)")) {
2647 value = opt_path;
2649 } else if (!prefixcmp(next, "%(file)")) {
2650 value = opt_file;
2652 } else if (!prefixcmp(next, "%(ref)")) {
2653 value = *opt_ref ? opt_ref : "HEAD";
2655 } else if (!prefixcmp(next, "%(head)")) {
2656 value = ref_head;
2658 } else if (!prefixcmp(next, "%(commit)")) {
2659 value = ref_commit;
2661 } else if (!prefixcmp(next, "%(blob)")) {
2662 value = ref_blob;
2664 } else {
2665 report("Unknown replacement: `%s`", next);
2666 return FALSE;
2667 }
2669 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2670 return FALSE;
2672 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2673 }
2675 dst_argv[argc] = strdup(buf);
2676 if (!dst_argv[argc])
2677 break;
2678 }
2680 dst_argv[argc] = NULL;
2682 return src_argv[argc] == NULL;
2683 }
2685 static bool
2686 restore_view_position(struct view *view)
2687 {
2688 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2689 return FALSE;
2691 /* Changing the view position cancels the restoring. */
2692 /* FIXME: Changing back to the first line is not detected. */
2693 if (view->offset != 0 || view->lineno != 0) {
2694 view->p_restore = FALSE;
2695 return FALSE;
2696 }
2698 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2699 view_is_displayed(view))
2700 werase(view->win);
2702 view->yoffset = view->p_yoffset;
2703 view->p_restore = FALSE;
2705 return TRUE;
2706 }
2708 static void
2709 end_update(struct view *view, bool force)
2710 {
2711 if (!view->pipe)
2712 return;
2713 while (!view->ops->read(view, NULL))
2714 if (!force)
2715 return;
2716 set_nonblocking_input(FALSE);
2717 if (force)
2718 kill_io(view->pipe);
2719 done_io(view->pipe);
2720 view->pipe = NULL;
2721 }
2723 static void
2724 setup_update(struct view *view, const char *vid)
2725 {
2726 set_nonblocking_input(TRUE);
2727 reset_view(view);
2728 string_copy_rev(view->vid, vid);
2729 view->pipe = &view->io;
2730 view->start_time = time(NULL);
2731 }
2733 static bool
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735 enum format_flags flags)
2736 {
2737 if (view->pipe)
2738 end_update(view, TRUE);
2739 return init_io_rd(&view->io, argv, dir, flags);
2740 }
2742 static bool
2743 prepare_update_file(struct view *view, const char *name)
2744 {
2745 if (view->pipe)
2746 end_update(view, TRUE);
2747 return io_open(&view->io, name);
2748 }
2750 static bool
2751 begin_update(struct view *view, bool refresh)
2752 {
2753 if (view->pipe)
2754 end_update(view, TRUE);
2756 if (refresh) {
2757 if (!start_io(&view->io))
2758 return FALSE;
2760 } else {
2761 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2762 opt_path[0] = 0;
2764 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2765 return FALSE;
2767 /* Put the current ref_* value to the view title ref
2768 * member. This is needed by the blob view. Most other
2769 * views sets it automatically after loading because the
2770 * first line is a commit line. */
2771 string_copy_rev(view->ref, view->id);
2772 }
2774 setup_update(view, view->id);
2776 return TRUE;
2777 }
2779 #define ITEM_CHUNK_SIZE 256
2780 static void *
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2782 {
2783 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786 if (mem == NULL || num_chunks != num_chunks_new) {
2787 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788 mem = realloc(mem, *size * item_size);
2789 }
2791 return mem;
2792 }
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2796 {
2797 size_t alloc = view->line_alloc;
2798 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799 sizeof(*view->line));
2801 if (!tmp)
2802 return NULL;
2804 view->line = tmp;
2805 view->line_alloc = alloc;
2806 return view->line;
2807 }
2809 static bool
2810 update_view(struct view *view)
2811 {
2812 char out_buffer[BUFSIZ * 2];
2813 char *line;
2814 /* Clear the view and redraw everything since the tree sorting
2815 * might have rearranged things. */
2816 bool redraw = view->lines == 0;
2817 bool can_read = TRUE;
2819 if (!view->pipe)
2820 return TRUE;
2822 if (!io_can_read(view->pipe)) {
2823 if (view->lines == 0) {
2824 time_t secs = time(NULL) - view->start_time;
2826 if (secs > 1 && secs > view->update_secs) {
2827 if (view->update_secs == 0)
2828 redraw_view(view);
2829 update_view_title(view);
2830 view->update_secs = secs;
2831 }
2832 }
2833 return TRUE;
2834 }
2836 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837 if (opt_iconv != ICONV_NONE) {
2838 ICONV_CONST char *inbuf = line;
2839 size_t inlen = strlen(line) + 1;
2841 char *outbuf = out_buffer;
2842 size_t outlen = sizeof(out_buffer);
2844 size_t ret;
2846 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847 if (ret != (size_t) -1)
2848 line = out_buffer;
2849 }
2851 if (!view->ops->read(view, line)) {
2852 report("Allocation failure");
2853 end_update(view, TRUE);
2854 return FALSE;
2855 }
2856 }
2858 {
2859 unsigned long lines = view->lines;
2860 int digits;
2862 for (digits = 0; lines; digits++)
2863 lines /= 10;
2865 /* Keep the displayed view in sync with line number scaling. */
2866 if (digits != view->digits) {
2867 view->digits = digits;
2868 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2869 redraw = TRUE;
2870 }
2871 }
2873 if (io_error(view->pipe)) {
2874 report("Failed to read: %s", io_strerror(view->pipe));
2875 end_update(view, TRUE);
2877 } else if (io_eof(view->pipe)) {
2878 report("");
2879 end_update(view, FALSE);
2880 }
2882 if (restore_view_position(view))
2883 redraw = TRUE;
2885 if (!view_is_displayed(view))
2886 return TRUE;
2888 if (redraw)
2889 redraw_view_from(view, 0);
2890 else
2891 redraw_view_dirty(view);
2893 /* Update the title _after_ the redraw so that if the redraw picks up a
2894 * commit reference in view->ref it'll be available here. */
2895 update_view_title(view);
2896 return TRUE;
2897 }
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2901 {
2902 struct line *line;
2904 if (!realloc_lines(view, view->lines + 1))
2905 return NULL;
2907 line = &view->line[view->lines++];
2908 memset(line, 0, sizeof(*line));
2909 line->type = type;
2910 line->data = data;
2911 line->dirty = 1;
2913 return line;
2914 }
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2918 {
2919 char *data = text ? strdup(text) : NULL;
2921 return data ? add_line_data(view, data, type) : NULL;
2922 }
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2926 {
2927 char buf[SIZEOF_STR];
2928 va_list args;
2930 va_start(args, fmt);
2931 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2932 buf[0] = 0;
2933 va_end(args);
2935 return buf[0] ? add_line_text(view, buf, type) : NULL;
2936 }
2938 /*
2939 * View opening
2940 */
2942 enum open_flags {
2943 OPEN_DEFAULT = 0, /* Use default view switching. */
2944 OPEN_SPLIT = 1, /* Split current view. */
2945 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2946 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2947 OPEN_PREPARED = 32, /* Open already prepared command. */
2948 };
2950 static void
2951 open_view(struct view *prev, enum request request, enum open_flags flags)
2952 {
2953 bool split = !!(flags & OPEN_SPLIT);
2954 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2955 bool nomaximize = !!(flags & OPEN_REFRESH);
2956 struct view *view = VIEW(request);
2957 int nviews = displayed_views();
2958 struct view *base_view = display[0];
2960 if (view == prev && nviews == 1 && !reload) {
2961 report("Already in %s view", view->name);
2962 return;
2963 }
2965 if (view->git_dir && !opt_git_dir[0]) {
2966 report("The %s view is disabled in pager view", view->name);
2967 return;
2968 }
2970 if (split) {
2971 display[1] = view;
2972 current_view = 1;
2973 } else if (!nomaximize) {
2974 /* Maximize the current view. */
2975 memset(display, 0, sizeof(display));
2976 current_view = 0;
2977 display[current_view] = view;
2978 }
2980 /* Resize the view when switching between split- and full-screen,
2981 * or when switching between two different full-screen views. */
2982 if (nviews != displayed_views() ||
2983 (nviews == 1 && base_view != display[0]))
2984 resize_display();
2986 if (view->ops->open) {
2987 if (view->pipe)
2988 end_update(view, TRUE);
2989 if (!view->ops->open(view)) {
2990 report("Failed to load %s view", view->name);
2991 return;
2992 }
2993 restore_view_position(view);
2995 } else if ((reload || strcmp(view->vid, view->id)) &&
2996 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2997 report("Failed to load %s view", view->name);
2998 return;
2999 }
3001 if (split && prev->lineno - prev->offset >= prev->height) {
3002 /* Take the title line into account. */
3003 int lines = prev->lineno - prev->offset - prev->height + 1;
3005 /* Scroll the view that was split if the current line is
3006 * outside the new limited view. */
3007 do_scroll_view(prev, lines);
3008 }
3010 if (prev && view != prev) {
3011 if (split) {
3012 /* "Blur" the previous view. */
3013 update_view_title(prev);
3014 }
3016 view->parent = prev;
3017 }
3019 if (view->pipe && view->lines == 0) {
3020 /* Clear the old view and let the incremental updating refill
3021 * the screen. */
3022 werase(view->win);
3023 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3024 report("");
3025 } else if (view_is_displayed(view)) {
3026 redraw_view(view);
3027 report("");
3028 }
3029 }
3031 static void
3032 open_external_viewer(const char *argv[], const char *dir)
3033 {
3034 def_prog_mode(); /* save current tty modes */
3035 endwin(); /* restore original tty modes */
3036 run_io_fg(argv, dir);
3037 fprintf(stderr, "Press Enter to continue");
3038 getc(opt_tty);
3039 reset_prog_mode();
3040 redraw_display(TRUE);
3041 }
3043 static void
3044 open_mergetool(const char *file)
3045 {
3046 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3048 open_external_viewer(mergetool_argv, opt_cdup);
3049 }
3051 static void
3052 open_editor(bool from_root, const char *file)
3053 {
3054 const char *editor_argv[] = { "vi", file, NULL };
3055 const char *editor;
3057 editor = getenv("GIT_EDITOR");
3058 if (!editor && *opt_editor)
3059 editor = opt_editor;
3060 if (!editor)
3061 editor = getenv("VISUAL");
3062 if (!editor)
3063 editor = getenv("EDITOR");
3064 if (!editor)
3065 editor = "vi";
3067 editor_argv[0] = editor;
3068 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3069 }
3071 static void
3072 open_run_request(enum request request)
3073 {
3074 struct run_request *req = get_run_request(request);
3075 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3077 if (!req) {
3078 report("Unknown run request");
3079 return;
3080 }
3082 if (format_argv(argv, req->argv, FORMAT_ALL))
3083 open_external_viewer(argv, NULL);
3084 free_argv(argv);
3085 }
3087 /*
3088 * User request switch noodle
3089 */
3091 static int
3092 view_driver(struct view *view, enum request request)
3093 {
3094 int i;
3096 if (request == REQ_NONE) {
3097 doupdate();
3098 return TRUE;
3099 }
3101 if (request > REQ_NONE) {
3102 open_run_request(request);
3103 /* FIXME: When all views can refresh always do this. */
3104 if (view == VIEW(REQ_VIEW_STATUS) ||
3105 view == VIEW(REQ_VIEW_MAIN) ||
3106 view == VIEW(REQ_VIEW_LOG) ||
3107 view == VIEW(REQ_VIEW_STAGE))
3108 request = REQ_REFRESH;
3109 else
3110 return TRUE;
3111 }
3113 if (view && view->lines) {
3114 request = view->ops->request(view, request, &view->line[view->lineno]);
3115 if (request == REQ_NONE)
3116 return TRUE;
3117 }
3119 switch (request) {
3120 case REQ_MOVE_UP:
3121 case REQ_MOVE_DOWN:
3122 case REQ_MOVE_PAGE_UP:
3123 case REQ_MOVE_PAGE_DOWN:
3124 case REQ_MOVE_FIRST_LINE:
3125 case REQ_MOVE_LAST_LINE:
3126 move_view(view, request);
3127 break;
3129 case REQ_SCROLL_LEFT:
3130 case REQ_SCROLL_RIGHT:
3131 case REQ_SCROLL_LINE_DOWN:
3132 case REQ_SCROLL_LINE_UP:
3133 case REQ_SCROLL_PAGE_DOWN:
3134 case REQ_SCROLL_PAGE_UP:
3135 scroll_view(view, request);
3136 break;
3138 case REQ_VIEW_BLAME:
3139 if (!opt_file[0]) {
3140 report("No file chosen, press %s to open tree view",
3141 get_key(REQ_VIEW_TREE));
3142 break;
3143 }
3144 open_view(view, request, OPEN_DEFAULT);
3145 break;
3147 case REQ_VIEW_BLOB:
3148 if (!ref_blob[0]) {
3149 report("No file chosen, press %s to open tree view",
3150 get_key(REQ_VIEW_TREE));
3151 break;
3152 }
3153 open_view(view, request, OPEN_DEFAULT);
3154 break;
3156 case REQ_VIEW_PAGER:
3157 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3158 report("No pager content, press %s to run command from prompt",
3159 get_key(REQ_PROMPT));
3160 break;
3161 }
3162 open_view(view, request, OPEN_DEFAULT);
3163 break;
3165 case REQ_VIEW_STAGE:
3166 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3167 report("No stage content, press %s to open the status view and choose file",
3168 get_key(REQ_VIEW_STATUS));
3169 break;
3170 }
3171 open_view(view, request, OPEN_DEFAULT);
3172 break;
3174 case REQ_VIEW_STATUS:
3175 if (opt_is_inside_work_tree == FALSE) {
3176 report("The status view requires a working tree");
3177 break;
3178 }
3179 open_view(view, request, OPEN_DEFAULT);
3180 break;
3182 case REQ_VIEW_MAIN:
3183 case REQ_VIEW_DIFF:
3184 case REQ_VIEW_LOG:
3185 case REQ_VIEW_TREE:
3186 case REQ_VIEW_HELP:
3187 open_view(view, request, OPEN_DEFAULT);
3188 break;
3190 case REQ_NEXT:
3191 case REQ_PREVIOUS:
3192 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3194 if ((view == VIEW(REQ_VIEW_DIFF) &&
3195 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3196 (view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3198 (view == VIEW(REQ_VIEW_STAGE) &&
3199 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3200 (view == VIEW(REQ_VIEW_BLOB) &&
3201 view->parent == VIEW(REQ_VIEW_TREE))) {
3202 int line;
3204 view = view->parent;
3205 line = view->lineno;
3206 move_view(view, request);
3207 if (view_is_displayed(view))
3208 update_view_title(view);
3209 if (line != view->lineno)
3210 view->ops->request(view, REQ_ENTER,
3211 &view->line[view->lineno]);
3213 } else {
3214 move_view(view, request);
3215 }
3216 break;
3218 case REQ_VIEW_NEXT:
3219 {
3220 int nviews = displayed_views();
3221 int next_view = (current_view + 1) % nviews;
3223 if (next_view == current_view) {
3224 report("Only one view is displayed");
3225 break;
3226 }
3228 current_view = next_view;
3229 /* Blur out the title of the previous view. */
3230 update_view_title(view);
3231 report("");
3232 break;
3233 }
3234 case REQ_REFRESH:
3235 report("Refreshing is not yet supported for the %s view", view->name);
3236 break;
3238 case REQ_MAXIMIZE:
3239 if (displayed_views() == 2)
3240 maximize_view(view);
3241 break;
3243 case REQ_TOGGLE_LINENO:
3244 toggle_view_option(&opt_line_number, "line numbers");
3245 break;
3247 case REQ_TOGGLE_DATE:
3248 toggle_view_option(&opt_date, "date display");
3249 break;
3251 case REQ_TOGGLE_AUTHOR:
3252 toggle_view_option(&opt_author, "author display");
3253 break;
3255 case REQ_TOGGLE_REV_GRAPH:
3256 toggle_view_option(&opt_rev_graph, "revision graph display");
3257 break;
3259 case REQ_TOGGLE_REFS:
3260 toggle_view_option(&opt_show_refs, "reference display");
3261 break;
3263 case REQ_SEARCH:
3264 case REQ_SEARCH_BACK:
3265 search_view(view, request);
3266 break;
3268 case REQ_FIND_NEXT:
3269 case REQ_FIND_PREV:
3270 find_next(view, request);
3271 break;
3273 case REQ_STOP_LOADING:
3274 for (i = 0; i < ARRAY_SIZE(views); i++) {
3275 view = &views[i];
3276 if (view->pipe)
3277 report("Stopped loading the %s view", view->name),
3278 end_update(view, TRUE);
3279 }
3280 break;
3282 case REQ_SHOW_VERSION:
3283 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3284 return TRUE;
3286 case REQ_SCREEN_REDRAW:
3287 redraw_display(TRUE);
3288 break;
3290 case REQ_EDIT:
3291 report("Nothing to edit");
3292 break;
3294 case REQ_ENTER:
3295 report("Nothing to enter");
3296 break;
3298 case REQ_VIEW_CLOSE:
3299 /* XXX: Mark closed views by letting view->parent point to the
3300 * view itself. Parents to closed view should never be
3301 * followed. */
3302 if (view->parent &&
3303 view->parent->parent != view->parent) {
3304 maximize_view(view->parent);
3305 view->parent = view;
3306 break;
3307 }
3308 /* Fall-through */
3309 case REQ_QUIT:
3310 return FALSE;
3312 default:
3313 report("Unknown key, press 'h' for help");
3314 return TRUE;
3315 }
3317 return TRUE;
3318 }
3321 /*
3322 * View backend utilities
3323 */
3325 static void
3326 parse_timezone(time_t *time, const char *zone)
3327 {
3328 long tz;
3330 tz = ('0' - zone[1]) * 60 * 60 * 10;
3331 tz += ('0' - zone[2]) * 60 * 60;
3332 tz += ('0' - zone[3]) * 60;
3333 tz += ('0' - zone[4]);
3335 if (zone[0] == '-')
3336 tz = -tz;
3338 *time -= tz;
3339 }
3341 /* Parse author lines where the name may be empty:
3342 * author <email@address.tld> 1138474660 +0100
3343 */
3344 static void
3345 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3346 {
3347 char *nameend = strchr(ident, '<');
3348 char *emailend = strchr(ident, '>');
3350 if (nameend && emailend)
3351 *nameend = *emailend = 0;
3352 ident = chomp_string(ident);
3353 if (!*ident) {
3354 if (nameend)
3355 ident = chomp_string(nameend + 1);
3356 if (!*ident)
3357 ident = "Unknown";
3358 }
3360 string_ncopy_do(author, authorsize, ident, strlen(ident));
3362 /* Parse epoch and timezone */
3363 if (emailend && emailend[1] == ' ') {
3364 char *secs = emailend + 2;
3365 char *zone = strchr(secs, ' ');
3366 time_t time = (time_t) atol(secs);
3368 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3369 parse_timezone(&time, zone + 1);
3371 gmtime_r(&time, tm);
3372 }
3373 }
3375 static enum input_status
3376 select_commit_parent_handler(void *data, char *buf, int c)
3377 {
3378 size_t parents = *(size_t *) data;
3379 int parent = 0;
3381 if (!isdigit(c))
3382 return INPUT_SKIP;
3384 if (*buf)
3385 parent = atoi(buf) * 10;
3386 parent += c - '0';
3388 if (parent > parents)
3389 return INPUT_SKIP;
3390 return INPUT_OK;
3391 }
3393 static bool
3394 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3395 {
3396 char buf[SIZEOF_STR * 4];
3397 const char *revlist_argv[] = {
3398 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3399 };
3400 int parents;
3402 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3403 !*chomp_string(buf) ||
3404 (parents = (strlen(buf) / 40) - 1) < 0) {
3405 report("Failed to get parent information");
3406 return FALSE;
3408 } else if (parents == 0) {
3409 if (path)
3410 report("Path '%s' does not exist in the parent", path);
3411 else
3412 report("The selected commit has no parents");
3413 return FALSE;
3414 }
3416 if (parents > 1) {
3417 char prompt[SIZEOF_STR];
3418 char *result;
3420 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3421 return FALSE;
3422 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3423 if (!result)
3424 return FALSE;
3425 parents = atoi(result);
3426 }
3428 string_copy_rev(rev, &buf[41 * parents]);
3429 return TRUE;
3430 }
3432 /*
3433 * Pager backend
3434 */
3436 static bool
3437 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3438 {
3439 char text[SIZEOF_STR];
3441 if (opt_line_number && draw_lineno(view, lineno))
3442 return TRUE;
3444 string_expand(text, sizeof(text), line->data, opt_tab_size);
3445 draw_text(view, line->type, text, TRUE);
3446 return TRUE;
3447 }
3449 static bool
3450 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3451 {
3452 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3453 char refbuf[SIZEOF_STR];
3454 char *ref = NULL;
3456 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3457 ref = chomp_string(refbuf);
3459 if (!ref || !*ref)
3460 return TRUE;
3462 /* This is the only fatal call, since it can "corrupt" the buffer. */
3463 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3464 return FALSE;
3466 return TRUE;
3467 }
3469 static void
3470 add_pager_refs(struct view *view, struct line *line)
3471 {
3472 char buf[SIZEOF_STR];
3473 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3474 struct ref **refs;
3475 size_t bufpos = 0, refpos = 0;
3476 const char *sep = "Refs: ";
3477 bool is_tag = FALSE;
3479 assert(line->type == LINE_COMMIT);
3481 refs = get_refs(commit_id);
3482 if (!refs) {
3483 if (view == VIEW(REQ_VIEW_DIFF))
3484 goto try_add_describe_ref;
3485 return;
3486 }
3488 do {
3489 struct ref *ref = refs[refpos];
3490 const char *fmt = ref->tag ? "%s[%s]" :
3491 ref->remote ? "%s<%s>" : "%s%s";
3493 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3494 return;
3495 sep = ", ";
3496 if (ref->tag)
3497 is_tag = TRUE;
3498 } while (refs[refpos++]->next);
3500 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3501 try_add_describe_ref:
3502 /* Add <tag>-g<commit_id> "fake" reference. */
3503 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3504 return;
3505 }
3507 if (bufpos == 0)
3508 return;
3510 add_line_text(view, buf, LINE_PP_REFS);
3511 }
3513 static bool
3514 pager_read(struct view *view, char *data)
3515 {
3516 struct line *line;
3518 if (!data)
3519 return TRUE;
3521 line = add_line_text(view, data, get_line_type(data));
3522 if (!line)
3523 return FALSE;
3525 if (line->type == LINE_COMMIT &&
3526 (view == VIEW(REQ_VIEW_DIFF) ||
3527 view == VIEW(REQ_VIEW_LOG)))
3528 add_pager_refs(view, line);
3530 return TRUE;
3531 }
3533 static enum request
3534 pager_request(struct view *view, enum request request, struct line *line)
3535 {
3536 int split = 0;
3538 if (request != REQ_ENTER)
3539 return request;
3541 if (line->type == LINE_COMMIT &&
3542 (view == VIEW(REQ_VIEW_LOG) ||
3543 view == VIEW(REQ_VIEW_PAGER))) {
3544 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3545 split = 1;
3546 }
3548 /* Always scroll the view even if it was split. That way
3549 * you can use Enter to scroll through the log view and
3550 * split open each commit diff. */
3551 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3553 /* FIXME: A minor workaround. Scrolling the view will call report("")
3554 * but if we are scrolling a non-current view this won't properly
3555 * update the view title. */
3556 if (split)
3557 update_view_title(view);
3559 return REQ_NONE;
3560 }
3562 static bool
3563 pager_grep(struct view *view, struct line *line)
3564 {
3565 regmatch_t pmatch;
3566 char *text = line->data;
3568 if (!*text)
3569 return FALSE;
3571 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3572 return FALSE;
3574 return TRUE;
3575 }
3577 static void
3578 pager_select(struct view *view, struct line *line)
3579 {
3580 if (line->type == LINE_COMMIT) {
3581 char *text = (char *)line->data + STRING_SIZE("commit ");
3583 if (view != VIEW(REQ_VIEW_PAGER))
3584 string_copy_rev(view->ref, text);
3585 string_copy_rev(ref_commit, text);
3586 }
3587 }
3589 static struct view_ops pager_ops = {
3590 "line",
3591 NULL,
3592 NULL,
3593 pager_read,
3594 pager_draw,
3595 pager_request,
3596 pager_grep,
3597 pager_select,
3598 };
3600 static const char *log_argv[SIZEOF_ARG] = {
3601 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3602 };
3604 static enum request
3605 log_request(struct view *view, enum request request, struct line *line)
3606 {
3607 switch (request) {
3608 case REQ_REFRESH:
3609 load_refs();
3610 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3611 return REQ_NONE;
3612 default:
3613 return pager_request(view, request, line);
3614 }
3615 }
3617 static struct view_ops log_ops = {
3618 "line",
3619 log_argv,
3620 NULL,
3621 pager_read,
3622 pager_draw,
3623 log_request,
3624 pager_grep,
3625 pager_select,
3626 };
3628 static const char *diff_argv[SIZEOF_ARG] = {
3629 "git", "show", "--pretty=fuller", "--no-color", "--root",
3630 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3631 };
3633 static struct view_ops diff_ops = {
3634 "line",
3635 diff_argv,
3636 NULL,
3637 pager_read,
3638 pager_draw,
3639 pager_request,
3640 pager_grep,
3641 pager_select,
3642 };
3644 /*
3645 * Help backend
3646 */
3648 static bool
3649 help_open(struct view *view)
3650 {
3651 char buf[SIZEOF_STR];
3652 size_t bufpos;
3653 int i;
3655 if (view->lines > 0)
3656 return TRUE;
3658 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3660 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3661 const char *key;
3663 if (req_info[i].request == REQ_NONE)
3664 continue;
3666 if (!req_info[i].request) {
3667 add_line_text(view, "", LINE_DEFAULT);
3668 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3669 continue;
3670 }
3672 key = get_key(req_info[i].request);
3673 if (!*key)
3674 key = "(no key defined)";
3676 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3677 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3678 if (buf[bufpos] == '_')
3679 buf[bufpos] = '-';
3680 }
3682 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3683 key, buf, req_info[i].help);
3684 }
3686 if (run_requests) {
3687 add_line_text(view, "", LINE_DEFAULT);
3688 add_line_text(view, "External commands:", LINE_DEFAULT);
3689 }
3691 for (i = 0; i < run_requests; i++) {
3692 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3693 const char *key;
3694 int argc;
3696 if (!req)
3697 continue;
3699 key = get_key_name(req->key);
3700 if (!*key)
3701 key = "(no key defined)";
3703 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3704 if (!string_format_from(buf, &bufpos, "%s%s",
3705 argc ? " " : "", req->argv[argc]))
3706 return REQ_NONE;
3708 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3709 keymap_table[req->keymap].name, key, buf);
3710 }
3712 return TRUE;
3713 }
3715 static struct view_ops help_ops = {
3716 "line",
3717 NULL,
3718 help_open,
3719 NULL,
3720 pager_draw,
3721 pager_request,
3722 pager_grep,
3723 pager_select,
3724 };
3727 /*
3728 * Tree backend
3729 */
3731 struct tree_stack_entry {
3732 struct tree_stack_entry *prev; /* Entry below this in the stack */
3733 unsigned long lineno; /* Line number to restore */
3734 char *name; /* Position of name in opt_path */
3735 };
3737 /* The top of the path stack. */
3738 static struct tree_stack_entry *tree_stack = NULL;
3739 unsigned long tree_lineno = 0;
3741 static void
3742 pop_tree_stack_entry(void)
3743 {
3744 struct tree_stack_entry *entry = tree_stack;
3746 tree_lineno = entry->lineno;
3747 entry->name[0] = 0;
3748 tree_stack = entry->prev;
3749 free(entry);
3750 }
3752 static void
3753 push_tree_stack_entry(const char *name, unsigned long lineno)
3754 {
3755 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3756 size_t pathlen = strlen(opt_path);
3758 if (!entry)
3759 return;
3761 entry->prev = tree_stack;
3762 entry->name = opt_path + pathlen;
3763 tree_stack = entry;
3765 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3766 pop_tree_stack_entry();
3767 return;
3768 }
3770 /* Move the current line to the first tree entry. */
3771 tree_lineno = 1;
3772 entry->lineno = lineno;
3773 }
3775 /* Parse output from git-ls-tree(1):
3776 *
3777 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3778 */
3780 #define SIZEOF_TREE_ATTR \
3781 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3783 #define SIZEOF_TREE_MODE \
3784 STRING_SIZE("100644 ")
3786 #define TREE_ID_OFFSET \
3787 STRING_SIZE("100644 blob ")
3789 struct tree_entry {
3790 char id[SIZEOF_REV];
3791 mode_t mode;
3792 struct tm time; /* Date from the author ident. */
3793 char author[75]; /* Author of the commit. */
3794 char name[1];
3795 };
3797 static const char *
3798 tree_path(struct line *line)
3799 {
3800 return ((struct tree_entry *) line->data)->name;
3801 }
3804 static int
3805 tree_compare_entry(struct line *line1, struct line *line2)
3806 {
3807 if (line1->type != line2->type)
3808 return line1->type == LINE_TREE_DIR ? -1 : 1;
3809 return strcmp(tree_path(line1), tree_path(line2));
3810 }
3812 static struct line *
3813 tree_entry(struct view *view, enum line_type type, const char *path,
3814 const char *mode, const char *id)
3815 {
3816 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3817 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3819 if (!entry || !line) {
3820 free(entry);
3821 return NULL;
3822 }
3824 strncpy(entry->name, path, strlen(path));
3825 if (mode)
3826 entry->mode = strtoul(mode, NULL, 8);
3827 if (id)
3828 string_copy_rev(entry->id, id);
3830 return line;
3831 }
3833 static bool
3834 tree_read_date(struct view *view, char *text, bool *read_date)
3835 {
3836 static char author_name[SIZEOF_STR];
3837 static struct tm author_time;
3839 if (!text && *read_date) {
3840 *read_date = FALSE;
3841 return TRUE;
3843 } else if (!text) {
3844 char *path = *opt_path ? opt_path : ".";
3845 /* Find next entry to process */
3846 const char *log_file[] = {
3847 "git", "log", "--no-color", "--pretty=raw",
3848 "--cc", "--raw", view->id, "--", path, NULL
3849 };
3850 struct io io = {};
3852 if (!view->lines) {
3853 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3854 report("Tree is empty");
3855 return TRUE;
3856 }
3858 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3859 report("Failed to load tree data");
3860 return TRUE;
3861 }
3863 done_io(view->pipe);
3864 view->io = io;
3865 *read_date = TRUE;
3866 return FALSE;
3868 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3869 parse_author_line(text + STRING_SIZE("author "),
3870 author_name, sizeof(author_name), &author_time);
3872 } else if (*text == ':') {
3873 char *pos;
3874 size_t annotated = 1;
3875 size_t i;
3877 pos = strchr(text, '\t');
3878 if (!pos)
3879 return TRUE;
3880 text = pos + 1;
3881 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3882 text += strlen(opt_prefix);
3883 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3884 text += strlen(opt_path);
3885 pos = strchr(text, '/');
3886 if (pos)
3887 *pos = 0;
3889 for (i = 1; i < view->lines; i++) {
3890 struct line *line = &view->line[i];
3891 struct tree_entry *entry = line->data;
3893 annotated += !!*entry->author;
3894 if (*entry->author || strcmp(entry->name, text))
3895 continue;
3897 string_copy(entry->author, author_name);
3898 memcpy(&entry->time, &author_time, sizeof(entry->time));
3899 line->dirty = 1;
3900 break;
3901 }
3903 if (annotated == view->lines)
3904 kill_io(view->pipe);
3905 }
3906 return TRUE;
3907 }
3909 static bool
3910 tree_read(struct view *view, char *text)
3911 {
3912 static bool read_date = FALSE;
3913 struct tree_entry *data;
3914 struct line *entry, *line;
3915 enum line_type type;
3916 size_t textlen = text ? strlen(text) : 0;
3917 char *path = text + SIZEOF_TREE_ATTR;
3919 if (read_date || !text)
3920 return tree_read_date(view, text, &read_date);
3922 if (textlen <= SIZEOF_TREE_ATTR)
3923 return FALSE;
3924 if (view->lines == 0 &&
3925 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3926 return FALSE;
3928 /* Strip the path part ... */
3929 if (*opt_path) {
3930 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3931 size_t striplen = strlen(opt_path);
3933 if (pathlen > striplen)
3934 memmove(path, path + striplen,
3935 pathlen - striplen + 1);
3937 /* Insert "link" to parent directory. */
3938 if (view->lines == 1 &&
3939 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3940 return FALSE;
3941 }
3943 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3944 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3945 if (!entry)
3946 return FALSE;
3947 data = entry->data;
3949 /* Skip "Directory ..." and ".." line. */
3950 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3951 if (tree_compare_entry(line, entry) <= 0)
3952 continue;
3954 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3956 line->data = data;
3957 line->type = type;
3958 for (; line <= entry; line++)
3959 line->dirty = line->cleareol = 1;
3960 return TRUE;
3961 }
3963 if (tree_lineno > view->lineno) {
3964 view->lineno = tree_lineno;
3965 tree_lineno = 0;
3966 }
3968 return TRUE;
3969 }
3971 static bool
3972 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3973 {
3974 struct tree_entry *entry = line->data;
3976 if (line->type == LINE_TREE_HEAD) {
3977 if (draw_text(view, line->type, "Directory path /", TRUE))
3978 return TRUE;
3979 } else {
3980 if (draw_mode(view, entry->mode))
3981 return TRUE;
3983 if (opt_author && draw_author(view, entry->author))
3984 return TRUE;
3986 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3987 return TRUE;
3988 }
3989 if (draw_text(view, line->type, entry->name, TRUE))
3990 return TRUE;
3991 return TRUE;
3992 }
3994 static void
3995 open_blob_editor()
3996 {
3997 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3998 int fd = mkstemp(file);
4000 if (fd == -1)
4001 report("Failed to create temporary file");
4002 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4003 report("Failed to save blob data to file");
4004 else
4005 open_editor(FALSE, file);
4006 if (fd != -1)
4007 unlink(file);
4008 }
4010 static enum request
4011 tree_request(struct view *view, enum request request, struct line *line)
4012 {
4013 enum open_flags flags;
4015 switch (request) {
4016 case REQ_VIEW_BLAME:
4017 if (line->type != LINE_TREE_FILE) {
4018 report("Blame only supported for files");
4019 return REQ_NONE;
4020 }
4022 string_copy(opt_ref, view->vid);
4023 return request;
4025 case REQ_EDIT:
4026 if (line->type != LINE_TREE_FILE) {
4027 report("Edit only supported for files");
4028 } else if (!is_head_commit(view->vid)) {
4029 open_blob_editor();
4030 } else {
4031 open_editor(TRUE, opt_file);
4032 }
4033 return REQ_NONE;
4035 case REQ_PARENT:
4036 if (!*opt_path) {
4037 /* quit view if at top of tree */
4038 return REQ_VIEW_CLOSE;
4039 }
4040 /* fake 'cd ..' */
4041 line = &view->line[1];
4042 break;
4044 case REQ_ENTER:
4045 break;
4047 default:
4048 return request;
4049 }
4051 /* Cleanup the stack if the tree view is at a different tree. */
4052 while (!*opt_path && tree_stack)
4053 pop_tree_stack_entry();
4055 switch (line->type) {
4056 case LINE_TREE_DIR:
4057 /* Depending on whether it is a subdirectory or parent link
4058 * mangle the path buffer. */
4059 if (line == &view->line[1] && *opt_path) {
4060 pop_tree_stack_entry();
4062 } else {
4063 const char *basename = tree_path(line);
4065 push_tree_stack_entry(basename, view->lineno);
4066 }
4068 /* Trees and subtrees share the same ID, so they are not not
4069 * unique like blobs. */
4070 flags = OPEN_RELOAD;
4071 request = REQ_VIEW_TREE;
4072 break;
4074 case LINE_TREE_FILE:
4075 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4076 request = REQ_VIEW_BLOB;
4077 break;
4079 default:
4080 return REQ_NONE;
4081 }
4083 open_view(view, request, flags);
4084 if (request == REQ_VIEW_TREE)
4085 view->lineno = tree_lineno;
4087 return REQ_NONE;
4088 }
4090 static void
4091 tree_select(struct view *view, struct line *line)
4092 {
4093 struct tree_entry *entry = line->data;
4095 if (line->type == LINE_TREE_FILE) {
4096 string_copy_rev(ref_blob, entry->id);
4097 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4099 } else if (line->type != LINE_TREE_DIR) {
4100 return;
4101 }
4103 string_copy_rev(view->ref, entry->id);
4104 }
4106 static const char *tree_argv[SIZEOF_ARG] = {
4107 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4108 };
4110 static struct view_ops tree_ops = {
4111 "file",
4112 tree_argv,
4113 NULL,
4114 tree_read,
4115 tree_draw,
4116 tree_request,
4117 pager_grep,
4118 tree_select,
4119 };
4121 static bool
4122 blob_read(struct view *view, char *line)
4123 {
4124 if (!line)
4125 return TRUE;
4126 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4127 }
4129 static enum request
4130 blob_request(struct view *view, enum request request, struct line *line)
4131 {
4132 switch (request) {
4133 case REQ_EDIT:
4134 open_blob_editor();
4135 return REQ_NONE;
4136 default:
4137 return pager_request(view, request, line);
4138 }
4139 }
4141 static const char *blob_argv[SIZEOF_ARG] = {
4142 "git", "cat-file", "blob", "%(blob)", NULL
4143 };
4145 static struct view_ops blob_ops = {
4146 "line",
4147 blob_argv,
4148 NULL,
4149 blob_read,
4150 pager_draw,
4151 blob_request,
4152 pager_grep,
4153 pager_select,
4154 };
4156 /*
4157 * Blame backend
4158 *
4159 * Loading the blame view is a two phase job:
4160 *
4161 * 1. File content is read either using opt_file from the
4162 * filesystem or using git-cat-file.
4163 * 2. Then blame information is incrementally added by
4164 * reading output from git-blame.
4165 */
4167 static const char *blame_head_argv[] = {
4168 "git", "blame", "--incremental", "--", "%(file)", NULL
4169 };
4171 static const char *blame_ref_argv[] = {
4172 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4173 };
4175 static const char *blame_cat_file_argv[] = {
4176 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4177 };
4179 struct blame_commit {
4180 char id[SIZEOF_REV]; /* SHA1 ID. */
4181 char title[128]; /* First line of the commit message. */
4182 char author[75]; /* Author of the commit. */
4183 struct tm time; /* Date from the author ident. */
4184 char filename[128]; /* Name of file. */
4185 bool has_previous; /* Was a "previous" line detected. */
4186 };
4188 struct blame {
4189 struct blame_commit *commit;
4190 unsigned long lineno;
4191 char text[1];
4192 };
4194 static bool
4195 blame_open(struct view *view)
4196 {
4197 if (*opt_ref || !io_open(&view->io, opt_file)) {
4198 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4199 return FALSE;
4200 }
4202 setup_update(view, opt_file);
4203 string_format(view->ref, "%s ...", opt_file);
4205 return TRUE;
4206 }
4208 static struct blame_commit *
4209 get_blame_commit(struct view *view, const char *id)
4210 {
4211 size_t i;
4213 for (i = 0; i < view->lines; i++) {
4214 struct blame *blame = view->line[i].data;
4216 if (!blame->commit)
4217 continue;
4219 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4220 return blame->commit;
4221 }
4223 {
4224 struct blame_commit *commit = calloc(1, sizeof(*commit));
4226 if (commit)
4227 string_ncopy(commit->id, id, SIZEOF_REV);
4228 return commit;
4229 }
4230 }
4232 static bool
4233 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4234 {
4235 const char *pos = *posref;
4237 *posref = NULL;
4238 pos = strchr(pos + 1, ' ');
4239 if (!pos || !isdigit(pos[1]))
4240 return FALSE;
4241 *number = atoi(pos + 1);
4242 if (*number < min || *number > max)
4243 return FALSE;
4245 *posref = pos;
4246 return TRUE;
4247 }
4249 static struct blame_commit *
4250 parse_blame_commit(struct view *view, const char *text, int *blamed)
4251 {
4252 struct blame_commit *commit;
4253 struct blame *blame;
4254 const char *pos = text + SIZEOF_REV - 2;
4255 size_t orig_lineno = 0;
4256 size_t lineno;
4257 size_t group;
4259 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4260 return NULL;
4262 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4263 !parse_number(&pos, &lineno, 1, view->lines) ||
4264 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4265 return NULL;
4267 commit = get_blame_commit(view, text);
4268 if (!commit)
4269 return NULL;
4271 *blamed += group;
4272 while (group--) {
4273 struct line *line = &view->line[lineno + group - 1];
4275 blame = line->data;
4276 blame->commit = commit;
4277 blame->lineno = orig_lineno + group - 1;
4278 line->dirty = 1;
4279 }
4281 return commit;
4282 }
4284 static bool
4285 blame_read_file(struct view *view, const char *line, bool *read_file)
4286 {
4287 if (!line) {
4288 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4289 struct io io = {};
4291 if (view->lines == 0 && !view->parent)
4292 die("No blame exist for %s", view->vid);
4294 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4295 report("Failed to load blame data");
4296 return TRUE;
4297 }
4299 done_io(view->pipe);
4300 view->io = io;
4301 *read_file = FALSE;
4302 return FALSE;
4304 } else {
4305 size_t linelen = strlen(line);
4306 struct blame *blame = malloc(sizeof(*blame) + linelen);
4308 if (!blame)
4309 return FALSE;
4311 blame->commit = NULL;
4312 strncpy(blame->text, line, linelen);
4313 blame->text[linelen] = 0;
4314 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4315 }
4316 }
4318 static bool
4319 match_blame_header(const char *name, char **line)
4320 {
4321 size_t namelen = strlen(name);
4322 bool matched = !strncmp(name, *line, namelen);
4324 if (matched)
4325 *line += namelen;
4327 return matched;
4328 }
4330 static bool
4331 blame_read(struct view *view, char *line)
4332 {
4333 static struct blame_commit *commit = NULL;
4334 static int blamed = 0;
4335 static time_t author_time;
4336 static bool read_file = TRUE;
4338 if (read_file)
4339 return blame_read_file(view, line, &read_file);
4341 if (!line) {
4342 /* Reset all! */
4343 commit = NULL;
4344 blamed = 0;
4345 read_file = TRUE;
4346 string_format(view->ref, "%s", view->vid);
4347 if (view_is_displayed(view)) {
4348 update_view_title(view);
4349 redraw_view_from(view, 0);
4350 }
4351 return TRUE;
4352 }
4354 if (!commit) {
4355 commit = parse_blame_commit(view, line, &blamed);
4356 string_format(view->ref, "%s %2d%%", view->vid,
4357 view->lines ? blamed * 100 / view->lines : 0);
4359 } else if (match_blame_header("author ", &line)) {
4360 string_ncopy(commit->author, line, strlen(line));
4362 } else if (match_blame_header("author-time ", &line)) {
4363 author_time = (time_t) atol(line);
4365 } else if (match_blame_header("author-tz ", &line)) {
4366 parse_timezone(&author_time, line);
4367 gmtime_r(&author_time, &commit->time);
4369 } else if (match_blame_header("summary ", &line)) {
4370 string_ncopy(commit->title, line, strlen(line));
4372 } else if (match_blame_header("previous ", &line)) {
4373 commit->has_previous = TRUE;
4375 } else if (match_blame_header("filename ", &line)) {
4376 string_ncopy(commit->filename, line, strlen(line));
4377 commit = NULL;
4378 }
4380 return TRUE;
4381 }
4383 static bool
4384 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4385 {
4386 struct blame *blame = line->data;
4387 struct tm *time = NULL;
4388 const char *id = NULL, *author = NULL;
4389 char text[SIZEOF_STR];
4391 if (blame->commit && *blame->commit->filename) {
4392 id = blame->commit->id;
4393 author = blame->commit->author;
4394 time = &blame->commit->time;
4395 }
4397 if (opt_date && draw_date(view, time))
4398 return TRUE;
4400 if (opt_author && draw_author(view, author))
4401 return TRUE;
4403 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4404 return TRUE;
4406 if (draw_lineno(view, lineno))
4407 return TRUE;
4409 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4410 draw_text(view, LINE_DEFAULT, text, TRUE);
4411 return TRUE;
4412 }
4414 static bool
4415 check_blame_commit(struct blame *blame, bool check_null_id)
4416 {
4417 if (!blame->commit)
4418 report("Commit data not loaded yet");
4419 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4420 report("No commit exist for the selected line");
4421 else
4422 return TRUE;
4423 return FALSE;
4424 }
4426 static void
4427 setup_blame_parent_line(struct view *view, struct blame *blame)
4428 {
4429 const char *diff_tree_argv[] = {
4430 "git", "diff-tree", "-U0", blame->commit->id,
4431 "--", blame->commit->filename, NULL
4432 };
4433 struct io io = {};
4434 int parent_lineno = -1;
4435 int blamed_lineno = -1;
4436 char *line;
4438 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4439 return;
4441 while ((line = io_get(&io, '\n', TRUE))) {
4442 if (*line == '@') {
4443 char *pos = strchr(line, '+');
4445 parent_lineno = atoi(line + 4);
4446 if (pos)
4447 blamed_lineno = atoi(pos + 1);
4449 } else if (*line == '+' && parent_lineno != -1) {
4450 if (blame->lineno == blamed_lineno - 1 &&
4451 !strcmp(blame->text, line + 1)) {
4452 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4453 break;
4454 }
4455 blamed_lineno++;
4456 }
4457 }
4459 done_io(&io);
4460 }
4462 static enum request
4463 blame_request(struct view *view, enum request request, struct line *line)
4464 {
4465 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4466 struct blame *blame = line->data;
4468 switch (request) {
4469 case REQ_VIEW_BLAME:
4470 if (check_blame_commit(blame, TRUE)) {
4471 string_copy(opt_ref, blame->commit->id);
4472 string_copy(opt_file, blame->commit->filename);
4473 if (blame->lineno)
4474 view->lineno = blame->lineno;
4475 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4476 }
4477 break;
4479 case REQ_PARENT:
4480 if (check_blame_commit(blame, TRUE) &&
4481 select_commit_parent(blame->commit->id, opt_ref,
4482 blame->commit->filename)) {
4483 string_copy(opt_file, blame->commit->filename);
4484 setup_blame_parent_line(view, blame);
4485 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4486 }
4487 break;
4489 case REQ_ENTER:
4490 if (!check_blame_commit(blame, FALSE))
4491 break;
4493 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4494 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4495 break;
4497 if (!strcmp(blame->commit->id, NULL_ID)) {
4498 struct view *diff = VIEW(REQ_VIEW_DIFF);
4499 const char *diff_index_argv[] = {
4500 "git", "diff-index", "--root", "--patch-with-stat",
4501 "-C", "-M", "HEAD", "--", view->vid, NULL
4502 };
4504 if (!blame->commit->has_previous) {
4505 diff_index_argv[1] = "diff";
4506 diff_index_argv[2] = "--no-color";
4507 diff_index_argv[6] = "--";
4508 diff_index_argv[7] = "/dev/null";
4509 }
4511 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4512 report("Failed to allocate diff command");
4513 break;
4514 }
4515 flags |= OPEN_PREPARED;
4516 }
4518 open_view(view, REQ_VIEW_DIFF, flags);
4519 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4520 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4521 break;
4523 default:
4524 return request;
4525 }
4527 return REQ_NONE;
4528 }
4530 static bool
4531 blame_grep(struct view *view, struct line *line)
4532 {
4533 struct blame *blame = line->data;
4534 struct blame_commit *commit = blame->commit;
4535 regmatch_t pmatch;
4537 #define MATCH(text, on) \
4538 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4540 if (commit) {
4541 char buf[DATE_COLS + 1];
4543 if (MATCH(commit->title, 1) ||
4544 MATCH(commit->author, opt_author) ||
4545 MATCH(commit->id, opt_date))
4546 return TRUE;
4548 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4549 MATCH(buf, 1))
4550 return TRUE;
4551 }
4553 return MATCH(blame->text, 1);
4555 #undef MATCH
4556 }
4558 static void
4559 blame_select(struct view *view, struct line *line)
4560 {
4561 struct blame *blame = line->data;
4562 struct blame_commit *commit = blame->commit;
4564 if (!commit)
4565 return;
4567 if (!strcmp(commit->id, NULL_ID))
4568 string_ncopy(ref_commit, "HEAD", 4);
4569 else
4570 string_copy_rev(ref_commit, commit->id);
4571 }
4573 static struct view_ops blame_ops = {
4574 "line",
4575 NULL,
4576 blame_open,
4577 blame_read,
4578 blame_draw,
4579 blame_request,
4580 blame_grep,
4581 blame_select,
4582 };
4584 /*
4585 * Status backend
4586 */
4588 struct status {
4589 char status;
4590 struct {
4591 mode_t mode;
4592 char rev[SIZEOF_REV];
4593 char name[SIZEOF_STR];
4594 } old;
4595 struct {
4596 mode_t mode;
4597 char rev[SIZEOF_REV];
4598 char name[SIZEOF_STR];
4599 } new;
4600 };
4602 static char status_onbranch[SIZEOF_STR];
4603 static struct status stage_status;
4604 static enum line_type stage_line_type;
4605 static size_t stage_chunks;
4606 static int *stage_chunk;
4608 /* This should work even for the "On branch" line. */
4609 static inline bool
4610 status_has_none(struct view *view, struct line *line)
4611 {
4612 return line < view->line + view->lines && !line[1].data;
4613 }
4615 /* Get fields from the diff line:
4616 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4617 */
4618 static inline bool
4619 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4620 {
4621 const char *old_mode = buf + 1;
4622 const char *new_mode = buf + 8;
4623 const char *old_rev = buf + 15;
4624 const char *new_rev = buf + 56;
4625 const char *status = buf + 97;
4627 if (bufsize < 98 ||
4628 old_mode[-1] != ':' ||
4629 new_mode[-1] != ' ' ||
4630 old_rev[-1] != ' ' ||
4631 new_rev[-1] != ' ' ||
4632 status[-1] != ' ')
4633 return FALSE;
4635 file->status = *status;
4637 string_copy_rev(file->old.rev, old_rev);
4638 string_copy_rev(file->new.rev, new_rev);
4640 file->old.mode = strtoul(old_mode, NULL, 8);
4641 file->new.mode = strtoul(new_mode, NULL, 8);
4643 file->old.name[0] = file->new.name[0] = 0;
4645 return TRUE;
4646 }
4648 static bool
4649 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4650 {
4651 struct status *unmerged = NULL;
4652 char *buf;
4653 struct io io = {};
4655 if (!run_io(&io, argv, NULL, IO_RD))
4656 return FALSE;
4658 add_line_data(view, NULL, type);
4660 while ((buf = io_get(&io, 0, TRUE))) {
4661 struct status *file = unmerged;
4663 if (!file) {
4664 file = calloc(1, sizeof(*file));
4665 if (!file || !add_line_data(view, file, type))
4666 goto error_out;
4667 }
4669 /* Parse diff info part. */
4670 if (status) {
4671 file->status = status;
4672 if (status == 'A')
4673 string_copy(file->old.rev, NULL_ID);
4675 } else if (!file->status || file == unmerged) {
4676 if (!status_get_diff(file, buf, strlen(buf)))
4677 goto error_out;
4679 buf = io_get(&io, 0, TRUE);
4680 if (!buf)
4681 break;
4683 /* Collapse all modified entries that follow an
4684 * associated unmerged entry. */
4685 if (unmerged == file) {
4686 unmerged->status = 'U';
4687 unmerged = NULL;
4688 } else if (file->status == 'U') {
4689 unmerged = file;
4690 }
4691 }
4693 /* Grab the old name for rename/copy. */
4694 if (!*file->old.name &&
4695 (file->status == 'R' || file->status == 'C')) {
4696 string_ncopy(file->old.name, buf, strlen(buf));
4698 buf = io_get(&io, 0, TRUE);
4699 if (!buf)
4700 break;
4701 }
4703 /* git-ls-files just delivers a NUL separated list of
4704 * file names similar to the second half of the
4705 * git-diff-* output. */
4706 string_ncopy(file->new.name, buf, strlen(buf));
4707 if (!*file->old.name)
4708 string_copy(file->old.name, file->new.name);
4709 file = NULL;
4710 }
4712 if (io_error(&io)) {
4713 error_out:
4714 done_io(&io);
4715 return FALSE;
4716 }
4718 if (!view->line[view->lines - 1].data)
4719 add_line_data(view, NULL, LINE_STAT_NONE);
4721 done_io(&io);
4722 return TRUE;
4723 }
4725 /* Don't show unmerged entries in the staged section. */
4726 static const char *status_diff_index_argv[] = {
4727 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4728 "--cached", "-M", "HEAD", NULL
4729 };
4731 static const char *status_diff_files_argv[] = {
4732 "git", "diff-files", "-z", NULL
4733 };
4735 static const char *status_list_other_argv[] = {
4736 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4737 };
4739 static const char *status_list_no_head_argv[] = {
4740 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4741 };
4743 static const char *update_index_argv[] = {
4744 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4745 };
4747 /* Restore the previous line number to stay in the context or select a
4748 * line with something that can be updated. */
4749 static void
4750 status_restore(struct view *view)
4751 {
4752 if (view->p_lineno >= view->lines)
4753 view->p_lineno = view->lines - 1;
4754 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4755 view->p_lineno++;
4756 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4757 view->p_lineno--;
4759 /* If the above fails, always skip the "On branch" line. */
4760 if (view->p_lineno < view->lines)
4761 view->lineno = view->p_lineno;
4762 else
4763 view->lineno = 1;
4765 if (view->lineno < view->offset)
4766 view->offset = view->lineno;
4767 else if (view->offset + view->height <= view->lineno)
4768 view->offset = view->lineno - view->height + 1;
4770 view->p_restore = FALSE;
4771 }
4773 static void
4774 status_update_onbranch(void)
4775 {
4776 static const char *paths[][2] = {
4777 { "rebase-apply/rebasing", "Rebasing" },
4778 { "rebase-apply/applying", "Applying mailbox" },
4779 { "rebase-apply/", "Rebasing mailbox" },
4780 { "rebase-merge/interactive", "Interactive rebase" },
4781 { "rebase-merge/", "Rebase merge" },
4782 { "MERGE_HEAD", "Merging" },
4783 { "BISECT_LOG", "Bisecting" },
4784 { "HEAD", "On branch" },
4785 };
4786 char buf[SIZEOF_STR];
4787 struct stat stat;
4788 int i;
4790 if (is_initial_commit()) {
4791 string_copy(status_onbranch, "Initial commit");
4792 return;
4793 }
4795 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4796 char *head = opt_head;
4798 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4799 lstat(buf, &stat) < 0)
4800 continue;
4802 if (!*opt_head) {
4803 struct io io = {};
4805 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4806 io_open(&io, buf) &&
4807 io_read_buf(&io, buf, sizeof(buf))) {
4808 head = chomp_string(buf);
4809 if (!prefixcmp(head, "refs/heads/"))
4810 head += STRING_SIZE("refs/heads/");
4811 }
4812 }
4814 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4815 string_copy(status_onbranch, opt_head);
4816 return;
4817 }
4819 string_copy(status_onbranch, "Not currently on any branch");
4820 }
4822 /* First parse staged info using git-diff-index(1), then parse unstaged
4823 * info using git-diff-files(1), and finally untracked files using
4824 * git-ls-files(1). */
4825 static bool
4826 status_open(struct view *view)
4827 {
4828 reset_view(view);
4830 add_line_data(view, NULL, LINE_STAT_HEAD);
4831 status_update_onbranch();
4833 run_io_bg(update_index_argv);
4835 if (is_initial_commit()) {
4836 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4837 return FALSE;
4838 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4839 return FALSE;
4840 }
4842 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4843 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4844 return FALSE;
4846 /* Restore the exact position or use the specialized restore
4847 * mode? */
4848 if (!view->p_restore)
4849 status_restore(view);
4850 return TRUE;
4851 }
4853 static bool
4854 status_draw(struct view *view, struct line *line, unsigned int lineno)
4855 {
4856 struct status *status = line->data;
4857 enum line_type type;
4858 const char *text;
4860 if (!status) {
4861 switch (line->type) {
4862 case LINE_STAT_STAGED:
4863 type = LINE_STAT_SECTION;
4864 text = "Changes to be committed:";
4865 break;
4867 case LINE_STAT_UNSTAGED:
4868 type = LINE_STAT_SECTION;
4869 text = "Changed but not updated:";
4870 break;
4872 case LINE_STAT_UNTRACKED:
4873 type = LINE_STAT_SECTION;
4874 text = "Untracked files:";
4875 break;
4877 case LINE_STAT_NONE:
4878 type = LINE_DEFAULT;
4879 text = " (no files)";
4880 break;
4882 case LINE_STAT_HEAD:
4883 type = LINE_STAT_HEAD;
4884 text = status_onbranch;
4885 break;
4887 default:
4888 return FALSE;
4889 }
4890 } else {
4891 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4893 buf[0] = status->status;
4894 if (draw_text(view, line->type, buf, TRUE))
4895 return TRUE;
4896 type = LINE_DEFAULT;
4897 text = status->new.name;
4898 }
4900 draw_text(view, type, text, TRUE);
4901 return TRUE;
4902 }
4904 static enum request
4905 status_enter(struct view *view, struct line *line)
4906 {
4907 struct status *status = line->data;
4908 const char *oldpath = status ? status->old.name : NULL;
4909 /* Diffs for unmerged entries are empty when passing the new
4910 * path, so leave it empty. */
4911 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4912 const char *info;
4913 enum open_flags split;
4914 struct view *stage = VIEW(REQ_VIEW_STAGE);
4916 if (line->type == LINE_STAT_NONE ||
4917 (!status && line[1].type == LINE_STAT_NONE)) {
4918 report("No file to diff");
4919 return REQ_NONE;
4920 }
4922 switch (line->type) {
4923 case LINE_STAT_STAGED:
4924 if (is_initial_commit()) {
4925 const char *no_head_diff_argv[] = {
4926 "git", "diff", "--no-color", "--patch-with-stat",
4927 "--", "/dev/null", newpath, NULL
4928 };
4930 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4931 return REQ_QUIT;
4932 } else {
4933 const char *index_show_argv[] = {
4934 "git", "diff-index", "--root", "--patch-with-stat",
4935 "-C", "-M", "--cached", "HEAD", "--",
4936 oldpath, newpath, NULL
4937 };
4939 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4940 return REQ_QUIT;
4941 }
4943 if (status)
4944 info = "Staged changes to %s";
4945 else
4946 info = "Staged changes";
4947 break;
4949 case LINE_STAT_UNSTAGED:
4950 {
4951 const char *files_show_argv[] = {
4952 "git", "diff-files", "--root", "--patch-with-stat",
4953 "-C", "-M", "--", oldpath, newpath, NULL
4954 };
4956 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4957 return REQ_QUIT;
4958 if (status)
4959 info = "Unstaged changes to %s";
4960 else
4961 info = "Unstaged changes";
4962 break;
4963 }
4964 case LINE_STAT_UNTRACKED:
4965 if (!newpath) {
4966 report("No file to show");
4967 return REQ_NONE;
4968 }
4970 if (!suffixcmp(status->new.name, -1, "/")) {
4971 report("Cannot display a directory");
4972 return REQ_NONE;
4973 }
4975 if (!prepare_update_file(stage, newpath))
4976 return REQ_QUIT;
4977 info = "Untracked file %s";
4978 break;
4980 case LINE_STAT_HEAD:
4981 return REQ_NONE;
4983 default:
4984 die("line type %d not handled in switch", line->type);
4985 }
4987 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4988 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4989 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4990 if (status) {
4991 stage_status = *status;
4992 } else {
4993 memset(&stage_status, 0, sizeof(stage_status));
4994 }
4996 stage_line_type = line->type;
4997 stage_chunks = 0;
4998 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4999 }
5001 return REQ_NONE;
5002 }
5004 static bool
5005 status_exists(struct status *status, enum line_type type)
5006 {
5007 struct view *view = VIEW(REQ_VIEW_STATUS);
5008 unsigned long lineno;
5010 for (lineno = 0; lineno < view->lines; lineno++) {
5011 struct line *line = &view->line[lineno];
5012 struct status *pos = line->data;
5014 if (line->type != type)
5015 continue;
5016 if (!pos && (!status || !status->status) && line[1].data) {
5017 select_view_line(view, lineno);
5018 return TRUE;
5019 }
5020 if (pos && !strcmp(status->new.name, pos->new.name)) {
5021 select_view_line(view, lineno);
5022 return TRUE;
5023 }
5024 }
5026 return FALSE;
5027 }
5030 static bool
5031 status_update_prepare(struct io *io, enum line_type type)
5032 {
5033 const char *staged_argv[] = {
5034 "git", "update-index", "-z", "--index-info", NULL
5035 };
5036 const char *others_argv[] = {
5037 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5038 };
5040 switch (type) {
5041 case LINE_STAT_STAGED:
5042 return run_io(io, staged_argv, opt_cdup, IO_WR);
5044 case LINE_STAT_UNSTAGED:
5045 return run_io(io, others_argv, opt_cdup, IO_WR);
5047 case LINE_STAT_UNTRACKED:
5048 return run_io(io, others_argv, NULL, IO_WR);
5050 default:
5051 die("line type %d not handled in switch", type);
5052 return FALSE;
5053 }
5054 }
5056 static bool
5057 status_update_write(struct io *io, struct status *status, enum line_type type)
5058 {
5059 char buf[SIZEOF_STR];
5060 size_t bufsize = 0;
5062 switch (type) {
5063 case LINE_STAT_STAGED:
5064 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5065 status->old.mode,
5066 status->old.rev,
5067 status->old.name, 0))
5068 return FALSE;
5069 break;
5071 case LINE_STAT_UNSTAGED:
5072 case LINE_STAT_UNTRACKED:
5073 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5074 return FALSE;
5075 break;
5077 default:
5078 die("line type %d not handled in switch", type);
5079 }
5081 return io_write(io, buf, bufsize);
5082 }
5084 static bool
5085 status_update_file(struct status *status, enum line_type type)
5086 {
5087 struct io io = {};
5088 bool result;
5090 if (!status_update_prepare(&io, type))
5091 return FALSE;
5093 result = status_update_write(&io, status, type);
5094 done_io(&io);
5095 return result;
5096 }
5098 static bool
5099 status_update_files(struct view *view, struct line *line)
5100 {
5101 struct io io = {};
5102 bool result = TRUE;
5103 struct line *pos = view->line + view->lines;
5104 int files = 0;
5105 int file, done;
5107 if (!status_update_prepare(&io, line->type))
5108 return FALSE;
5110 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5111 files++;
5113 for (file = 0, done = 0; result && file < files; line++, file++) {
5114 int almost_done = file * 100 / files;
5116 if (almost_done > done) {
5117 done = almost_done;
5118 string_format(view->ref, "updating file %u of %u (%d%% done)",
5119 file, files, done);
5120 update_view_title(view);
5121 }
5122 result = status_update_write(&io, line->data, line->type);
5123 }
5125 done_io(&io);
5126 return result;
5127 }
5129 static bool
5130 status_update(struct view *view)
5131 {
5132 struct line *line = &view->line[view->lineno];
5134 assert(view->lines);
5136 if (!line->data) {
5137 /* This should work even for the "On branch" line. */
5138 if (line < view->line + view->lines && !line[1].data) {
5139 report("Nothing to update");
5140 return FALSE;
5141 }
5143 if (!status_update_files(view, line + 1)) {
5144 report("Failed to update file status");
5145 return FALSE;
5146 }
5148 } else if (!status_update_file(line->data, line->type)) {
5149 report("Failed to update file status");
5150 return FALSE;
5151 }
5153 return TRUE;
5154 }
5156 static bool
5157 status_revert(struct status *status, enum line_type type, bool has_none)
5158 {
5159 if (!status || type != LINE_STAT_UNSTAGED) {
5160 if (type == LINE_STAT_STAGED) {
5161 report("Cannot revert changes to staged files");
5162 } else if (type == LINE_STAT_UNTRACKED) {
5163 report("Cannot revert changes to untracked files");
5164 } else if (has_none) {
5165 report("Nothing to revert");
5166 } else {
5167 report("Cannot revert changes to multiple files");
5168 }
5169 return FALSE;
5171 } else {
5172 char mode[10] = "100644";
5173 const char *reset_argv[] = {
5174 "git", "update-index", "--cacheinfo", mode,
5175 status->old.rev, status->old.name, NULL
5176 };
5177 const char *checkout_argv[] = {
5178 "git", "checkout", "--", status->old.name, NULL
5179 };
5181 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5182 return FALSE;
5183 string_format(mode, "%o", status->old.mode);
5184 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5185 run_io_fg(checkout_argv, opt_cdup);
5186 }
5187 }
5189 static enum request
5190 status_request(struct view *view, enum request request, struct line *line)
5191 {
5192 struct status *status = line->data;
5194 switch (request) {
5195 case REQ_STATUS_UPDATE:
5196 if (!status_update(view))
5197 return REQ_NONE;
5198 break;
5200 case REQ_STATUS_REVERT:
5201 if (!status_revert(status, line->type, status_has_none(view, line)))
5202 return REQ_NONE;
5203 break;
5205 case REQ_STATUS_MERGE:
5206 if (!status || status->status != 'U') {
5207 report("Merging only possible for files with unmerged status ('U').");
5208 return REQ_NONE;
5209 }
5210 open_mergetool(status->new.name);
5211 break;
5213 case REQ_EDIT:
5214 if (!status)
5215 return request;
5216 if (status->status == 'D') {
5217 report("File has been deleted.");
5218 return REQ_NONE;
5219 }
5221 open_editor(status->status != '?', status->new.name);
5222 break;
5224 case REQ_VIEW_BLAME:
5225 if (status) {
5226 string_copy(opt_file, status->new.name);
5227 opt_ref[0] = 0;
5228 }
5229 return request;
5231 case REQ_ENTER:
5232 /* After returning the status view has been split to
5233 * show the stage view. No further reloading is
5234 * necessary. */
5235 status_enter(view, line);
5236 return REQ_NONE;
5238 case REQ_REFRESH:
5239 /* Simply reload the view. */
5240 break;
5242 default:
5243 return request;
5244 }
5246 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5248 return REQ_NONE;
5249 }
5251 static void
5252 status_select(struct view *view, struct line *line)
5253 {
5254 struct status *status = line->data;
5255 char file[SIZEOF_STR] = "all files";
5256 const char *text;
5257 const char *key;
5259 if (status && !string_format(file, "'%s'", status->new.name))
5260 return;
5262 if (!status && line[1].type == LINE_STAT_NONE)
5263 line++;
5265 switch (line->type) {
5266 case LINE_STAT_STAGED:
5267 text = "Press %s to unstage %s for commit";
5268 break;
5270 case LINE_STAT_UNSTAGED:
5271 text = "Press %s to stage %s for commit";
5272 break;
5274 case LINE_STAT_UNTRACKED:
5275 text = "Press %s to stage %s for addition";
5276 break;
5278 case LINE_STAT_HEAD:
5279 case LINE_STAT_NONE:
5280 text = "Nothing to update";
5281 break;
5283 default:
5284 die("line type %d not handled in switch", line->type);
5285 }
5287 if (status && status->status == 'U') {
5288 text = "Press %s to resolve conflict in %s";
5289 key = get_key(REQ_STATUS_MERGE);
5291 } else {
5292 key = get_key(REQ_STATUS_UPDATE);
5293 }
5295 string_format(view->ref, text, key, file);
5296 }
5298 static bool
5299 status_grep(struct view *view, struct line *line)
5300 {
5301 struct status *status = line->data;
5302 enum { S_STATUS, S_NAME, S_END } state;
5303 char buf[2] = "?";
5304 regmatch_t pmatch;
5306 if (!status)
5307 return FALSE;
5309 for (state = S_STATUS; state < S_END; state++) {
5310 const char *text;
5312 switch (state) {
5313 case S_NAME: text = status->new.name; break;
5314 case S_STATUS:
5315 buf[0] = status->status;
5316 text = buf;
5317 break;
5319 default:
5320 return FALSE;
5321 }
5323 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5324 return TRUE;
5325 }
5327 return FALSE;
5328 }
5330 static struct view_ops status_ops = {
5331 "file",
5332 NULL,
5333 status_open,
5334 NULL,
5335 status_draw,
5336 status_request,
5337 status_grep,
5338 status_select,
5339 };
5342 static bool
5343 stage_diff_write(struct io *io, struct line *line, struct line *end)
5344 {
5345 while (line < end) {
5346 if (!io_write(io, line->data, strlen(line->data)) ||
5347 !io_write(io, "\n", 1))
5348 return FALSE;
5349 line++;
5350 if (line->type == LINE_DIFF_CHUNK ||
5351 line->type == LINE_DIFF_HEADER)
5352 break;
5353 }
5355 return TRUE;
5356 }
5358 static struct line *
5359 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5360 {
5361 for (; view->line < line; line--)
5362 if (line->type == type)
5363 return line;
5365 return NULL;
5366 }
5368 static bool
5369 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5370 {
5371 const char *apply_argv[SIZEOF_ARG] = {
5372 "git", "apply", "--whitespace=nowarn", NULL
5373 };
5374 struct line *diff_hdr;
5375 struct io io = {};
5376 int argc = 3;
5378 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5379 if (!diff_hdr)
5380 return FALSE;
5382 if (!revert)
5383 apply_argv[argc++] = "--cached";
5384 if (revert || stage_line_type == LINE_STAT_STAGED)
5385 apply_argv[argc++] = "-R";
5386 apply_argv[argc++] = "-";
5387 apply_argv[argc++] = NULL;
5388 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5389 return FALSE;
5391 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5392 !stage_diff_write(&io, chunk, view->line + view->lines))
5393 chunk = NULL;
5395 done_io(&io);
5396 run_io_bg(update_index_argv);
5398 return chunk ? TRUE : FALSE;
5399 }
5401 static bool
5402 stage_update(struct view *view, struct line *line)
5403 {
5404 struct line *chunk = NULL;
5406 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5407 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5409 if (chunk) {
5410 if (!stage_apply_chunk(view, chunk, FALSE)) {
5411 report("Failed to apply chunk");
5412 return FALSE;
5413 }
5415 } else if (!stage_status.status) {
5416 view = VIEW(REQ_VIEW_STATUS);
5418 for (line = view->line; line < view->line + view->lines; line++)
5419 if (line->type == stage_line_type)
5420 break;
5422 if (!status_update_files(view, line + 1)) {
5423 report("Failed to update files");
5424 return FALSE;
5425 }
5427 } else if (!status_update_file(&stage_status, stage_line_type)) {
5428 report("Failed to update file");
5429 return FALSE;
5430 }
5432 return TRUE;
5433 }
5435 static bool
5436 stage_revert(struct view *view, struct line *line)
5437 {
5438 struct line *chunk = NULL;
5440 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5441 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5443 if (chunk) {
5444 if (!prompt_yesno("Are you sure you want to revert changes?"))
5445 return FALSE;
5447 if (!stage_apply_chunk(view, chunk, TRUE)) {
5448 report("Failed to revert chunk");
5449 return FALSE;
5450 }
5451 return TRUE;
5453 } else {
5454 return status_revert(stage_status.status ? &stage_status : NULL,
5455 stage_line_type, FALSE);
5456 }
5457 }
5460 static void
5461 stage_next(struct view *view, struct line *line)
5462 {
5463 int i;
5465 if (!stage_chunks) {
5466 static size_t alloc = 0;
5467 int *tmp;
5469 for (line = view->line; line < view->line + view->lines; line++) {
5470 if (line->type != LINE_DIFF_CHUNK)
5471 continue;
5473 tmp = realloc_items(stage_chunk, &alloc,
5474 stage_chunks, sizeof(*tmp));
5475 if (!tmp) {
5476 report("Allocation failure");
5477 return;
5478 }
5480 stage_chunk = tmp;
5481 stage_chunk[stage_chunks++] = line - view->line;
5482 }
5483 }
5485 for (i = 0; i < stage_chunks; i++) {
5486 if (stage_chunk[i] > view->lineno) {
5487 do_scroll_view(view, stage_chunk[i] - view->lineno);
5488 report("Chunk %d of %d", i + 1, stage_chunks);
5489 return;
5490 }
5491 }
5493 report("No next chunk found");
5494 }
5496 static enum request
5497 stage_request(struct view *view, enum request request, struct line *line)
5498 {
5499 switch (request) {
5500 case REQ_STATUS_UPDATE:
5501 if (!stage_update(view, line))
5502 return REQ_NONE;
5503 break;
5505 case REQ_STATUS_REVERT:
5506 if (!stage_revert(view, line))
5507 return REQ_NONE;
5508 break;
5510 case REQ_STAGE_NEXT:
5511 if (stage_line_type == LINE_STAT_UNTRACKED) {
5512 report("File is untracked; press %s to add",
5513 get_key(REQ_STATUS_UPDATE));
5514 return REQ_NONE;
5515 }
5516 stage_next(view, line);
5517 return REQ_NONE;
5519 case REQ_EDIT:
5520 if (!stage_status.new.name[0])
5521 return request;
5522 if (stage_status.status == 'D') {
5523 report("File has been deleted.");
5524 return REQ_NONE;
5525 }
5527 open_editor(stage_status.status != '?', stage_status.new.name);
5528 break;
5530 case REQ_REFRESH:
5531 /* Reload everything ... */
5532 break;
5534 case REQ_VIEW_BLAME:
5535 if (stage_status.new.name[0]) {
5536 string_copy(opt_file, stage_status.new.name);
5537 opt_ref[0] = 0;
5538 }
5539 return request;
5541 case REQ_ENTER:
5542 return pager_request(view, request, line);
5544 default:
5545 return request;
5546 }
5548 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5549 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5551 /* Check whether the staged entry still exists, and close the
5552 * stage view if it doesn't. */
5553 if (!status_exists(&stage_status, stage_line_type)) {
5554 status_restore(VIEW(REQ_VIEW_STATUS));
5555 return REQ_VIEW_CLOSE;
5556 }
5558 if (stage_line_type == LINE_STAT_UNTRACKED) {
5559 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5560 report("Cannot display a directory");
5561 return REQ_NONE;
5562 }
5564 if (!prepare_update_file(view, stage_status.new.name)) {
5565 report("Failed to open file: %s", strerror(errno));
5566 return REQ_NONE;
5567 }
5568 }
5569 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5571 return REQ_NONE;
5572 }
5574 static struct view_ops stage_ops = {
5575 "line",
5576 NULL,
5577 NULL,
5578 pager_read,
5579 pager_draw,
5580 stage_request,
5581 pager_grep,
5582 pager_select,
5583 };
5586 /*
5587 * Revision graph
5588 */
5590 struct commit {
5591 char id[SIZEOF_REV]; /* SHA1 ID. */
5592 char title[128]; /* First line of the commit message. */
5593 char author[75]; /* Author of the commit. */
5594 struct tm time; /* Date from the author ident. */
5595 struct ref **refs; /* Repository references. */
5596 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5597 size_t graph_size; /* The width of the graph array. */
5598 bool has_parents; /* Rewritten --parents seen. */
5599 };
5601 /* Size of rev graph with no "padding" columns */
5602 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5604 struct rev_graph {
5605 struct rev_graph *prev, *next, *parents;
5606 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5607 size_t size;
5608 struct commit *commit;
5609 size_t pos;
5610 unsigned int boundary:1;
5611 };
5613 /* Parents of the commit being visualized. */
5614 static struct rev_graph graph_parents[4];
5616 /* The current stack of revisions on the graph. */
5617 static struct rev_graph graph_stacks[4] = {
5618 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5619 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5620 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5621 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5622 };
5624 static inline bool
5625 graph_parent_is_merge(struct rev_graph *graph)
5626 {
5627 return graph->parents->size > 1;
5628 }
5630 static inline void
5631 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5632 {
5633 struct commit *commit = graph->commit;
5635 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5636 commit->graph[commit->graph_size++] = symbol;
5637 }
5639 static void
5640 clear_rev_graph(struct rev_graph *graph)
5641 {
5642 graph->boundary = 0;
5643 graph->size = graph->pos = 0;
5644 graph->commit = NULL;
5645 memset(graph->parents, 0, sizeof(*graph->parents));
5646 }
5648 static void
5649 done_rev_graph(struct rev_graph *graph)
5650 {
5651 if (graph_parent_is_merge(graph) &&
5652 graph->pos < graph->size - 1 &&
5653 graph->next->size == graph->size + graph->parents->size - 1) {
5654 size_t i = graph->pos + graph->parents->size - 1;
5656 graph->commit->graph_size = i * 2;
5657 while (i < graph->next->size - 1) {
5658 append_to_rev_graph(graph, ' ');
5659 append_to_rev_graph(graph, '\\');
5660 i++;
5661 }
5662 }
5664 clear_rev_graph(graph);
5665 }
5667 static void
5668 push_rev_graph(struct rev_graph *graph, const char *parent)
5669 {
5670 int i;
5672 /* "Collapse" duplicate parents lines.
5673 *
5674 * FIXME: This needs to also update update the drawn graph but
5675 * for now it just serves as a method for pruning graph lines. */
5676 for (i = 0; i < graph->size; i++)
5677 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5678 return;
5680 if (graph->size < SIZEOF_REVITEMS) {
5681 string_copy_rev(graph->rev[graph->size++], parent);
5682 }
5683 }
5685 static chtype
5686 get_rev_graph_symbol(struct rev_graph *graph)
5687 {
5688 chtype symbol;
5690 if (graph->boundary)
5691 symbol = REVGRAPH_BOUND;
5692 else if (graph->parents->size == 0)
5693 symbol = REVGRAPH_INIT;
5694 else if (graph_parent_is_merge(graph))
5695 symbol = REVGRAPH_MERGE;
5696 else if (graph->pos >= graph->size)
5697 symbol = REVGRAPH_BRANCH;
5698 else
5699 symbol = REVGRAPH_COMMIT;
5701 return symbol;
5702 }
5704 static void
5705 draw_rev_graph(struct rev_graph *graph)
5706 {
5707 struct rev_filler {
5708 chtype separator, line;
5709 };
5710 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5711 static struct rev_filler fillers[] = {
5712 { ' ', '|' },
5713 { '`', '.' },
5714 { '\'', ' ' },
5715 { '/', ' ' },
5716 };
5717 chtype symbol = get_rev_graph_symbol(graph);
5718 struct rev_filler *filler;
5719 size_t i;
5721 if (opt_line_graphics)
5722 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5724 filler = &fillers[DEFAULT];
5726 for (i = 0; i < graph->pos; i++) {
5727 append_to_rev_graph(graph, filler->line);
5728 if (graph_parent_is_merge(graph->prev) &&
5729 graph->prev->pos == i)
5730 filler = &fillers[RSHARP];
5732 append_to_rev_graph(graph, filler->separator);
5733 }
5735 /* Place the symbol for this revision. */
5736 append_to_rev_graph(graph, symbol);
5738 if (graph->prev->size > graph->size)
5739 filler = &fillers[RDIAG];
5740 else
5741 filler = &fillers[DEFAULT];
5743 i++;
5745 for (; i < graph->size; i++) {
5746 append_to_rev_graph(graph, filler->separator);
5747 append_to_rev_graph(graph, filler->line);
5748 if (graph_parent_is_merge(graph->prev) &&
5749 i < graph->prev->pos + graph->parents->size)
5750 filler = &fillers[RSHARP];
5751 if (graph->prev->size > graph->size)
5752 filler = &fillers[LDIAG];
5753 }
5755 if (graph->prev->size > graph->size) {
5756 append_to_rev_graph(graph, filler->separator);
5757 if (filler->line != ' ')
5758 append_to_rev_graph(graph, filler->line);
5759 }
5760 }
5762 /* Prepare the next rev graph */
5763 static void
5764 prepare_rev_graph(struct rev_graph *graph)
5765 {
5766 size_t i;
5768 /* First, traverse all lines of revisions up to the active one. */
5769 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5770 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5771 break;
5773 push_rev_graph(graph->next, graph->rev[graph->pos]);
5774 }
5776 /* Interleave the new revision parent(s). */
5777 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5778 push_rev_graph(graph->next, graph->parents->rev[i]);
5780 /* Lastly, put any remaining revisions. */
5781 for (i = graph->pos + 1; i < graph->size; i++)
5782 push_rev_graph(graph->next, graph->rev[i]);
5783 }
5785 static void
5786 update_rev_graph(struct view *view, struct rev_graph *graph)
5787 {
5788 /* If this is the finalizing update ... */
5789 if (graph->commit)
5790 prepare_rev_graph(graph);
5792 /* Graph visualization needs a one rev look-ahead,
5793 * so the first update doesn't visualize anything. */
5794 if (!graph->prev->commit)
5795 return;
5797 if (view->lines > 2)
5798 view->line[view->lines - 3].dirty = 1;
5799 if (view->lines > 1)
5800 view->line[view->lines - 2].dirty = 1;
5801 draw_rev_graph(graph->prev);
5802 done_rev_graph(graph->prev->prev);
5803 }
5806 /*
5807 * Main view backend
5808 */
5810 static const char *main_argv[SIZEOF_ARG] = {
5811 "git", "log", "--no-color", "--pretty=raw", "--parents",
5812 "--topo-order", "%(head)", NULL
5813 };
5815 static bool
5816 main_draw(struct view *view, struct line *line, unsigned int lineno)
5817 {
5818 struct commit *commit = line->data;
5820 if (!*commit->author)
5821 return FALSE;
5823 if (opt_date && draw_date(view, &commit->time))
5824 return TRUE;
5826 if (opt_author && draw_author(view, commit->author))
5827 return TRUE;
5829 if (opt_rev_graph && commit->graph_size &&
5830 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5831 return TRUE;
5833 if (opt_show_refs && commit->refs) {
5834 size_t i = 0;
5836 do {
5837 enum line_type type;
5839 if (commit->refs[i]->head)
5840 type = LINE_MAIN_HEAD;
5841 else if (commit->refs[i]->ltag)
5842 type = LINE_MAIN_LOCAL_TAG;
5843 else if (commit->refs[i]->tag)
5844 type = LINE_MAIN_TAG;
5845 else if (commit->refs[i]->tracked)
5846 type = LINE_MAIN_TRACKED;
5847 else if (commit->refs[i]->remote)
5848 type = LINE_MAIN_REMOTE;
5849 else
5850 type = LINE_MAIN_REF;
5852 if (draw_text(view, type, "[", TRUE) ||
5853 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5854 draw_text(view, type, "]", TRUE))
5855 return TRUE;
5857 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5858 return TRUE;
5859 } while (commit->refs[i++]->next);
5860 }
5862 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5863 return TRUE;
5864 }
5866 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5867 static bool
5868 main_read(struct view *view, char *line)
5869 {
5870 static struct rev_graph *graph = graph_stacks;
5871 enum line_type type;
5872 struct commit *commit;
5874 if (!line) {
5875 int i;
5877 if (!view->lines && !view->parent)
5878 die("No revisions match the given arguments.");
5879 if (view->lines > 0) {
5880 commit = view->line[view->lines - 1].data;
5881 view->line[view->lines - 1].dirty = 1;
5882 if (!*commit->author) {
5883 view->lines--;
5884 free(commit);
5885 graph->commit = NULL;
5886 }
5887 }
5888 update_rev_graph(view, graph);
5890 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5891 clear_rev_graph(&graph_stacks[i]);
5892 return TRUE;
5893 }
5895 type = get_line_type(line);
5896 if (type == LINE_COMMIT) {
5897 commit = calloc(1, sizeof(struct commit));
5898 if (!commit)
5899 return FALSE;
5901 line += STRING_SIZE("commit ");
5902 if (*line == '-') {
5903 graph->boundary = 1;
5904 line++;
5905 }
5907 string_copy_rev(commit->id, line);
5908 commit->refs = get_refs(commit->id);
5909 graph->commit = commit;
5910 add_line_data(view, commit, LINE_MAIN_COMMIT);
5912 while ((line = strchr(line, ' '))) {
5913 line++;
5914 push_rev_graph(graph->parents, line);
5915 commit->has_parents = TRUE;
5916 }
5917 return TRUE;
5918 }
5920 if (!view->lines)
5921 return TRUE;
5922 commit = view->line[view->lines - 1].data;
5924 switch (type) {
5925 case LINE_PARENT:
5926 if (commit->has_parents)
5927 break;
5928 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5929 break;
5931 case LINE_AUTHOR:
5932 parse_author_line(line + STRING_SIZE("author "),
5933 commit->author, sizeof(commit->author),
5934 &commit->time);
5935 update_rev_graph(view, graph);
5936 graph = graph->next;
5937 break;
5939 default:
5940 /* Fill in the commit title if it has not already been set. */
5941 if (commit->title[0])
5942 break;
5944 /* Require titles to start with a non-space character at the
5945 * offset used by git log. */
5946 if (strncmp(line, " ", 4))
5947 break;
5948 line += 4;
5949 /* Well, if the title starts with a whitespace character,
5950 * try to be forgiving. Otherwise we end up with no title. */
5951 while (isspace(*line))
5952 line++;
5953 if (*line == '\0')
5954 break;
5955 /* FIXME: More graceful handling of titles; append "..." to
5956 * shortened titles, etc. */
5958 string_expand(commit->title, sizeof(commit->title), line, 1);
5959 view->line[view->lines - 1].dirty = 1;
5960 }
5962 return TRUE;
5963 }
5965 static enum request
5966 main_request(struct view *view, enum request request, struct line *line)
5967 {
5968 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5970 switch (request) {
5971 case REQ_ENTER:
5972 open_view(view, REQ_VIEW_DIFF, flags);
5973 break;
5974 case REQ_REFRESH:
5975 load_refs();
5976 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5977 break;
5978 default:
5979 return request;
5980 }
5982 return REQ_NONE;
5983 }
5985 static bool
5986 grep_refs(struct ref **refs, regex_t *regex)
5987 {
5988 regmatch_t pmatch;
5989 size_t i = 0;
5991 if (!refs)
5992 return FALSE;
5993 do {
5994 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5995 return TRUE;
5996 } while (refs[i++]->next);
5998 return FALSE;
5999 }
6001 static bool
6002 main_grep(struct view *view, struct line *line)
6003 {
6004 struct commit *commit = line->data;
6005 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6006 char buf[DATE_COLS + 1];
6007 regmatch_t pmatch;
6009 for (state = S_TITLE; state < S_END; state++) {
6010 char *text;
6012 switch (state) {
6013 case S_TITLE: text = commit->title; break;
6014 case S_AUTHOR:
6015 if (!opt_author)
6016 continue;
6017 text = commit->author;
6018 break;
6019 case S_DATE:
6020 if (!opt_date)
6021 continue;
6022 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6023 continue;
6024 text = buf;
6025 break;
6026 case S_REFS:
6027 if (!opt_show_refs)
6028 continue;
6029 if (grep_refs(commit->refs, view->regex) == TRUE)
6030 return TRUE;
6031 continue;
6032 default:
6033 return FALSE;
6034 }
6036 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6037 return TRUE;
6038 }
6040 return FALSE;
6041 }
6043 static void
6044 main_select(struct view *view, struct line *line)
6045 {
6046 struct commit *commit = line->data;
6048 string_copy_rev(view->ref, commit->id);
6049 string_copy_rev(ref_commit, view->ref);
6050 }
6052 static struct view_ops main_ops = {
6053 "commit",
6054 main_argv,
6055 NULL,
6056 main_read,
6057 main_draw,
6058 main_request,
6059 main_grep,
6060 main_select,
6061 };
6064 /*
6065 * Unicode / UTF-8 handling
6066 *
6067 * NOTE: Much of the following code for dealing with Unicode is derived from
6068 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6069 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6070 */
6072 static inline int
6073 unicode_width(unsigned long c)
6074 {
6075 if (c >= 0x1100 &&
6076 (c <= 0x115f /* Hangul Jamo */
6077 || c == 0x2329
6078 || c == 0x232a
6079 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6080 /* CJK ... Yi */
6081 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6082 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6083 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6084 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6085 || (c >= 0xffe0 && c <= 0xffe6)
6086 || (c >= 0x20000 && c <= 0x2fffd)
6087 || (c >= 0x30000 && c <= 0x3fffd)))
6088 return 2;
6090 if (c == '\t')
6091 return opt_tab_size;
6093 return 1;
6094 }
6096 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6097 * Illegal bytes are set one. */
6098 static const unsigned char utf8_bytes[256] = {
6099 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,
6100 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,
6101 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,
6102 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,
6103 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,
6104 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,
6105 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,
6106 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,
6107 };
6109 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6110 static inline unsigned long
6111 utf8_to_unicode(const char *string, size_t length)
6112 {
6113 unsigned long unicode;
6115 switch (length) {
6116 case 1:
6117 unicode = string[0];
6118 break;
6119 case 2:
6120 unicode = (string[0] & 0x1f) << 6;
6121 unicode += (string[1] & 0x3f);
6122 break;
6123 case 3:
6124 unicode = (string[0] & 0x0f) << 12;
6125 unicode += ((string[1] & 0x3f) << 6);
6126 unicode += (string[2] & 0x3f);
6127 break;
6128 case 4:
6129 unicode = (string[0] & 0x0f) << 18;
6130 unicode += ((string[1] & 0x3f) << 12);
6131 unicode += ((string[2] & 0x3f) << 6);
6132 unicode += (string[3] & 0x3f);
6133 break;
6134 case 5:
6135 unicode = (string[0] & 0x0f) << 24;
6136 unicode += ((string[1] & 0x3f) << 18);
6137 unicode += ((string[2] & 0x3f) << 12);
6138 unicode += ((string[3] & 0x3f) << 6);
6139 unicode += (string[4] & 0x3f);
6140 break;
6141 case 6:
6142 unicode = (string[0] & 0x01) << 30;
6143 unicode += ((string[1] & 0x3f) << 24);
6144 unicode += ((string[2] & 0x3f) << 18);
6145 unicode += ((string[3] & 0x3f) << 12);
6146 unicode += ((string[4] & 0x3f) << 6);
6147 unicode += (string[5] & 0x3f);
6148 break;
6149 default:
6150 die("Invalid Unicode length");
6151 }
6153 /* Invalid characters could return the special 0xfffd value but NUL
6154 * should be just as good. */
6155 return unicode > 0xffff ? 0 : unicode;
6156 }
6158 /* Calculates how much of string can be shown within the given maximum width
6159 * and sets trimmed parameter to non-zero value if all of string could not be
6160 * shown. If the reserve flag is TRUE, it will reserve at least one
6161 * trailing character, which can be useful when drawing a delimiter.
6162 *
6163 * Returns the number of bytes to output from string to satisfy max_width. */
6164 static size_t
6165 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6166 {
6167 const char *string = *start;
6168 const char *end = strchr(string, '\0');
6169 unsigned char last_bytes = 0;
6170 size_t last_ucwidth = 0;
6172 *width = 0;
6173 *trimmed = 0;
6175 while (string < end) {
6176 int c = *(unsigned char *) string;
6177 unsigned char bytes = utf8_bytes[c];
6178 size_t ucwidth;
6179 unsigned long unicode;
6181 if (string + bytes > end)
6182 break;
6184 /* Change representation to figure out whether
6185 * it is a single- or double-width character. */
6187 unicode = utf8_to_unicode(string, bytes);
6188 /* FIXME: Graceful handling of invalid Unicode character. */
6189 if (!unicode)
6190 break;
6192 ucwidth = unicode_width(unicode);
6193 if (skip > 0) {
6194 skip -= ucwidth <= skip ? ucwidth : skip;
6195 *start += bytes;
6196 }
6197 *width += ucwidth;
6198 if (*width > max_width) {
6199 *trimmed = 1;
6200 *width -= ucwidth;
6201 if (reserve && *width == max_width) {
6202 string -= last_bytes;
6203 *width -= last_ucwidth;
6204 }
6205 break;
6206 }
6208 string += bytes;
6209 last_bytes = ucwidth ? bytes : 0;
6210 last_ucwidth = ucwidth;
6211 }
6213 return string - *start;
6214 }
6217 /*
6218 * Status management
6219 */
6221 /* Whether or not the curses interface has been initialized. */
6222 static bool cursed = FALSE;
6224 /* Terminal hacks and workarounds. */
6225 static bool use_scroll_redrawwin;
6226 static bool use_scroll_status_wclear;
6228 /* The status window is used for polling keystrokes. */
6229 static WINDOW *status_win;
6231 /* Reading from the prompt? */
6232 static bool input_mode = FALSE;
6234 static bool status_empty = FALSE;
6236 /* Update status and title window. */
6237 static void
6238 report(const char *msg, ...)
6239 {
6240 struct view *view = display[current_view];
6242 if (input_mode)
6243 return;
6245 if (!view) {
6246 char buf[SIZEOF_STR];
6247 va_list args;
6249 va_start(args, msg);
6250 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6251 buf[sizeof(buf) - 1] = 0;
6252 buf[sizeof(buf) - 2] = '.';
6253 buf[sizeof(buf) - 3] = '.';
6254 buf[sizeof(buf) - 4] = '.';
6255 }
6256 va_end(args);
6257 die("%s", buf);
6258 }
6260 if (!status_empty || *msg) {
6261 va_list args;
6263 va_start(args, msg);
6265 wmove(status_win, 0, 0);
6266 if (view->has_scrolled && use_scroll_status_wclear)
6267 wclear(status_win);
6268 if (*msg) {
6269 vwprintw(status_win, msg, args);
6270 status_empty = FALSE;
6271 } else {
6272 status_empty = TRUE;
6273 }
6274 wclrtoeol(status_win);
6275 wnoutrefresh(status_win);
6277 va_end(args);
6278 }
6280 update_view_title(view);
6281 }
6283 /* Controls when nodelay should be in effect when polling user input. */
6284 static void
6285 set_nonblocking_input(bool loading)
6286 {
6287 static unsigned int loading_views;
6289 if ((loading == FALSE && loading_views-- == 1) ||
6290 (loading == TRUE && loading_views++ == 0))
6291 nodelay(status_win, loading);
6292 }
6294 static void
6295 init_display(void)
6296 {
6297 const char *term;
6298 int x, y;
6300 /* Initialize the curses library */
6301 if (isatty(STDIN_FILENO)) {
6302 cursed = !!initscr();
6303 opt_tty = stdin;
6304 } else {
6305 /* Leave stdin and stdout alone when acting as a pager. */
6306 opt_tty = fopen("/dev/tty", "r+");
6307 if (!opt_tty)
6308 die("Failed to open /dev/tty");
6309 cursed = !!newterm(NULL, opt_tty, opt_tty);
6310 }
6312 if (!cursed)
6313 die("Failed to initialize curses");
6315 nonl(); /* Disable conversion and detect newlines from input. */
6316 cbreak(); /* Take input chars one at a time, no wait for \n */
6317 noecho(); /* Don't echo input */
6318 leaveok(stdscr, FALSE);
6320 if (has_colors())
6321 init_colors();
6323 getmaxyx(stdscr, y, x);
6324 status_win = newwin(1, 0, y - 1, 0);
6325 if (!status_win)
6326 die("Failed to create status window");
6328 /* Enable keyboard mapping */
6329 keypad(status_win, TRUE);
6330 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6332 TABSIZE = opt_tab_size;
6333 if (opt_line_graphics) {
6334 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6335 }
6337 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6338 if (term && !strcmp(term, "gnome-terminal")) {
6339 /* In the gnome-terminal-emulator, the message from
6340 * scrolling up one line when impossible followed by
6341 * scrolling down one line causes corruption of the
6342 * status line. This is fixed by calling wclear. */
6343 use_scroll_status_wclear = TRUE;
6344 use_scroll_redrawwin = FALSE;
6346 } else if (term && !strcmp(term, "xrvt-xpm")) {
6347 /* No problems with full optimizations in xrvt-(unicode)
6348 * and aterm. */
6349 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6351 } else {
6352 /* When scrolling in (u)xterm the last line in the
6353 * scrolling direction will update slowly. */
6354 use_scroll_redrawwin = TRUE;
6355 use_scroll_status_wclear = FALSE;
6356 }
6357 }
6359 static int
6360 get_input(int prompt_position)
6361 {
6362 struct view *view;
6363 int i, key, cursor_y, cursor_x;
6365 if (prompt_position)
6366 input_mode = TRUE;
6368 while (TRUE) {
6369 foreach_view (view, i) {
6370 update_view(view);
6371 if (view_is_displayed(view) && view->has_scrolled &&
6372 use_scroll_redrawwin)
6373 redrawwin(view->win);
6374 view->has_scrolled = FALSE;
6375 }
6377 /* Update the cursor position. */
6378 if (prompt_position) {
6379 getbegyx(status_win, cursor_y, cursor_x);
6380 cursor_x = prompt_position;
6381 } else {
6382 view = display[current_view];
6383 getbegyx(view->win, cursor_y, cursor_x);
6384 cursor_x = view->width - 1;
6385 cursor_y += view->lineno - view->offset;
6386 }
6387 setsyx(cursor_y, cursor_x);
6389 /* Refresh, accept single keystroke of input */
6390 doupdate();
6391 key = wgetch(status_win);
6393 /* wgetch() with nodelay() enabled returns ERR when
6394 * there's no input. */
6395 if (key == ERR) {
6397 } else if (key == KEY_RESIZE) {
6398 int height, width;
6400 getmaxyx(stdscr, height, width);
6402 wresize(status_win, 1, width);
6403 mvwin(status_win, height - 1, 0);
6404 wnoutrefresh(status_win);
6405 resize_display();
6406 redraw_display(TRUE);
6408 } else {
6409 input_mode = FALSE;
6410 return key;
6411 }
6412 }
6413 }
6415 static char *
6416 prompt_input(const char *prompt, input_handler handler, void *data)
6417 {
6418 enum input_status status = INPUT_OK;
6419 static char buf[SIZEOF_STR];
6420 size_t pos = 0;
6422 buf[pos] = 0;
6424 while (status == INPUT_OK || status == INPUT_SKIP) {
6425 int key;
6427 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6428 wclrtoeol(status_win);
6430 key = get_input(pos + 1);
6431 switch (key) {
6432 case KEY_RETURN:
6433 case KEY_ENTER:
6434 case '\n':
6435 status = pos ? INPUT_STOP : INPUT_CANCEL;
6436 break;
6438 case KEY_BACKSPACE:
6439 if (pos > 0)
6440 buf[--pos] = 0;
6441 else
6442 status = INPUT_CANCEL;
6443 break;
6445 case KEY_ESC:
6446 status = INPUT_CANCEL;
6447 break;
6449 default:
6450 if (pos >= sizeof(buf)) {
6451 report("Input string too long");
6452 return NULL;
6453 }
6455 status = handler(data, buf, key);
6456 if (status == INPUT_OK)
6457 buf[pos++] = (char) key;
6458 }
6459 }
6461 /* Clear the status window */
6462 status_empty = FALSE;
6463 report("");
6465 if (status == INPUT_CANCEL)
6466 return NULL;
6468 buf[pos++] = 0;
6470 return buf;
6471 }
6473 static enum input_status
6474 prompt_yesno_handler(void *data, char *buf, int c)
6475 {
6476 if (c == 'y' || c == 'Y')
6477 return INPUT_STOP;
6478 if (c == 'n' || c == 'N')
6479 return INPUT_CANCEL;
6480 return INPUT_SKIP;
6481 }
6483 static bool
6484 prompt_yesno(const char *prompt)
6485 {
6486 char prompt2[SIZEOF_STR];
6488 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6489 return FALSE;
6491 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6492 }
6494 static enum input_status
6495 read_prompt_handler(void *data, char *buf, int c)
6496 {
6497 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6498 }
6500 static char *
6501 read_prompt(const char *prompt)
6502 {
6503 return prompt_input(prompt, read_prompt_handler, NULL);
6504 }
6506 /*
6507 * Repository properties
6508 */
6510 static struct ref *refs = NULL;
6511 static size_t refs_alloc = 0;
6512 static size_t refs_size = 0;
6514 /* Id <-> ref store */
6515 static struct ref ***id_refs = NULL;
6516 static size_t id_refs_alloc = 0;
6517 static size_t id_refs_size = 0;
6519 static int
6520 compare_refs(const void *ref1_, const void *ref2_)
6521 {
6522 const struct ref *ref1 = *(const struct ref **)ref1_;
6523 const struct ref *ref2 = *(const struct ref **)ref2_;
6525 if (ref1->tag != ref2->tag)
6526 return ref2->tag - ref1->tag;
6527 if (ref1->ltag != ref2->ltag)
6528 return ref2->ltag - ref2->ltag;
6529 if (ref1->head != ref2->head)
6530 return ref2->head - ref1->head;
6531 if (ref1->tracked != ref2->tracked)
6532 return ref2->tracked - ref1->tracked;
6533 if (ref1->remote != ref2->remote)
6534 return ref2->remote - ref1->remote;
6535 return strcmp(ref1->name, ref2->name);
6536 }
6538 static struct ref **
6539 get_refs(const char *id)
6540 {
6541 struct ref ***tmp_id_refs;
6542 struct ref **ref_list = NULL;
6543 size_t ref_list_alloc = 0;
6544 size_t ref_list_size = 0;
6545 size_t i;
6547 for (i = 0; i < id_refs_size; i++)
6548 if (!strcmp(id, id_refs[i][0]->id))
6549 return id_refs[i];
6551 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6552 sizeof(*id_refs));
6553 if (!tmp_id_refs)
6554 return NULL;
6556 id_refs = tmp_id_refs;
6558 for (i = 0; i < refs_size; i++) {
6559 struct ref **tmp;
6561 if (strcmp(id, refs[i].id))
6562 continue;
6564 tmp = realloc_items(ref_list, &ref_list_alloc,
6565 ref_list_size + 1, sizeof(*ref_list));
6566 if (!tmp) {
6567 if (ref_list)
6568 free(ref_list);
6569 return NULL;
6570 }
6572 ref_list = tmp;
6573 ref_list[ref_list_size] = &refs[i];
6574 /* XXX: The properties of the commit chains ensures that we can
6575 * safely modify the shared ref. The repo references will
6576 * always be similar for the same id. */
6577 ref_list[ref_list_size]->next = 1;
6579 ref_list_size++;
6580 }
6582 if (ref_list) {
6583 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6584 ref_list[ref_list_size - 1]->next = 0;
6585 id_refs[id_refs_size++] = ref_list;
6586 }
6588 return ref_list;
6589 }
6591 static int
6592 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6593 {
6594 struct ref *ref;
6595 bool tag = FALSE;
6596 bool ltag = FALSE;
6597 bool remote = FALSE;
6598 bool tracked = FALSE;
6599 bool check_replace = FALSE;
6600 bool head = FALSE;
6602 if (!prefixcmp(name, "refs/tags/")) {
6603 if (!suffixcmp(name, namelen, "^{}")) {
6604 namelen -= 3;
6605 name[namelen] = 0;
6606 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6607 check_replace = TRUE;
6608 } else {
6609 ltag = TRUE;
6610 }
6612 tag = TRUE;
6613 namelen -= STRING_SIZE("refs/tags/");
6614 name += STRING_SIZE("refs/tags/");
6616 } else if (!prefixcmp(name, "refs/remotes/")) {
6617 remote = TRUE;
6618 namelen -= STRING_SIZE("refs/remotes/");
6619 name += STRING_SIZE("refs/remotes/");
6620 tracked = !strcmp(opt_remote, name);
6622 } else if (!prefixcmp(name, "refs/heads/")) {
6623 namelen -= STRING_SIZE("refs/heads/");
6624 name += STRING_SIZE("refs/heads/");
6625 head = !strncmp(opt_head, name, namelen);
6627 } else if (!strcmp(name, "HEAD")) {
6628 string_ncopy(opt_head_rev, id, idlen);
6629 return OK;
6630 }
6632 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6633 /* it's an annotated tag, replace the previous SHA1 with the
6634 * resolved commit id; relies on the fact git-ls-remote lists
6635 * the commit id of an annotated tag right before the commit id
6636 * it points to. */
6637 refs[refs_size - 1].ltag = ltag;
6638 string_copy_rev(refs[refs_size - 1].id, id);
6640 return OK;
6641 }
6642 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6643 if (!refs)
6644 return ERR;
6646 ref = &refs[refs_size++];
6647 ref->name = malloc(namelen + 1);
6648 if (!ref->name)
6649 return ERR;
6651 strncpy(ref->name, name, namelen);
6652 ref->name[namelen] = 0;
6653 ref->head = head;
6654 ref->tag = tag;
6655 ref->ltag = ltag;
6656 ref->remote = remote;
6657 ref->tracked = tracked;
6658 string_copy_rev(ref->id, id);
6660 return OK;
6661 }
6663 static int
6664 load_refs(void)
6665 {
6666 static const char *ls_remote_argv[SIZEOF_ARG] = {
6667 "git", "ls-remote", opt_git_dir, NULL
6668 };
6669 static bool init = FALSE;
6671 if (!init) {
6672 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6673 init = TRUE;
6674 }
6676 if (!*opt_git_dir)
6677 return OK;
6679 while (refs_size > 0)
6680 free(refs[--refs_size].name);
6681 while (id_refs_size > 0)
6682 free(id_refs[--id_refs_size]);
6684 return run_io_load(ls_remote_argv, "\t", read_ref);
6685 }
6687 static void
6688 set_remote_branch(const char *name, const char *value, size_t valuelen)
6689 {
6690 if (!strcmp(name, ".remote")) {
6691 string_ncopy(opt_remote, value, valuelen);
6693 } else if (*opt_remote && !strcmp(name, ".merge")) {
6694 size_t from = strlen(opt_remote);
6696 if (!prefixcmp(value, "refs/heads/"))
6697 value += STRING_SIZE("refs/heads/");
6699 if (!string_format_from(opt_remote, &from, "/%s", value))
6700 opt_remote[0] = 0;
6701 }
6702 }
6704 static void
6705 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6706 {
6707 const char *argv[SIZEOF_ARG] = { name, "=" };
6708 int argc = 1 + (cmd == option_set_command);
6709 int error = ERR;
6711 if (!argv_from_string(argv, &argc, value))
6712 config_msg = "Too many option arguments";
6713 else
6714 error = cmd(argc, argv);
6716 if (error == ERR)
6717 warn("Option 'tig.%s': %s", name, config_msg);
6718 }
6720 static void
6721 set_work_tree(const char *value)
6722 {
6723 char cwd[SIZEOF_STR];
6725 if (!getcwd(cwd, sizeof(cwd)))
6726 die("Failed to get cwd path: %s", strerror(errno));
6727 if (chdir(opt_git_dir) < 0)
6728 die("Failed to chdir(%s): %s", strerror(errno));
6729 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6730 die("Failed to get git path: %s", strerror(errno));
6731 if (chdir(cwd) < 0)
6732 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6733 if (chdir(value) < 0)
6734 die("Failed to chdir(%s): %s", value, strerror(errno));
6735 if (!getcwd(cwd, sizeof(cwd)))
6736 die("Failed to get cwd path: %s", strerror(errno));
6737 if (setenv("GIT_WORK_TREE", cwd, TRUE) < 0)
6738 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6739 if (setenv("GIT_DIR", opt_git_dir, TRUE) < 0)
6740 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6741 opt_is_inside_work_tree = TRUE;
6742 }
6744 static int
6745 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6746 {
6747 if (!strcmp(name, "i18n.commitencoding"))
6748 string_ncopy(opt_encoding, value, valuelen);
6750 else if (!strcmp(name, "core.editor"))
6751 string_ncopy(opt_editor, value, valuelen);
6753 else if (!strcmp(name, "core.worktree"))
6754 set_work_tree(value);
6756 else if (!prefixcmp(name, "tig.color."))
6757 set_repo_config_option(name + 10, value, option_color_command);
6759 else if (!prefixcmp(name, "tig.bind."))
6760 set_repo_config_option(name + 9, value, option_bind_command);
6762 else if (!prefixcmp(name, "tig."))
6763 set_repo_config_option(name + 4, value, option_set_command);
6765 else if (*opt_head && !prefixcmp(name, "branch.") &&
6766 !strncmp(name + 7, opt_head, strlen(opt_head)))
6767 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6769 return OK;
6770 }
6772 static int
6773 load_git_config(void)
6774 {
6775 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6777 return run_io_load(config_list_argv, "=", read_repo_config_option);
6778 }
6780 static int
6781 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6782 {
6783 if (!opt_git_dir[0]) {
6784 string_ncopy(opt_git_dir, name, namelen);
6786 } else if (opt_is_inside_work_tree == -1) {
6787 /* This can be 3 different values depending on the
6788 * version of git being used. If git-rev-parse does not
6789 * understand --is-inside-work-tree it will simply echo
6790 * the option else either "true" or "false" is printed.
6791 * Default to true for the unknown case. */
6792 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6794 } else if (*name == '.') {
6795 string_ncopy(opt_cdup, name, namelen);
6797 } else {
6798 string_ncopy(opt_prefix, name, namelen);
6799 }
6801 return OK;
6802 }
6804 static int
6805 load_repo_info(void)
6806 {
6807 const char *head_argv[] = {
6808 "git", "symbolic-ref", "HEAD", NULL
6809 };
6810 const char *rev_parse_argv[] = {
6811 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6812 "--show-cdup", "--show-prefix", NULL
6813 };
6815 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6816 chomp_string(opt_head);
6817 if (!prefixcmp(opt_head, "refs/heads/")) {
6818 char *offset = opt_head + STRING_SIZE("refs/heads/");
6820 memmove(opt_head, offset, strlen(offset) + 1);
6821 }
6822 }
6824 return run_io_load(rev_parse_argv, "=", read_repo_info);
6825 }
6828 /*
6829 * Main
6830 */
6832 static const char usage[] =
6833 "tig " TIG_VERSION " (" __DATE__ ")\n"
6834 "\n"
6835 "Usage: tig [options] [revs] [--] [paths]\n"
6836 " or: tig show [options] [revs] [--] [paths]\n"
6837 " or: tig blame [rev] path\n"
6838 " or: tig status\n"
6839 " or: tig < [git command output]\n"
6840 "\n"
6841 "Options:\n"
6842 " -v, --version Show version and exit\n"
6843 " -h, --help Show help message and exit";
6845 static void __NORETURN
6846 quit(int sig)
6847 {
6848 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6849 if (cursed)
6850 endwin();
6851 exit(0);
6852 }
6854 static void __NORETURN
6855 die(const char *err, ...)
6856 {
6857 va_list args;
6859 endwin();
6861 va_start(args, err);
6862 fputs("tig: ", stderr);
6863 vfprintf(stderr, err, args);
6864 fputs("\n", stderr);
6865 va_end(args);
6867 exit(1);
6868 }
6870 static void
6871 warn(const char *msg, ...)
6872 {
6873 va_list args;
6875 va_start(args, msg);
6876 fputs("tig warning: ", stderr);
6877 vfprintf(stderr, msg, args);
6878 fputs("\n", stderr);
6879 va_end(args);
6880 }
6882 static enum request
6883 parse_options(int argc, const char *argv[])
6884 {
6885 enum request request = REQ_VIEW_MAIN;
6886 const char *subcommand;
6887 bool seen_dashdash = FALSE;
6888 /* XXX: This is vulnerable to the user overriding options
6889 * required for the main view parser. */
6890 const char *custom_argv[SIZEOF_ARG] = {
6891 "git", "log", "--no-color", "--pretty=raw", "--parents",
6892 "--topo-order", NULL
6893 };
6894 int i, j = 6;
6896 if (!isatty(STDIN_FILENO)) {
6897 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6898 return REQ_VIEW_PAGER;
6899 }
6901 if (argc <= 1)
6902 return REQ_NONE;
6904 subcommand = argv[1];
6905 if (!strcmp(subcommand, "status")) {
6906 if (argc > 2)
6907 warn("ignoring arguments after `%s'", subcommand);
6908 return REQ_VIEW_STATUS;
6910 } else if (!strcmp(subcommand, "blame")) {
6911 if (argc <= 2 || argc > 4)
6912 die("invalid number of options to blame\n\n%s", usage);
6914 i = 2;
6915 if (argc == 4) {
6916 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6917 i++;
6918 }
6920 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6921 return REQ_VIEW_BLAME;
6923 } else if (!strcmp(subcommand, "show")) {
6924 request = REQ_VIEW_DIFF;
6926 } else {
6927 subcommand = NULL;
6928 }
6930 if (subcommand) {
6931 custom_argv[1] = subcommand;
6932 j = 2;
6933 }
6935 for (i = 1 + !!subcommand; i < argc; i++) {
6936 const char *opt = argv[i];
6938 if (seen_dashdash || !strcmp(opt, "--")) {
6939 seen_dashdash = TRUE;
6941 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6942 printf("tig version %s\n", TIG_VERSION);
6943 quit(0);
6945 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6946 printf("%s\n", usage);
6947 quit(0);
6948 }
6950 custom_argv[j++] = opt;
6951 if (j >= ARRAY_SIZE(custom_argv))
6952 die("command too long");
6953 }
6955 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6956 die("Failed to format arguments");
6958 return request;
6959 }
6961 int
6962 main(int argc, const char *argv[])
6963 {
6964 enum request request = parse_options(argc, argv);
6965 struct view *view;
6966 size_t i;
6968 signal(SIGINT, quit);
6970 if (setlocale(LC_ALL, "")) {
6971 char *codeset = nl_langinfo(CODESET);
6973 string_ncopy(opt_codeset, codeset, strlen(codeset));
6974 }
6976 if (load_repo_info() == ERR)
6977 die("Failed to load repo info.");
6979 if (load_options() == ERR)
6980 die("Failed to load user config.");
6982 if (load_git_config() == ERR)
6983 die("Failed to load repo config.");
6985 /* Require a git repository unless when running in pager mode. */
6986 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6987 die("Not a git repository");
6989 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6990 opt_utf8 = FALSE;
6992 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6993 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6994 if (opt_iconv == ICONV_NONE)
6995 die("Failed to initialize character set conversion");
6996 }
6998 if (load_refs() == ERR)
6999 die("Failed to load refs.");
7001 foreach_view (view, i)
7002 argv_from_env(view->ops->argv, view->cmd_env);
7004 init_display();
7006 if (request != REQ_NONE)
7007 open_view(NULL, request, OPEN_PREPARED);
7008 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7010 while (view_driver(display[current_view], request)) {
7011 int key = get_input(0);
7013 view = display[current_view];
7014 request = get_keybinding(view->keymap, key);
7016 /* Some low-level request handling. This keeps access to
7017 * status_win restricted. */
7018 switch (request) {
7019 case REQ_PROMPT:
7020 {
7021 char *cmd = read_prompt(":");
7023 if (cmd && isdigit(*cmd)) {
7024 int lineno = view->lineno + 1;
7026 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7027 select_view_line(view, lineno - 1);
7028 report("");
7029 } else {
7030 report("Unable to parse '%s' as a line number", cmd);
7031 }
7033 } else if (cmd) {
7034 struct view *next = VIEW(REQ_VIEW_PAGER);
7035 const char *argv[SIZEOF_ARG] = { "git" };
7036 int argc = 1;
7038 /* When running random commands, initially show the
7039 * command in the title. However, it maybe later be
7040 * overwritten if a commit line is selected. */
7041 string_ncopy(next->ref, cmd, strlen(cmd));
7043 if (!argv_from_string(argv, &argc, cmd)) {
7044 report("Too many arguments");
7045 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7046 report("Failed to format command");
7047 } else {
7048 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7049 }
7050 }
7052 request = REQ_NONE;
7053 break;
7054 }
7055 case REQ_SEARCH:
7056 case REQ_SEARCH_BACK:
7057 {
7058 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7059 char *search = read_prompt(prompt);
7061 if (search)
7062 string_ncopy(opt_search, search, strlen(search));
7063 else if (*opt_search)
7064 request = request == REQ_SEARCH ?
7065 REQ_FIND_NEXT :
7066 REQ_FIND_PREV;
7067 else
7068 request = REQ_NONE;
7069 break;
7070 }
7071 default:
7072 break;
7073 }
7074 }
7076 quit(0);
7078 return 0;
7079 }