1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.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>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN
die(const char *err
, ...);
68 static void warn(const char *msg
, ...);
69 static void report(const char *msg
, ...);
70 static void set_nonblocking_input(bool loading
);
71 static size_t utf8_length(const char **string
, size_t col
, int *width
, size_t max_width
, int *trimmed
, bool reserve
);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define MAX(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. */
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)
100 #define ICONV_CONST /* nothing */
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 ")
109 #define MIN_VIEW_HEIGHT 4
111 #define NULL_ID "0000000000000000000000000000000000000000"
113 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 #define GIT_CONFIG "config"
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_RETURN '\r'
126 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
127 unsigned int head
:1; /* Is it the current HEAD? */
128 unsigned int tag
:1; /* Is it a tag? */
129 unsigned int ltag
:1; /* If so, is the tag local? */
130 unsigned int remote
:1; /* Is it a remote ref? */
131 unsigned int tracked
:1; /* Is it the remote for the current HEAD? */
132 char name
[1]; /* Ref name; tag or head names are shortened. */
136 char id
[SIZEOF_REV
]; /* Commit SHA1 ID */
137 size_t size
; /* Number of refs. */
138 struct ref
**refs
; /* References for this ID. */
141 static struct ref_list
*get_ref_list(const char *id
);
142 static void foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
);
143 static int load_refs(void);
146 FORMAT_ALL
, /* Perform replacement in all arguments. */
147 FORMAT_DASH
, /* Perform replacement up until "--". */
148 FORMAT_NONE
/* No replacement should be performed. */
151 static bool format_argv(const char *dst
[], const char *src
[], enum format_flags flags
);
160 typedef enum input_status (*input_handler
)(void *data
, char *buf
, int c
);
162 static char *prompt_input(const char *prompt
, input_handler handler
, void *data
);
163 static bool prompt_yesno(const char *prompt
);
171 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
199 string_ncopy_do(char *dst
, size_t dstlen
, const char *src
, size_t srclen
)
201 if (srclen
> dstlen
- 1)
204 strncpy(dst
, src
, srclen
);
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 string_expand(char *dst
, size_t dstlen
, const char *src
, int tabsize
)
227 for (size
= pos
= 0; size
< dstlen
- 1 && src
[pos
]; pos
++) {
228 if (src
[pos
] == '\t') {
229 size_t expanded
= tabsize
- (size
% tabsize
);
231 if (expanded
+ size
>= dstlen
- 1)
232 expanded
= dstlen
- size
- 1;
233 memcpy(dst
+ size
, " ", expanded
);
236 dst
[size
++] = src
[pos
];
244 chomp_string(char *name
)
248 while (isspace(*name
))
251 namelen
= strlen(name
) - 1;
252 while (namelen
> 0 && isspace(name
[namelen
]))
259 string_nformat(char *buf
, size_t bufsize
, size_t *bufpos
, const char *fmt
, ...)
262 size_t pos
= bufpos
? *bufpos
: 0;
265 pos
+= vsnprintf(buf
+ pos
, bufsize
- pos
, fmt
, args
);
271 return pos
>= bufsize
? FALSE
: TRUE
;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
281 string_enum_compare(const char *str1
, const char *str2
, int len
)
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i
= 0; i
< len
; i
++) {
289 if (toupper(str1
[i
]) == toupper(str2
[i
]))
292 if (string_enum_sep(str1
[i
]) &&
293 string_enum_sep(str2
[i
]))
296 return str1
[i
] - str2
[i
];
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 map_enum_do(const struct enum_map
*map
, size_t map_size
, int *value
, const char *name
)
313 size_t namelen
= strlen(name
);
316 for (i
= 0; i
< map_size
; i
++)
317 if (namelen
== map
[i
].namelen
&&
318 !string_enum_compare(name
, map
[i
].name
, namelen
)) {
319 *value
= map
[i
].value
;
326 #define map_enum(attr, map, name) \
327 map_enum_do(map, ARRAY_SIZE(map), attr, name)
329 #define prefixcmp(str1, str2) \
330 strncmp(str1, str2, STRING_SIZE(str2))
333 suffixcmp(const char *str
, int slen
, const char *suffix
)
335 size_t len
= slen
>= 0 ? slen
: strlen(str
);
336 size_t suffixlen
= strlen(suffix
);
338 return suffixlen
< len
? strcmp(str
+ len
- suffixlen
, suffix
) : -1;
343 mkdate(const time_t *time
)
345 static char buf
[DATE_COLS
+ 1];
349 return strftime(buf
, sizeof(buf
), DATE_FORMAT
, &tm
) ? buf
: NULL
;
354 argv_from_string(const char *argv
[SIZEOF_ARG
], int *argc
, char *cmd
)
358 while (*cmd
&& *argc
< SIZEOF_ARG
&& (valuelen
= strcspn(cmd
, " \t"))) {
359 bool advance
= cmd
[valuelen
] != 0;
362 argv
[(*argc
)++] = chomp_string(cmd
);
363 cmd
= chomp_string(cmd
+ valuelen
+ advance
);
366 if (*argc
< SIZEOF_ARG
)
368 return *argc
< SIZEOF_ARG
;
372 argv_from_env(const char **argv
, const char *name
)
374 char *env
= argv
? getenv(name
) : NULL
;
379 if (env
&& !argv_from_string(argv
, &argc
, env
))
380 die("Too many arguments in the `%s` environment variable", name
);
385 * Executing external commands.
389 IO_FD
, /* File descriptor based IO. */
390 IO_BG
, /* Execute command in the background. */
391 IO_FG
, /* Execute command with same std{in,out,err}. */
392 IO_RD
, /* Read only fork+exec IO. */
393 IO_WR
, /* Write only fork+exec IO. */
394 IO_AP
, /* Append fork+exec output to file. */
398 enum io_type type
; /* The requested type of pipe. */
399 const char *dir
; /* Directory from which to execute. */
400 pid_t pid
; /* Pipe for reading or writing. */
401 int pipe
; /* Pipe end for reading or writing. */
402 int error
; /* Error status. */
403 const char *argv
[SIZEOF_ARG
]; /* Shell command arguments. */
404 char *buf
; /* Read buffer. */
405 size_t bufalloc
; /* Allocated buffer size. */
406 size_t bufsize
; /* Buffer content size. */
407 char *bufpos
; /* Current buffer position. */
408 unsigned int eof
:1; /* Has end of file been reached. */
412 reset_io(struct io
*io
)
416 io
->buf
= io
->bufpos
= NULL
;
417 io
->bufalloc
= io
->bufsize
= 0;
423 init_io(struct io
*io
, const char *dir
, enum io_type type
)
431 init_io_rd(struct io
*io
, const char *argv
[], const char *dir
,
432 enum format_flags flags
)
434 init_io(io
, dir
, IO_RD
);
435 return format_argv(io
->argv
, argv
, flags
);
439 io_open(struct io
*io
, const char *name
)
441 init_io(io
, NULL
, IO_FD
);
442 io
->pipe
= *name
? open(name
, O_RDONLY
) : STDIN_FILENO
;
445 return io
->pipe
!= -1;
449 kill_io(struct io
*io
)
451 return io
->pid
== 0 || kill(io
->pid
, SIGKILL
) != -1;
455 done_io(struct io
*io
)
466 pid_t waiting
= waitpid(pid
, &status
, 0);
471 report("waitpid failed (%s)", strerror(errno
));
475 return waiting
== pid
&&
476 !WIFSIGNALED(status
) &&
478 !WEXITSTATUS(status
);
485 start_io(struct io
*io
)
487 int pipefds
[2] = { -1, -1 };
489 if (io
->type
== IO_FD
)
492 if ((io
->type
== IO_RD
|| io
->type
== IO_WR
) &&
495 else if (io
->type
== IO_AP
)
496 pipefds
[1] = io
->pipe
;
498 if ((io
->pid
= fork())) {
499 if (pipefds
[!(io
->type
== IO_WR
)] != -1)
500 close(pipefds
[!(io
->type
== IO_WR
)]);
502 io
->pipe
= pipefds
[!!(io
->type
== IO_WR
)];
507 if (io
->type
!= IO_FG
) {
508 int devnull
= open("/dev/null", O_RDWR
);
509 int readfd
= io
->type
== IO_WR
? pipefds
[0] : devnull
;
510 int writefd
= (io
->type
== IO_RD
|| io
->type
== IO_AP
)
511 ? pipefds
[1] : devnull
;
513 dup2(readfd
, STDIN_FILENO
);
514 dup2(writefd
, STDOUT_FILENO
);
515 dup2(devnull
, STDERR_FILENO
);
518 if (pipefds
[0] != -1)
520 if (pipefds
[1] != -1)
524 if (io
->dir
&& *io
->dir
&& chdir(io
->dir
) == -1)
525 die("Failed to change directory: %s", strerror(errno
));
527 execvp(io
->argv
[0], (char *const*) io
->argv
);
528 die("Failed to execute program: %s", strerror(errno
));
531 if (pipefds
[!!(io
->type
== IO_WR
)] != -1)
532 close(pipefds
[!!(io
->type
== IO_WR
)]);
537 run_io(struct io
*io
, const char **argv
, const char *dir
, enum io_type type
)
539 init_io(io
, dir
, type
);
540 if (!format_argv(io
->argv
, argv
, FORMAT_NONE
))
546 run_io_do(struct io
*io
)
548 return start_io(io
) && done_io(io
);
552 run_io_bg(const char **argv
)
556 init_io(&io
, NULL
, IO_BG
);
557 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
559 return run_io_do(&io
);
563 run_io_fg(const char **argv
, const char *dir
)
567 init_io(&io
, dir
, IO_FG
);
568 if (!format_argv(io
.argv
, argv
, FORMAT_NONE
))
570 return run_io_do(&io
);
574 run_io_append(const char **argv
, enum format_flags flags
, int fd
)
578 init_io(&io
, NULL
, IO_AP
);
580 if (format_argv(io
.argv
, argv
, flags
))
581 return run_io_do(&io
);
587 run_io_rd(struct io
*io
, const char **argv
, enum format_flags flags
)
589 return init_io_rd(io
, argv
, NULL
, flags
) && start_io(io
);
593 io_eof(struct io
*io
)
599 io_error(struct io
*io
)
605 io_strerror(struct io
*io
)
607 return strerror(io
->error
);
611 io_can_read(struct io
*io
)
613 struct timeval tv
= { 0, 500 };
617 FD_SET(io
->pipe
, &fds
);
619 return select(io
->pipe
+ 1, &fds
, NULL
, NULL
, &tv
) > 0;
623 io_read(struct io
*io
, void *buf
, size_t bufsize
)
626 ssize_t readsize
= read(io
->pipe
, buf
, bufsize
);
628 if (readsize
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
630 else if (readsize
== -1)
632 else if (readsize
== 0)
638 DEFINE_ALLOCATOR(realloc_io_buf
, char, BUFSIZ
)
641 io_get(struct io
*io
, int c
, bool can_read
)
647 if (io
->bufsize
> 0) {
648 eol
= memchr(io
->bufpos
, c
, io
->bufsize
);
650 char *line
= io
->bufpos
;
653 io
->bufpos
= eol
+ 1;
654 io
->bufsize
-= io
->bufpos
- line
;
661 io
->bufpos
[io
->bufsize
] = 0;
671 if (io
->bufsize
> 0 && io
->bufpos
> io
->buf
)
672 memmove(io
->buf
, io
->bufpos
, io
->bufsize
);
674 if (io
->bufalloc
== io
->bufsize
) {
675 if (!realloc_io_buf(&io
->buf
, io
->bufalloc
, BUFSIZ
))
677 io
->bufalloc
+= BUFSIZ
;
680 io
->bufpos
= io
->buf
;
681 readsize
= io_read(io
, io
->buf
+ io
->bufsize
, io
->bufalloc
- io
->bufsize
);
684 io
->bufsize
+= readsize
;
689 io_write(struct io
*io
, const void *buf
, size_t bufsize
)
693 while (!io_error(io
) && written
< bufsize
) {
696 size
= write(io
->pipe
, buf
+ written
, bufsize
- written
);
697 if (size
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
705 return written
== bufsize
;
709 io_read_buf(struct io
*io
, char buf
[], size_t bufsize
)
711 char *result
= io_get(io
, '\n', TRUE
);
714 result
= chomp_string(result
);
715 string_ncopy_do(buf
, bufsize
, result
, strlen(result
));
718 return done_io(io
) && result
;
722 run_io_buf(const char **argv
, char buf
[], size_t bufsize
)
726 return run_io_rd(&io
, argv
, FORMAT_NONE
) && io_read_buf(&io
, buf
, bufsize
);
730 io_load(struct io
*io
, const char *separators
,
731 int (*read_property
)(char *, size_t, char *, size_t))
739 while (state
== OK
&& (name
= io_get(io
, '\n', TRUE
))) {
744 name
= chomp_string(name
);
745 namelen
= strcspn(name
, separators
);
749 value
= chomp_string(name
+ namelen
+ 1);
750 valuelen
= strlen(value
);
757 state
= read_property(name
, namelen
, value
, valuelen
);
760 if (state
!= ERR
&& io_error(io
))
768 run_io_load(const char **argv
, const char *separators
,
769 int (*read_property
)(char *, size_t, char *, size_t))
773 return init_io_rd(&io
, argv
, NULL
, FORMAT_NONE
)
774 ? io_load(&io
, separators
, read_property
) : ERR
;
783 /* XXX: Keep the view request first and in sync with views[]. */ \
784 REQ_GROUP("View switching") \
785 REQ_(VIEW_MAIN, "Show main view"), \
786 REQ_(VIEW_DIFF, "Show diff view"), \
787 REQ_(VIEW_LOG, "Show log view"), \
788 REQ_(VIEW_TREE, "Show tree view"), \
789 REQ_(VIEW_BLOB, "Show blob view"), \
790 REQ_(VIEW_BLAME, "Show blame view"), \
791 REQ_(VIEW_BRANCH, "Show branch view"), \
792 REQ_(VIEW_HELP, "Show help page"), \
793 REQ_(VIEW_PAGER, "Show pager view"), \
794 REQ_(VIEW_STATUS, "Show status view"), \
795 REQ_(VIEW_STAGE, "Show stage view"), \
797 REQ_GROUP("View manipulation") \
798 REQ_(ENTER, "Enter current line and scroll"), \
799 REQ_(NEXT, "Move to next"), \
800 REQ_(PREVIOUS, "Move to previous"), \
801 REQ_(PARENT, "Move to parent"), \
802 REQ_(VIEW_NEXT, "Move focus to next view"), \
803 REQ_(REFRESH, "Reload and refresh"), \
804 REQ_(MAXIMIZE, "Maximize the current view"), \
805 REQ_(VIEW_CLOSE, "Close the current view"), \
806 REQ_(QUIT, "Close all views and quit"), \
808 REQ_GROUP("View specific requests") \
809 REQ_(STATUS_UPDATE, "Update file status"), \
810 REQ_(STATUS_REVERT, "Revert file changes"), \
811 REQ_(STATUS_MERGE, "Merge file using external tool"), \
812 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
814 REQ_GROUP("Cursor navigation") \
815 REQ_(MOVE_UP, "Move cursor one line up"), \
816 REQ_(MOVE_DOWN, "Move cursor one line down"), \
817 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
818 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
819 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
820 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
822 REQ_GROUP("Scrolling") \
823 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
824 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
825 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
826 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
827 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
828 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
830 REQ_GROUP("Searching") \
831 REQ_(SEARCH, "Search the view"), \
832 REQ_(SEARCH_BACK, "Search backwards in the view"), \
833 REQ_(FIND_NEXT, "Find next search match"), \
834 REQ_(FIND_PREV, "Find previous search match"), \
836 REQ_GROUP("Option manipulation") \
837 REQ_(OPTIONS, "Open option menu"), \
838 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
839 REQ_(TOGGLE_DATE, "Toggle date display"), \
840 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
841 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
842 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
843 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
844 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
847 REQ_(PROMPT, "Bring up the prompt"), \
848 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
849 REQ_(SHOW_VERSION, "Show version information"), \
850 REQ_(STOP_LOADING, "Stop all loading views"), \
851 REQ_(EDIT, "Open in editor"), \
852 REQ_(NONE, "Do nothing")
855 /* User action requests. */
857 #define REQ_GROUP(help)
858 #define REQ_(req, help) REQ_##req
860 /* Offset all requests to avoid conflicts with ncurses getch values. */
861 REQ_OFFSET
= KEY_MAX
+ 1,
868 struct request_info
{
869 enum request request
;
875 static const struct request_info req_info
[] = {
876 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
877 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
884 get_request(const char *name
)
886 int namelen
= strlen(name
);
889 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++)
890 if (req_info
[i
].namelen
== namelen
&&
891 !string_enum_compare(req_info
[i
].name
, name
, namelen
))
892 return req_info
[i
].request
;
902 /* Option and state variables. */
903 static bool opt_date
= TRUE
;
904 static bool opt_author
= TRUE
;
905 static bool opt_line_number
= FALSE
;
906 static bool opt_line_graphics
= TRUE
;
907 static bool opt_rev_graph
= FALSE
;
908 static bool opt_show_refs
= TRUE
;
909 static int opt_num_interval
= 5;
910 static double opt_hscroll
= 0.50;
911 static double opt_scale_split_view
= 2.0 / 3.0;
912 static int opt_tab_size
= 8;
913 static int opt_author_cols
= 19;
914 static char opt_path
[SIZEOF_STR
] = "";
915 static char opt_file
[SIZEOF_STR
] = "";
916 static char opt_ref
[SIZEOF_REF
] = "";
917 static char opt_head
[SIZEOF_REF
] = "";
918 static char opt_head_rev
[SIZEOF_REV
] = "";
919 static char opt_remote
[SIZEOF_REF
] = "";
920 static char opt_encoding
[20] = "UTF-8";
921 static bool opt_utf8
= TRUE
;
922 static char opt_codeset
[20] = "UTF-8";
923 static iconv_t opt_iconv
= ICONV_NONE
;
924 static char opt_search
[SIZEOF_STR
] = "";
925 static char opt_cdup
[SIZEOF_STR
] = "";
926 static char opt_prefix
[SIZEOF_STR
] = "";
927 static char opt_git_dir
[SIZEOF_STR
] = "";
928 static signed char opt_is_inside_work_tree
= -1; /* set to TRUE or FALSE */
929 static char opt_editor
[SIZEOF_STR
] = "";
930 static FILE *opt_tty
= NULL
;
932 #define is_initial_commit() (!*opt_head_rev)
933 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
937 * Line-oriented content detection.
941 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
944 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
945 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
946 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
956 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
960 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
961 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
962 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
963 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
965 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
967 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
968 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
969 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
970 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
971 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
972 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
973 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
974 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
975 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
976 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
977 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
978 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
979 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
980 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
984 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
985 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
986 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
987 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
988 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
989 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
990 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
991 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
992 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
993 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
994 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
995 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
998 #define LINE(type, line, fg, bg, attr) \
1006 const char *name
; /* Option name. */
1007 int namelen
; /* Size of option name. */
1008 const char *line
; /* The start of line to match. */
1009 int linelen
; /* Size of string to match. */
1010 int fg
, bg
, attr
; /* Color and text attributes for the lines. */
1013 static struct line_info line_info
[] = {
1014 #define LINE(type, line, fg, bg, attr) \
1015 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1020 static enum line_type
1021 get_line_type(const char *line
)
1023 int linelen
= strlen(line
);
1024 enum line_type type
;
1026 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1027 /* Case insensitive search matches Signed-off-by lines better. */
1028 if (linelen
>= line_info
[type
].linelen
&&
1029 !strncasecmp(line_info
[type
].line
, line
, line_info
[type
].linelen
))
1032 return LINE_DEFAULT
;
1036 get_line_attr(enum line_type type
)
1038 assert(type
< ARRAY_SIZE(line_info
));
1039 return COLOR_PAIR(type
) | line_info
[type
].attr
;
1042 static struct line_info
*
1043 get_line_info(const char *name
)
1045 size_t namelen
= strlen(name
);
1046 enum line_type type
;
1048 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++)
1049 if (namelen
== line_info
[type
].namelen
&&
1050 !string_enum_compare(line_info
[type
].name
, name
, namelen
))
1051 return &line_info
[type
];
1059 int default_bg
= line_info
[LINE_DEFAULT
].bg
;
1060 int default_fg
= line_info
[LINE_DEFAULT
].fg
;
1061 enum line_type type
;
1065 if (assume_default_colors(default_fg
, default_bg
) == ERR
) {
1066 default_bg
= COLOR_BLACK
;
1067 default_fg
= COLOR_WHITE
;
1070 for (type
= 0; type
< ARRAY_SIZE(line_info
); type
++) {
1071 struct line_info
*info
= &line_info
[type
];
1072 int bg
= info
->bg
== COLOR_DEFAULT
? default_bg
: info
->bg
;
1073 int fg
= info
->fg
== COLOR_DEFAULT
? default_fg
: info
->fg
;
1075 init_pair(type
, fg
, bg
);
1080 enum line_type type
;
1083 unsigned int selected
:1;
1084 unsigned int dirty
:1;
1085 unsigned int cleareol
:1;
1087 void *data
; /* User data */
1097 enum request request
;
1100 static const struct keybinding default_keybindings
[] = {
1101 /* View switching */
1102 { 'm', REQ_VIEW_MAIN
},
1103 { 'd', REQ_VIEW_DIFF
},
1104 { 'l', REQ_VIEW_LOG
},
1105 { 't', REQ_VIEW_TREE
},
1106 { 'f', REQ_VIEW_BLOB
},
1107 { 'B', REQ_VIEW_BLAME
},
1108 { 'H', REQ_VIEW_BRANCH
},
1109 { 'p', REQ_VIEW_PAGER
},
1110 { 'h', REQ_VIEW_HELP
},
1111 { 'S', REQ_VIEW_STATUS
},
1112 { 'c', REQ_VIEW_STAGE
},
1114 /* View manipulation */
1115 { 'q', REQ_VIEW_CLOSE
},
1116 { KEY_TAB
, REQ_VIEW_NEXT
},
1117 { KEY_RETURN
, REQ_ENTER
},
1118 { KEY_UP
, REQ_PREVIOUS
},
1119 { KEY_DOWN
, REQ_NEXT
},
1120 { 'R', REQ_REFRESH
},
1121 { KEY_F(5), REQ_REFRESH
},
1122 { 'O', REQ_MAXIMIZE
},
1124 /* Cursor navigation */
1125 { 'k', REQ_MOVE_UP
},
1126 { 'j', REQ_MOVE_DOWN
},
1127 { KEY_HOME
, REQ_MOVE_FIRST_LINE
},
1128 { KEY_END
, REQ_MOVE_LAST_LINE
},
1129 { KEY_NPAGE
, REQ_MOVE_PAGE_DOWN
},
1130 { ' ', REQ_MOVE_PAGE_DOWN
},
1131 { KEY_PPAGE
, REQ_MOVE_PAGE_UP
},
1132 { 'b', REQ_MOVE_PAGE_UP
},
1133 { '-', REQ_MOVE_PAGE_UP
},
1136 { KEY_LEFT
, REQ_SCROLL_LEFT
},
1137 { KEY_RIGHT
, REQ_SCROLL_RIGHT
},
1138 { KEY_IC
, REQ_SCROLL_LINE_UP
},
1139 { KEY_DC
, REQ_SCROLL_LINE_DOWN
},
1140 { 'w', REQ_SCROLL_PAGE_UP
},
1141 { 's', REQ_SCROLL_PAGE_DOWN
},
1144 { '/', REQ_SEARCH
},
1145 { '?', REQ_SEARCH_BACK
},
1146 { 'n', REQ_FIND_NEXT
},
1147 { 'N', REQ_FIND_PREV
},
1151 { 'z', REQ_STOP_LOADING
},
1152 { 'v', REQ_SHOW_VERSION
},
1153 { 'r', REQ_SCREEN_REDRAW
},
1154 { 'o', REQ_OPTIONS
},
1155 { '.', REQ_TOGGLE_LINENO
},
1156 { 'D', REQ_TOGGLE_DATE
},
1157 { 'A', REQ_TOGGLE_AUTHOR
},
1158 { 'g', REQ_TOGGLE_REV_GRAPH
},
1159 { 'F', REQ_TOGGLE_REFS
},
1160 { 'I', REQ_TOGGLE_SORT_ORDER
},
1161 { 'i', REQ_TOGGLE_SORT_FIELD
},
1162 { ':', REQ_PROMPT
},
1163 { 'u', REQ_STATUS_UPDATE
},
1164 { '!', REQ_STATUS_REVERT
},
1165 { 'M', REQ_STATUS_MERGE
},
1166 { '@', REQ_STAGE_NEXT
},
1167 { ',', REQ_PARENT
},
1171 #define KEYMAP_INFO \
1186 #define KEYMAP_(name) KEYMAP_##name
1191 static const struct enum_map keymap_table
[] = {
1192 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1197 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1199 struct keybinding_table
{
1200 struct keybinding
*data
;
1204 static struct keybinding_table keybindings
[ARRAY_SIZE(keymap_table
)];
1207 add_keybinding(enum keymap keymap
, enum request request
, int key
)
1209 struct keybinding_table
*table
= &keybindings
[keymap
];
1211 table
->data
= realloc(table
->data
, (table
->size
+ 1) * sizeof(*table
->data
));
1213 die("Failed to allocate keybinding");
1214 table
->data
[table
->size
].alias
= key
;
1215 table
->data
[table
->size
++].request
= request
;
1218 /* Looks for a key binding first in the given map, then in the generic map, and
1219 * lastly in the default keybindings. */
1221 get_keybinding(enum keymap keymap
, int key
)
1225 for (i
= 0; i
< keybindings
[keymap
].size
; i
++)
1226 if (keybindings
[keymap
].data
[i
].alias
== key
)
1227 return keybindings
[keymap
].data
[i
].request
;
1229 for (i
= 0; i
< keybindings
[KEYMAP_GENERIC
].size
; i
++)
1230 if (keybindings
[KEYMAP_GENERIC
].data
[i
].alias
== key
)
1231 return keybindings
[KEYMAP_GENERIC
].data
[i
].request
;
1233 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++)
1234 if (default_keybindings
[i
].alias
== key
)
1235 return default_keybindings
[i
].request
;
1237 return (enum request
) key
;
1246 static const struct key key_table
[] = {
1247 { "Enter", KEY_RETURN
},
1249 { "Backspace", KEY_BACKSPACE
},
1251 { "Escape", KEY_ESC
},
1252 { "Left", KEY_LEFT
},
1253 { "Right", KEY_RIGHT
},
1255 { "Down", KEY_DOWN
},
1256 { "Insert", KEY_IC
},
1257 { "Delete", KEY_DC
},
1259 { "Home", KEY_HOME
},
1261 { "PageUp", KEY_PPAGE
},
1262 { "PageDown", KEY_NPAGE
},
1272 { "F10", KEY_F(10) },
1273 { "F11", KEY_F(11) },
1274 { "F12", KEY_F(12) },
1278 get_key_value(const char *name
)
1282 for (i
= 0; i
< ARRAY_SIZE(key_table
); i
++)
1283 if (!strcasecmp(key_table
[i
].name
, name
))
1284 return key_table
[i
].value
;
1286 if (strlen(name
) == 1 && isprint(*name
))
1293 get_key_name(int key_value
)
1295 static char key_char
[] = "'X'";
1296 const char *seq
= NULL
;
1299 for (key
= 0; key
< ARRAY_SIZE(key_table
); key
++)
1300 if (key_table
[key
].value
== key_value
)
1301 seq
= key_table
[key
].name
;
1305 isprint(key_value
)) {
1306 key_char
[1] = (char) key_value
;
1310 return seq
? seq
: "(no key)";
1314 get_key(enum request request
)
1316 static char buf
[BUFSIZ
];
1323 for (i
= 0; i
< ARRAY_SIZE(default_keybindings
); i
++) {
1324 const struct keybinding
*keybinding
= &default_keybindings
[i
];
1326 if (keybinding
->request
!= request
)
1329 if (!string_format_from(buf
, &pos
, "%s%s", sep
,
1330 get_key_name(keybinding
->alias
)))
1331 return "Too many keybindings!";
1338 struct run_request
{
1341 const char *argv
[SIZEOF_ARG
];
1344 static struct run_request
*run_request
;
1345 static size_t run_requests
;
1347 DEFINE_ALLOCATOR(realloc_run_requests
, struct run_request
, 8)
1350 add_run_request(enum keymap keymap
, int key
, int argc
, const char **argv
)
1352 struct run_request
*req
;
1354 if (argc
>= ARRAY_SIZE(req
->argv
) - 1)
1357 if (!realloc_run_requests(&run_request
, run_requests
, 1))
1360 req
= &run_request
[run_requests
];
1361 req
->keymap
= keymap
;
1363 req
->argv
[0] = NULL
;
1365 if (!format_argv(req
->argv
, argv
, FORMAT_NONE
))
1368 return REQ_NONE
+ ++run_requests
;
1371 static struct run_request
*
1372 get_run_request(enum request request
)
1374 if (request
<= REQ_NONE
)
1376 return &run_request
[request
- REQ_NONE
- 1];
1380 add_builtin_run_requests(void)
1382 const char *cherry_pick
[] = { "git", "cherry-pick", "%(commit)", NULL
};
1383 const char *commit
[] = { "git", "commit", NULL
};
1384 const char *gc
[] = { "git", "gc", NULL
};
1391 { KEYMAP_MAIN
, 'C', ARRAY_SIZE(cherry_pick
) - 1, cherry_pick
},
1392 { KEYMAP_STATUS
, 'C', ARRAY_SIZE(commit
) - 1, commit
},
1393 { KEYMAP_GENERIC
, 'G', ARRAY_SIZE(gc
) - 1, gc
},
1397 for (i
= 0; i
< ARRAY_SIZE(reqs
); i
++) {
1400 req
= add_run_request(reqs
[i
].keymap
, reqs
[i
].key
, reqs
[i
].argc
, reqs
[i
].argv
);
1401 if (req
!= REQ_NONE
)
1402 add_keybinding(reqs
[i
].keymap
, req
, reqs
[i
].key
);
1407 * User config file handling.
1410 static int config_lineno
;
1411 static bool config_errors
;
1412 static const char *config_msg
;
1414 static const struct enum_map color_map
[] = {
1415 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1427 static const struct enum_map attr_map
[] = {
1428 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1435 ATTR_MAP(UNDERLINE
),
1438 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1440 static int parse_step(double *opt
, const char *arg
)
1443 if (!strchr(arg
, '%'))
1446 /* "Shift down" so 100% and 1 does not conflict. */
1447 *opt
= (*opt
- 1) / 100;
1450 config_msg
= "Step value larger than 100%";
1455 config_msg
= "Invalid step value";
1462 parse_int(int *opt
, const char *arg
, int min
, int max
)
1464 int value
= atoi(arg
);
1466 if (min
<= value
&& value
<= max
) {
1471 config_msg
= "Integer value out of bound";
1476 set_color(int *color
, const char *name
)
1478 if (map_enum(color
, color_map
, name
))
1480 if (!prefixcmp(name
, "color"))
1481 return parse_int(color
, name
+ 5, 0, 255) == OK
;
1485 /* Wants: object fgcolor bgcolor [attribute] */
1487 option_color_command(int argc
, const char *argv
[])
1489 struct line_info
*info
;
1491 if (argc
!= 3 && argc
!= 4) {
1492 config_msg
= "Wrong number of arguments given to color command";
1496 info
= get_line_info(argv
[0]);
1498 static const struct enum_map obsolete
[] = {
1499 ENUM_MAP("main-delim", LINE_DELIMITER
),
1500 ENUM_MAP("main-date", LINE_DATE
),
1501 ENUM_MAP("main-author", LINE_AUTHOR
),
1505 if (!map_enum(&index
, obsolete
, argv
[0])) {
1506 config_msg
= "Unknown color name";
1509 info
= &line_info
[index
];
1512 if (!set_color(&info
->fg
, argv
[1]) ||
1513 !set_color(&info
->bg
, argv
[2])) {
1514 config_msg
= "Unknown color";
1518 if (argc
== 4 && !set_attribute(&info
->attr
, argv
[3])) {
1519 config_msg
= "Unknown attribute";
1526 static int parse_bool(bool *opt
, const char *arg
)
1528 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
1534 parse_string(char *opt
, const char *arg
, size_t optsize
)
1536 int arglen
= strlen(arg
);
1541 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0]) {
1542 config_msg
= "Unmatched quotation";
1545 arg
+= 1; arglen
-= 2;
1547 string_ncopy_do(opt
, optsize
, arg
, arglen
);
1552 /* Wants: name = value */
1554 option_set_command(int argc
, const char *argv
[])
1557 config_msg
= "Wrong number of arguments given to set command";
1561 if (strcmp(argv
[1], "=")) {
1562 config_msg
= "No value assigned";
1566 if (!strcmp(argv
[0], "show-author"))
1567 return parse_bool(&opt_author
, argv
[2]);
1569 if (!strcmp(argv
[0], "show-date"))
1570 return parse_bool(&opt_date
, argv
[2]);
1572 if (!strcmp(argv
[0], "show-rev-graph"))
1573 return parse_bool(&opt_rev_graph
, argv
[2]);
1575 if (!strcmp(argv
[0], "show-refs"))
1576 return parse_bool(&opt_show_refs
, argv
[2]);
1578 if (!strcmp(argv
[0], "show-line-numbers"))
1579 return parse_bool(&opt_line_number
, argv
[2]);
1581 if (!strcmp(argv
[0], "line-graphics"))
1582 return parse_bool(&opt_line_graphics
, argv
[2]);
1584 if (!strcmp(argv
[0], "line-number-interval"))
1585 return parse_int(&opt_num_interval
, argv
[2], 1, 1024);
1587 if (!strcmp(argv
[0], "author-width"))
1588 return parse_int(&opt_author_cols
, argv
[2], 0, 1024);
1590 if (!strcmp(argv
[0], "horizontal-scroll"))
1591 return parse_step(&opt_hscroll
, argv
[2]);
1593 if (!strcmp(argv
[0], "split-view-height"))
1594 return parse_step(&opt_scale_split_view
, argv
[2]);
1596 if (!strcmp(argv
[0], "tab-size"))
1597 return parse_int(&opt_tab_size
, argv
[2], 1, 1024);
1599 if (!strcmp(argv
[0], "commit-encoding"))
1600 return parse_string(opt_encoding
, argv
[2], sizeof(opt_encoding
));
1602 config_msg
= "Unknown variable name";
1606 /* Wants: mode request key */
1608 option_bind_command(int argc
, const char *argv
[])
1610 enum request request
;
1615 config_msg
= "Wrong number of arguments given to bind command";
1619 if (set_keymap(&keymap
, argv
[0]) == ERR
) {
1620 config_msg
= "Unknown key map";
1624 key
= get_key_value(argv
[1]);
1626 config_msg
= "Unknown key";
1630 request
= get_request(argv
[2]);
1631 if (request
== REQ_NONE
) {
1632 static const struct enum_map obsolete
[] = {
1633 ENUM_MAP("cherry-pick", REQ_NONE
),
1634 ENUM_MAP("screen-resize", REQ_NONE
),
1635 ENUM_MAP("tree-parent", REQ_PARENT
),
1639 if (map_enum(&alias
, obsolete
, argv
[2])) {
1640 if (alias
!= REQ_NONE
)
1641 add_keybinding(keymap
, alias
, key
);
1642 config_msg
= "Obsolete request name";
1646 if (request
== REQ_NONE
&& *argv
[2]++ == '!')
1647 request
= add_run_request(keymap
, key
, argc
- 2, argv
+ 2);
1648 if (request
== REQ_NONE
) {
1649 config_msg
= "Unknown request name";
1653 add_keybinding(keymap
, request
, key
);
1659 set_option(const char *opt
, char *value
)
1661 const char *argv
[SIZEOF_ARG
];
1664 if (!argv_from_string(argv
, &argc
, value
)) {
1665 config_msg
= "Too many option arguments";
1669 if (!strcmp(opt
, "color"))
1670 return option_color_command(argc
, argv
);
1672 if (!strcmp(opt
, "set"))
1673 return option_set_command(argc
, argv
);
1675 if (!strcmp(opt
, "bind"))
1676 return option_bind_command(argc
, argv
);
1678 config_msg
= "Unknown option command";
1683 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
)
1688 config_msg
= "Internal error";
1690 /* Check for comment markers, since read_properties() will
1691 * only ensure opt and value are split at first " \t". */
1692 optlen
= strcspn(opt
, "#");
1696 if (opt
[optlen
] != 0) {
1697 config_msg
= "No option value";
1701 /* Look for comment endings in the value. */
1702 size_t len
= strcspn(value
, "#");
1704 if (len
< valuelen
) {
1706 value
[valuelen
] = 0;
1709 status
= set_option(opt
, value
);
1712 if (status
== ERR
) {
1713 warn("Error on line %d, near '%.*s': %s",
1714 config_lineno
, (int) optlen
, opt
, config_msg
);
1715 config_errors
= TRUE
;
1718 /* Always keep going if errors are encountered. */
1723 load_option_file(const char *path
)
1727 /* It's OK that the file doesn't exist. */
1728 if (!io_open(&io
, path
))
1732 config_errors
= FALSE
;
1734 if (io_load(&io
, " \t", read_option
) == ERR
||
1735 config_errors
== TRUE
)
1736 warn("Errors while loading %s.", path
);
1742 const char *home
= getenv("HOME");
1743 const char *tigrc_user
= getenv("TIGRC_USER");
1744 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
1745 char buf
[SIZEOF_STR
];
1747 add_builtin_run_requests();
1750 tigrc_system
= SYSCONFDIR
"/tigrc";
1751 load_option_file(tigrc_system
);
1754 if (!home
|| !string_format(buf
, "%s/.tigrc", home
))
1758 load_option_file(tigrc_user
);
1771 /* The display array of active views and the index of the current view. */
1772 static struct view
*display
[2];
1773 static unsigned int current_view
;
1775 #define foreach_displayed_view(view, i) \
1776 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1778 #define displayed_views() (display[1] != NULL ? 2 : 1)
1780 /* Current head and commit ID */
1781 static char ref_blob
[SIZEOF_REF
] = "";
1782 static char ref_commit
[SIZEOF_REF
] = "HEAD";
1783 static char ref_head
[SIZEOF_REF
] = "HEAD";
1786 const char *name
; /* View name */
1787 const char *cmd_env
; /* Command line set via environment */
1788 const char *id
; /* Points to either of ref_{head,commit,blob} */
1790 struct view_ops
*ops
; /* View operations */
1792 enum keymap keymap
; /* What keymap does this view have */
1793 bool git_dir
; /* Whether the view requires a git directory. */
1795 char ref
[SIZEOF_REF
]; /* Hovered commit reference */
1796 char vid
[SIZEOF_REF
]; /* View ID. Set to id member when updating. */
1798 int height
, width
; /* The width and height of the main window */
1799 WINDOW
*win
; /* The main window */
1800 WINDOW
*title
; /* The title window living below the main window */
1803 unsigned long offset
; /* Offset of the window top */
1804 unsigned long yoffset
; /* Offset from the window side. */
1805 unsigned long lineno
; /* Current line number */
1806 unsigned long p_offset
; /* Previous offset of the window top */
1807 unsigned long p_yoffset
;/* Previous offset from the window side */
1808 unsigned long p_lineno
; /* Previous current line number */
1809 bool p_restore
; /* Should the previous position be restored. */
1812 char grep
[SIZEOF_STR
]; /* Search string */
1813 regex_t
*regex
; /* Pre-compiled regexp */
1815 /* If non-NULL, points to the view that opened this view. If this view
1816 * is closed tig will switch back to the parent view. */
1817 struct view
*parent
;
1820 size_t lines
; /* Total number of lines */
1821 struct line
*line
; /* Line index */
1822 unsigned int digits
; /* Number of digits in the lines member. */
1825 struct line
*curline
; /* Line currently being drawn. */
1826 enum line_type curtype
; /* Attribute currently used for drawing. */
1827 unsigned long col
; /* Column when drawing. */
1828 bool has_scrolled
; /* View was scrolled. */
1838 /* What type of content being displayed. Used in the title bar. */
1840 /* Default command arguments. */
1842 /* Open and reads in all view content. */
1843 bool (*open
)(struct view
*view
);
1844 /* Read one line; updates view->line. */
1845 bool (*read
)(struct view
*view
, char *data
);
1846 /* Draw one line; @lineno must be < view->height. */
1847 bool (*draw
)(struct view
*view
, struct line
*line
, unsigned int lineno
);
1848 /* Depending on view handle a special requests. */
1849 enum request (*request
)(struct view
*view
, enum request request
, struct line
*line
);
1850 /* Search for regexp in a line. */
1851 bool (*grep
)(struct view
*view
, struct line
*line
);
1853 void (*select
)(struct view
*view
, struct line
*line
);
1856 static struct view_ops blame_ops
;
1857 static struct view_ops blob_ops
;
1858 static struct view_ops diff_ops
;
1859 static struct view_ops help_ops
;
1860 static struct view_ops log_ops
;
1861 static struct view_ops main_ops
;
1862 static struct view_ops pager_ops
;
1863 static struct view_ops stage_ops
;
1864 static struct view_ops status_ops
;
1865 static struct view_ops tree_ops
;
1866 static struct view_ops branch_ops
;
1868 #define VIEW_STR(name, env, ref, ops, map, git) \
1869 { name, #env, ref, ops, map, git }
1871 #define VIEW_(id, name, ops, git, ref) \
1872 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1875 static struct view views
[] = {
1876 VIEW_(MAIN
, "main", &main_ops
, TRUE
, ref_head
),
1877 VIEW_(DIFF
, "diff", &diff_ops
, TRUE
, ref_commit
),
1878 VIEW_(LOG
, "log", &log_ops
, TRUE
, ref_head
),
1879 VIEW_(TREE
, "tree", &tree_ops
, TRUE
, ref_commit
),
1880 VIEW_(BLOB
, "blob", &blob_ops
, TRUE
, ref_blob
),
1881 VIEW_(BLAME
, "blame", &blame_ops
, TRUE
, ref_commit
),
1882 VIEW_(BRANCH
, "branch", &branch_ops
, TRUE
, ref_head
),
1883 VIEW_(HELP
, "help", &help_ops
, FALSE
, ""),
1884 VIEW_(PAGER
, "pager", &pager_ops
, FALSE
, "stdin"),
1885 VIEW_(STATUS
, "status", &status_ops
, TRUE
, ""),
1886 VIEW_(STAGE
, "stage", &stage_ops
, TRUE
, ""),
1889 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1890 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1892 #define foreach_view(view, i) \
1893 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1895 #define view_is_displayed(view) \
1896 (view == display[0] || view == display[1])
1903 static chtype line_graphics
[] = {
1904 /* LINE_GRAPHIC_VLINE: */ '|'
1908 set_view_attr(struct view
*view
, enum line_type type
)
1910 if (!view
->curline
->selected
&& view
->curtype
!= type
) {
1911 wattrset(view
->win
, get_line_attr(type
));
1912 wchgat(view
->win
, -1, 0, type
, NULL
);
1913 view
->curtype
= type
;
1918 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
1919 int max_len
, bool use_tilde
)
1923 int trimmed
= FALSE
;
1924 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
1930 len
= utf8_length(&string
, skip
, &col
, max_len
, &trimmed
, use_tilde
);
1932 col
= len
= strlen(string
);
1933 if (len
> max_len
) {
1937 col
= len
= max_len
;
1942 set_view_attr(view
, type
);
1944 waddnstr(view
->win
, string
, len
);
1945 if (trimmed
&& use_tilde
) {
1946 set_view_attr(view
, LINE_DELIMITER
);
1947 waddch(view
->win
, '~');
1955 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
1957 static char space
[] = " ";
1960 spaces
= MIN(max
, spaces
);
1962 while (spaces
> 0) {
1963 int len
= MIN(spaces
, sizeof(space
) - 1);
1965 col
+= draw_chars(view
, type
, space
, len
, FALSE
);
1973 draw_text(struct view
*view
, enum line_type type
, const char *string
, bool trim
)
1975 view
->col
+= draw_chars(view
, type
, string
, view
->width
+ view
->yoffset
- view
->col
, trim
);
1976 return view
->width
+ view
->yoffset
<= view
->col
;
1980 draw_graphic(struct view
*view
, enum line_type type
, chtype graphic
[], size_t size
)
1982 size_t skip
= view
->yoffset
> view
->col
? view
->yoffset
- view
->col
: 0;
1983 int max
= view
->width
+ view
->yoffset
- view
->col
;
1989 set_view_attr(view
, type
);
1990 /* Using waddch() instead of waddnstr() ensures that
1991 * they'll be rendered correctly for the cursor line. */
1992 for (i
= skip
; i
< size
; i
++)
1993 waddch(view
->win
, graphic
[i
]);
1996 if (size
< max
&& skip
<= size
)
1997 waddch(view
->win
, ' ');
2000 return view
->width
+ view
->yoffset
<= view
->col
;
2004 draw_field(struct view
*view
, enum line_type type
, const char *text
, int len
, bool trim
)
2006 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, len
);
2010 col
= draw_chars(view
, type
, text
, max
- 1, trim
);
2012 col
= draw_space(view
, type
, max
- 1, max
- 1);
2015 view
->col
+= draw_space(view
, LINE_DEFAULT
, max
- col
, max
- col
);
2016 return view
->width
+ view
->yoffset
<= view
->col
;
2020 draw_date(struct view
*view
, time_t *time
)
2022 const char *date
= mkdate(time
);
2024 return draw_field(view
, LINE_DATE
, date
, DATE_COLS
, FALSE
);
2028 draw_author(struct view
*view
, const char *author
)
2030 bool trim
= opt_author_cols
== 0 || opt_author_cols
> 5 || !author
;
2033 static char initials
[10];
2036 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2038 memset(initials
, 0, sizeof(initials
));
2039 for (pos
= 0; *author
&& pos
< opt_author_cols
- 1; author
++, pos
++) {
2040 while (is_initial_sep(*author
))
2042 strncpy(&initials
[pos
], author
, sizeof(initials
) - 1 - pos
);
2043 while (*author
&& !is_initial_sep(author
[1]))
2050 return draw_field(view
, LINE_AUTHOR
, author
, opt_author_cols
, trim
);
2054 draw_mode(struct view
*view
, mode_t mode
)
2060 else if (S_ISLNK(mode
))
2062 else if (S_ISGITLINK(mode
))
2064 else if (S_ISREG(mode
) && mode
& S_IXUSR
)
2066 else if (S_ISREG(mode
))
2071 return draw_field(view
, LINE_MODE
, str
, STRING_SIZE("-rw-r--r-- "), FALSE
);
2075 draw_lineno(struct view
*view
, unsigned int lineno
)
2078 int digits3
= view
->digits
< 3 ? 3 : view
->digits
;
2079 int max
= MIN(view
->width
+ view
->yoffset
- view
->col
, digits3
);
2082 lineno
+= view
->offset
+ 1;
2083 if (lineno
== 1 || (lineno
% opt_num_interval
) == 0) {
2084 static char fmt
[] = "%1ld";
2086 fmt
[1] = '0' + (view
->digits
<= 9 ? digits3
: 1);
2087 if (string_format(number
, fmt
, lineno
))
2091 view
->col
+= draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, TRUE
);
2093 view
->col
+= draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
2094 return draw_graphic(view
, LINE_DEFAULT
, &line_graphics
[LINE_GRAPHIC_VLINE
], 1);
2098 draw_view_line(struct view
*view
, unsigned int lineno
)
2101 bool selected
= (view
->offset
+ lineno
== view
->lineno
);
2103 assert(view_is_displayed(view
));
2105 if (view
->offset
+ lineno
>= view
->lines
)
2108 line
= &view
->line
[view
->offset
+ lineno
];
2110 wmove(view
->win
, lineno
, 0);
2112 wclrtoeol(view
->win
);
2114 view
->curline
= line
;
2115 view
->curtype
= LINE_NONE
;
2116 line
->selected
= FALSE
;
2117 line
->dirty
= line
->cleareol
= 0;
2120 set_view_attr(view
, LINE_CURSOR
);
2121 line
->selected
= TRUE
;
2122 view
->ops
->select(view
, line
);
2125 return view
->ops
->draw(view
, line
, lineno
);
2129 redraw_view_dirty(struct view
*view
)
2134 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
2135 if (view
->offset
+ lineno
>= view
->lines
)
2137 if (!view
->line
[view
->offset
+ lineno
].dirty
)
2140 if (!draw_view_line(view
, lineno
))
2146 wnoutrefresh(view
->win
);
2150 redraw_view_from(struct view
*view
, int lineno
)
2152 assert(0 <= lineno
&& lineno
< view
->height
);
2154 for (; lineno
< view
->height
; lineno
++) {
2155 if (!draw_view_line(view
, lineno
))
2159 wnoutrefresh(view
->win
);
2163 redraw_view(struct view
*view
)
2166 redraw_view_from(view
, 0);
2171 update_view_title(struct view
*view
)
2173 char buf
[SIZEOF_STR
];
2174 char state
[SIZEOF_STR
];
2175 size_t bufpos
= 0, statelen
= 0;
2177 assert(view_is_displayed(view
));
2179 if (view
!= VIEW(REQ_VIEW_STATUS
) && view
->lines
) {
2180 unsigned int view_lines
= view
->offset
+ view
->height
;
2181 unsigned int lines
= view
->lines
2182 ? MIN(view_lines
, view
->lines
) * 100 / view
->lines
2185 string_format_from(state
, &statelen
, " - %s %d of %d (%d%%)",
2194 time_t secs
= time(NULL
) - view
->start_time
;
2196 /* Three git seconds are a long time ... */
2198 string_format_from(state
, &statelen
, " loading %lds", secs
);
2201 string_format_from(buf
, &bufpos
, "[%s]", view
->name
);
2202 if (*view
->ref
&& bufpos
< view
->width
) {
2203 size_t refsize
= strlen(view
->ref
);
2204 size_t minsize
= bufpos
+ 1 + /* abbrev= */ 7 + 1 + statelen
;
2206 if (minsize
< view
->width
)
2207 refsize
= view
->width
- minsize
+ 7;
2208 string_format_from(buf
, &bufpos
, " %.*s", (int) refsize
, view
->ref
);
2211 if (statelen
&& bufpos
< view
->width
) {
2212 string_format_from(buf
, &bufpos
, "%s", state
);
2215 if (view
== display
[current_view
])
2216 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_FOCUS
));
2218 wbkgdset(view
->title
, get_line_attr(LINE_TITLE_BLUR
));
2220 mvwaddnstr(view
->title
, 0, 0, buf
, bufpos
);
2221 wclrtoeol(view
->title
);
2222 wnoutrefresh(view
->title
);
2226 apply_step(double step
, int value
)
2230 value
*= step
+ 0.01;
2231 return value
? value
: 1;
2235 resize_display(void)
2238 struct view
*base
= display
[0];
2239 struct view
*view
= display
[1] ? display
[1] : display
[0];
2241 /* Setup window dimensions */
2243 getmaxyx(stdscr
, base
->height
, base
->width
);
2245 /* Make room for the status window. */
2249 /* Horizontal split. */
2250 view
->width
= base
->width
;
2251 view
->height
= apply_step(opt_scale_split_view
, base
->height
);
2252 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
2253 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
2254 base
->height
-= view
->height
;
2256 /* Make room for the title bar. */
2260 /* Make room for the title bar. */
2265 foreach_displayed_view (view
, i
) {
2267 view
->win
= newwin(view
->height
, 0, offset
, 0);
2269 die("Failed to create %s view", view
->name
);
2271 scrollok(view
->win
, FALSE
);
2273 view
->title
= newwin(1, 0, offset
+ view
->height
, 0);
2275 die("Failed to create title window");
2278 wresize(view
->win
, view
->height
, view
->width
);
2279 mvwin(view
->win
, offset
, 0);
2280 mvwin(view
->title
, offset
+ view
->height
, 0);
2283 offset
+= view
->height
+ 1;
2288 redraw_display(bool clear
)
2293 foreach_displayed_view (view
, i
) {
2297 update_view_title(view
);
2302 toggle_view_option(bool *option
, const char *help
)
2305 redraw_display(FALSE
);
2306 report("%sabling %s", *option
? "En" : "Dis", help
);
2310 open_option_menu(void)
2312 const struct menu_item menu
[] = {
2313 { '.', "line numbers", &opt_line_number
},
2314 { 'D', "date display", &opt_date
},
2315 { 'A', "author display", &opt_author
},
2316 { 'g', "revision graph display", &opt_rev_graph
},
2317 { 'F', "reference display", &opt_show_refs
},
2322 if (prompt_menu("Toggle option", menu
, &selected
))
2323 toggle_view_option(menu
[selected
].data
, menu
[selected
].text
);
2327 maximize_view(struct view
*view
)
2329 memset(display
, 0, sizeof(display
));
2331 display
[current_view
] = view
;
2333 redraw_display(FALSE
);
2343 goto_view_line(struct view
*view
, unsigned long offset
, unsigned long lineno
)
2345 if (lineno
>= view
->lines
)
2346 lineno
= view
->lines
> 0 ? view
->lines
- 1 : 0;
2348 if (offset
> lineno
|| offset
+ view
->height
<= lineno
) {
2349 unsigned long half
= view
->height
/ 2;
2352 offset
= lineno
- half
;
2357 if (offset
!= view
->offset
|| lineno
!= view
->lineno
) {
2358 view
->offset
= offset
;
2359 view
->lineno
= lineno
;
2366 /* Scrolling backend */
2368 do_scroll_view(struct view
*view
, int lines
)
2370 bool redraw_current_line
= FALSE
;
2372 /* The rendering expects the new offset. */
2373 view
->offset
+= lines
;
2375 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2378 /* Move current line into the view. */
2379 if (view
->lineno
< view
->offset
) {
2380 view
->lineno
= view
->offset
;
2381 redraw_current_line
= TRUE
;
2382 } else if (view
->lineno
>= view
->offset
+ view
->height
) {
2383 view
->lineno
= view
->offset
+ view
->height
- 1;
2384 redraw_current_line
= TRUE
;
2387 assert(view
->offset
<= view
->lineno
&& view
->lineno
< view
->lines
);
2389 /* Redraw the whole screen if scrolling is pointless. */
2390 if (view
->height
< ABS(lines
)) {
2394 int line
= lines
> 0 ? view
->height
- lines
: 0;
2395 int end
= line
+ ABS(lines
);
2397 scrollok(view
->win
, TRUE
);
2398 wscrl(view
->win
, lines
);
2399 scrollok(view
->win
, FALSE
);
2401 while (line
< end
&& draw_view_line(view
, line
))
2404 if (redraw_current_line
)
2405 draw_view_line(view
, view
->lineno
- view
->offset
);
2406 wnoutrefresh(view
->win
);
2409 view
->has_scrolled
= TRUE
;
2413 /* Scroll frontend */
2415 scroll_view(struct view
*view
, enum request request
)
2419 assert(view_is_displayed(view
));
2422 case REQ_SCROLL_LEFT
:
2423 if (view
->yoffset
== 0) {
2424 report("Cannot scroll beyond the first column");
2427 if (view
->yoffset
<= apply_step(opt_hscroll
, view
->width
))
2430 view
->yoffset
-= apply_step(opt_hscroll
, view
->width
);
2431 redraw_view_from(view
, 0);
2434 case REQ_SCROLL_RIGHT
:
2435 view
->yoffset
+= apply_step(opt_hscroll
, view
->width
);
2439 case REQ_SCROLL_PAGE_DOWN
:
2440 lines
= view
->height
;
2441 case REQ_SCROLL_LINE_DOWN
:
2442 if (view
->offset
+ lines
> view
->lines
)
2443 lines
= view
->lines
- view
->offset
;
2445 if (lines
== 0 || view
->offset
+ view
->height
>= view
->lines
) {
2446 report("Cannot scroll beyond the last line");
2451 case REQ_SCROLL_PAGE_UP
:
2452 lines
= view
->height
;
2453 case REQ_SCROLL_LINE_UP
:
2454 if (lines
> view
->offset
)
2455 lines
= view
->offset
;
2458 report("Cannot scroll beyond the first line");
2466 die("request %d not handled in switch", request
);
2469 do_scroll_view(view
, lines
);
2474 move_view(struct view
*view
, enum request request
)
2476 int scroll_steps
= 0;
2480 case REQ_MOVE_FIRST_LINE
:
2481 steps
= -view
->lineno
;
2484 case REQ_MOVE_LAST_LINE
:
2485 steps
= view
->lines
- view
->lineno
- 1;
2488 case REQ_MOVE_PAGE_UP
:
2489 steps
= view
->height
> view
->lineno
2490 ? -view
->lineno
: -view
->height
;
2493 case REQ_MOVE_PAGE_DOWN
:
2494 steps
= view
->lineno
+ view
->height
>= view
->lines
2495 ? view
->lines
- view
->lineno
- 1 : view
->height
;
2507 die("request %d not handled in switch", request
);
2510 if (steps
<= 0 && view
->lineno
== 0) {
2511 report("Cannot move beyond the first line");
2514 } else if (steps
>= 0 && view
->lineno
+ 1 >= view
->lines
) {
2515 report("Cannot move beyond the last line");
2519 /* Move the current line */
2520 view
->lineno
+= steps
;
2521 assert(0 <= view
->lineno
&& view
->lineno
< view
->lines
);
2523 /* Check whether the view needs to be scrolled */
2524 if (view
->lineno
< view
->offset
||
2525 view
->lineno
>= view
->offset
+ view
->height
) {
2526 scroll_steps
= steps
;
2527 if (steps
< 0 && -steps
> view
->offset
) {
2528 scroll_steps
= -view
->offset
;
2530 } else if (steps
> 0) {
2531 if (view
->lineno
== view
->lines
- 1 &&
2532 view
->lines
> view
->height
) {
2533 scroll_steps
= view
->lines
- view
->offset
- 1;
2534 if (scroll_steps
>= view
->height
)
2535 scroll_steps
-= view
->height
- 1;
2540 if (!view_is_displayed(view
)) {
2541 view
->offset
+= scroll_steps
;
2542 assert(0 <= view
->offset
&& view
->offset
< view
->lines
);
2543 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2547 /* Repaint the old "current" line if we be scrolling */
2548 if (ABS(steps
) < view
->height
)
2549 draw_view_line(view
, view
->lineno
- steps
- view
->offset
);
2552 do_scroll_view(view
, scroll_steps
);
2556 /* Draw the current line */
2557 draw_view_line(view
, view
->lineno
- view
->offset
);
2559 wnoutrefresh(view
->win
);
2568 static void search_view(struct view
*view
, enum request request
);
2571 grep_text(struct view
*view
, const char *text
[])
2576 for (i
= 0; text
[i
]; i
++)
2578 regexec(view
->regex
, text
[i
], 1, &pmatch
, 0) != REG_NOMATCH
)
2584 select_view_line(struct view
*view
, unsigned long lineno
)
2586 unsigned long old_lineno
= view
->lineno
;
2587 unsigned long old_offset
= view
->offset
;
2589 if (goto_view_line(view
, view
->offset
, lineno
)) {
2590 if (view_is_displayed(view
)) {
2591 if (old_offset
!= view
->offset
) {
2594 draw_view_line(view
, old_lineno
- view
->offset
);
2595 draw_view_line(view
, view
->lineno
- view
->offset
);
2596 wnoutrefresh(view
->win
);
2599 view
->ops
->select(view
, &view
->line
[view
->lineno
]);
2605 find_next(struct view
*view
, enum request request
)
2607 unsigned long lineno
= view
->lineno
;
2612 report("No previous search");
2614 search_view(view
, request
);
2624 case REQ_SEARCH_BACK
:
2633 if (request
== REQ_FIND_NEXT
|| request
== REQ_FIND_PREV
)
2634 lineno
+= direction
;
2636 /* Note, lineno is unsigned long so will wrap around in which case it
2637 * will become bigger than view->lines. */
2638 for (; lineno
< view
->lines
; lineno
+= direction
) {
2639 if (view
->ops
->grep(view
, &view
->line
[lineno
])) {
2640 select_view_line(view
, lineno
);
2641 report("Line %ld matches '%s'", lineno
+ 1, view
->grep
);
2646 report("No match found for '%s'", view
->grep
);
2650 search_view(struct view
*view
, enum request request
)
2655 regfree(view
->regex
);
2658 view
->regex
= calloc(1, sizeof(*view
->regex
));
2663 regex_err
= regcomp(view
->regex
, opt_search
, REG_EXTENDED
);
2664 if (regex_err
!= 0) {
2665 char buf
[SIZEOF_STR
] = "unknown error";
2667 regerror(regex_err
, view
->regex
, buf
, sizeof(buf
));
2668 report("Search failed: %s", buf
);
2672 string_copy(view
->grep
, opt_search
);
2674 find_next(view
, request
);
2678 * Incremental updating
2682 reset_view(struct view
*view
)
2686 for (i
= 0; i
< view
->lines
; i
++)
2687 free(view
->line
[i
].data
);
2690 view
->p_offset
= view
->offset
;
2691 view
->p_yoffset
= view
->yoffset
;
2692 view
->p_lineno
= view
->lineno
;
2700 view
->update_secs
= 0;
2704 free_argv(const char *argv
[])
2708 for (argc
= 0; argv
[argc
]; argc
++)
2709 free((void *) argv
[argc
]);
2713 format_argv(const char *dst_argv
[], const char *src_argv
[], enum format_flags flags
)
2715 char buf
[SIZEOF_STR
];
2717 bool noreplace
= flags
== FORMAT_NONE
;
2719 free_argv(dst_argv
);
2721 for (argc
= 0; src_argv
[argc
]; argc
++) {
2722 const char *arg
= src_argv
[argc
];
2726 char *next
= strstr(arg
, "%(");
2727 int len
= next
- arg
;
2730 if (!next
|| noreplace
) {
2731 if (flags
== FORMAT_DASH
&& !strcmp(arg
, "--"))
2736 } else if (!prefixcmp(next
, "%(directory)")) {
2739 } else if (!prefixcmp(next
, "%(file)")) {
2742 } else if (!prefixcmp(next
, "%(ref)")) {
2743 value
= *opt_ref
? opt_ref
: "HEAD";
2745 } else if (!prefixcmp(next
, "%(head)")) {
2748 } else if (!prefixcmp(next
, "%(commit)")) {
2751 } else if (!prefixcmp(next
, "%(blob)")) {
2755 report("Unknown replacement: `%s`", next
);
2759 if (!string_format_from(buf
, &bufpos
, "%.*s%s", len
, arg
, value
))
2762 arg
= next
&& !noreplace
? strchr(next
, ')') + 1 : NULL
;
2765 dst_argv
[argc
] = strdup(buf
);
2766 if (!dst_argv
[argc
])
2770 dst_argv
[argc
] = NULL
;
2772 return src_argv
[argc
] == NULL
;
2776 restore_view_position(struct view
*view
)
2778 if (!view
->p_restore
|| (view
->pipe
&& view
->lines
<= view
->p_lineno
))
2781 /* Changing the view position cancels the restoring. */
2782 /* FIXME: Changing back to the first line is not detected. */
2783 if (view
->offset
!= 0 || view
->lineno
!= 0) {
2784 view
->p_restore
= FALSE
;
2788 if (goto_view_line(view
, view
->p_offset
, view
->p_lineno
) &&
2789 view_is_displayed(view
))
2792 view
->yoffset
= view
->p_yoffset
;
2793 view
->p_restore
= FALSE
;
2799 end_update(struct view
*view
, bool force
)
2803 while (!view
->ops
->read(view
, NULL
))
2806 set_nonblocking_input(FALSE
);
2808 kill_io(view
->pipe
);
2809 done_io(view
->pipe
);
2814 setup_update(struct view
*view
, const char *vid
)
2816 set_nonblocking_input(TRUE
);
2818 string_copy_rev(view
->vid
, vid
);
2819 view
->pipe
= &view
->io
;
2820 view
->start_time
= time(NULL
);
2824 prepare_update(struct view
*view
, const char *argv
[], const char *dir
,
2825 enum format_flags flags
)
2828 end_update(view
, TRUE
);
2829 return init_io_rd(&view
->io
, argv
, dir
, flags
);
2833 prepare_update_file(struct view
*view
, const char *name
)
2836 end_update(view
, TRUE
);
2837 return io_open(&view
->io
, name
);
2841 begin_update(struct view
*view
, bool refresh
)
2844 end_update(view
, TRUE
);
2847 if (!start_io(&view
->io
))
2851 if (view
== VIEW(REQ_VIEW_TREE
) && strcmp(view
->vid
, view
->id
))
2854 if (!run_io_rd(&view
->io
, view
->ops
->argv
, FORMAT_ALL
))
2857 /* Put the current ref_* value to the view title ref
2858 * member. This is needed by the blob view. Most other
2859 * views sets it automatically after loading because the
2860 * first line is a commit line. */
2861 string_copy_rev(view
->ref
, view
->id
);
2864 setup_update(view
, view
->id
);
2870 update_view(struct view
*view
)
2872 char out_buffer
[BUFSIZ
* 2];
2874 /* Clear the view and redraw everything since the tree sorting
2875 * might have rearranged things. */
2876 bool redraw
= view
->lines
== 0;
2877 bool can_read
= TRUE
;
2882 if (!io_can_read(view
->pipe
)) {
2883 if (view
->lines
== 0 && view_is_displayed(view
)) {
2884 time_t secs
= time(NULL
) - view
->start_time
;
2886 if (secs
> 1 && secs
> view
->update_secs
) {
2887 if (view
->update_secs
== 0)
2889 update_view_title(view
);
2890 view
->update_secs
= secs
;
2896 for (; (line
= io_get(view
->pipe
, '\n', can_read
)); can_read
= FALSE
) {
2897 if (opt_iconv
!= ICONV_NONE
) {
2898 ICONV_CONST
char *inbuf
= line
;
2899 size_t inlen
= strlen(line
) + 1;
2901 char *outbuf
= out_buffer
;
2902 size_t outlen
= sizeof(out_buffer
);
2906 ret
= iconv(opt_iconv
, &inbuf
, &inlen
, &outbuf
, &outlen
);
2907 if (ret
!= (size_t) -1)
2911 if (!view
->ops
->read(view
, line
)) {
2912 report("Allocation failure");
2913 end_update(view
, TRUE
);
2919 unsigned long lines
= view
->lines
;
2922 for (digits
= 0; lines
; digits
++)
2925 /* Keep the displayed view in sync with line number scaling. */
2926 if (digits
!= view
->digits
) {
2927 view
->digits
= digits
;
2928 if (opt_line_number
|| view
== VIEW(REQ_VIEW_BLAME
))
2933 if (io_error(view
->pipe
)) {
2934 report("Failed to read: %s", io_strerror(view
->pipe
));
2935 end_update(view
, TRUE
);
2937 } else if (io_eof(view
->pipe
)) {
2939 end_update(view
, FALSE
);
2942 if (restore_view_position(view
))
2945 if (!view_is_displayed(view
))
2949 redraw_view_from(view
, 0);
2951 redraw_view_dirty(view
);
2953 /* Update the title _after_ the redraw so that if the redraw picks up a
2954 * commit reference in view->ref it'll be available here. */
2955 update_view_title(view
);
2959 DEFINE_ALLOCATOR(realloc_lines
, struct line
, 256)
2961 static struct line
*
2962 add_line_data(struct view
*view
, void *data
, enum line_type type
)
2966 if (!realloc_lines(&view
->line
, view
->lines
, 1))
2969 line
= &view
->line
[view
->lines
++];
2970 memset(line
, 0, sizeof(*line
));
2978 static struct line
*
2979 add_line_text(struct view
*view
, const char *text
, enum line_type type
)
2981 char *data
= text
? strdup(text
) : NULL
;
2983 return data
? add_line_data(view
, data
, type
) : NULL
;
2986 static struct line
*
2987 add_line_format(struct view
*view
, enum line_type type
, const char *fmt
, ...)
2989 char buf
[SIZEOF_STR
];
2992 va_start(args
, fmt
);
2993 if (vsnprintf(buf
, sizeof(buf
), fmt
, args
) >= sizeof(buf
))
2997 return buf
[0] ? add_line_text(view
, buf
, type
) : NULL
;
3005 OPEN_DEFAULT
= 0, /* Use default view switching. */
3006 OPEN_SPLIT
= 1, /* Split current view. */
3007 OPEN_RELOAD
= 4, /* Reload view even if it is the current. */
3008 OPEN_REFRESH
= 16, /* Refresh view using previous command. */
3009 OPEN_PREPARED
= 32, /* Open already prepared command. */
3013 open_view(struct view
*prev
, enum request request
, enum open_flags flags
)
3015 bool split
= !!(flags
& OPEN_SPLIT
);
3016 bool reload
= !!(flags
& (OPEN_RELOAD
| OPEN_REFRESH
| OPEN_PREPARED
));
3017 bool nomaximize
= !!(flags
& OPEN_REFRESH
);
3018 struct view
*view
= VIEW(request
);
3019 int nviews
= displayed_views();
3020 struct view
*base_view
= display
[0];
3022 if (view
== prev
&& nviews
== 1 && !reload
) {
3023 report("Already in %s view", view
->name
);
3027 if (view
->git_dir
&& !opt_git_dir
[0]) {
3028 report("The %s view is disabled in pager view", view
->name
);
3035 } else if (!nomaximize
) {
3036 /* Maximize the current view. */
3037 memset(display
, 0, sizeof(display
));
3039 display
[current_view
] = view
;
3042 /* Resize the view when switching between split- and full-screen,
3043 * or when switching between two different full-screen views. */
3044 if (nviews
!= displayed_views() ||
3045 (nviews
== 1 && base_view
!= display
[0]))
3048 if (view
->ops
->open
) {
3050 end_update(view
, TRUE
);
3051 if (!view
->ops
->open(view
)) {
3052 report("Failed to load %s view", view
->name
);
3055 restore_view_position(view
);
3057 } else if ((reload
|| strcmp(view
->vid
, view
->id
)) &&
3058 !begin_update(view
, flags
& (OPEN_REFRESH
| OPEN_PREPARED
))) {
3059 report("Failed to load %s view", view
->name
);
3063 if (split
&& prev
->lineno
- prev
->offset
>= prev
->height
) {
3064 /* Take the title line into account. */
3065 int lines
= prev
->lineno
- prev
->offset
- prev
->height
+ 1;
3067 /* Scroll the view that was split if the current line is
3068 * outside the new limited view. */
3069 do_scroll_view(prev
, lines
);
3072 if (prev
&& view
!= prev
) {
3074 /* "Blur" the previous view. */
3075 update_view_title(prev
);
3078 view
->parent
= prev
;
3081 if (view
->pipe
&& view
->lines
== 0) {
3082 /* Clear the old view and let the incremental updating refill
3085 view
->p_restore
= flags
& (OPEN_RELOAD
| OPEN_REFRESH
);
3087 } else if (view_is_displayed(view
)) {
3094 open_external_viewer(const char *argv
[], const char *dir
)
3096 def_prog_mode(); /* save current tty modes */
3097 endwin(); /* restore original tty modes */
3098 run_io_fg(argv
, dir
);
3099 fprintf(stderr
, "Press Enter to continue");
3102 redraw_display(TRUE
);
3106 open_mergetool(const char *file
)
3108 const char *mergetool_argv
[] = { "git", "mergetool", file
, NULL
};
3110 open_external_viewer(mergetool_argv
, opt_cdup
);
3114 open_editor(bool from_root
, const char *file
)
3116 const char *editor_argv
[] = { "vi", file
, NULL
};
3119 editor
= getenv("GIT_EDITOR");
3120 if (!editor
&& *opt_editor
)
3121 editor
= opt_editor
;
3123 editor
= getenv("VISUAL");
3125 editor
= getenv("EDITOR");
3129 editor_argv
[0] = editor
;
3130 open_external_viewer(editor_argv
, from_root
? opt_cdup
: NULL
);
3134 open_run_request(enum request request
)
3136 struct run_request
*req
= get_run_request(request
);
3137 const char *argv
[ARRAY_SIZE(req
->argv
)] = { NULL
};
3140 report("Unknown run request");
3144 if (format_argv(argv
, req
->argv
, FORMAT_ALL
))
3145 open_external_viewer(argv
, NULL
);
3150 * User request switch noodle
3154 view_driver(struct view
*view
, enum request request
)
3158 if (request
== REQ_NONE
)
3161 if (request
> REQ_NONE
) {
3162 open_run_request(request
);
3163 /* FIXME: When all views can refresh always do this. */
3164 if (view
== VIEW(REQ_VIEW_STATUS
) ||
3165 view
== VIEW(REQ_VIEW_MAIN
) ||
3166 view
== VIEW(REQ_VIEW_LOG
) ||
3167 view
== VIEW(REQ_VIEW_BRANCH
) ||
3168 view
== VIEW(REQ_VIEW_STAGE
))
3169 request
= REQ_REFRESH
;
3174 if (view
&& view
->lines
) {
3175 request
= view
->ops
->request(view
, request
, &view
->line
[view
->lineno
]);
3176 if (request
== REQ_NONE
)
3183 case REQ_MOVE_PAGE_UP
:
3184 case REQ_MOVE_PAGE_DOWN
:
3185 case REQ_MOVE_FIRST_LINE
:
3186 case REQ_MOVE_LAST_LINE
:
3187 move_view(view
, request
);
3190 case REQ_SCROLL_LEFT
:
3191 case REQ_SCROLL_RIGHT
:
3192 case REQ_SCROLL_LINE_DOWN
:
3193 case REQ_SCROLL_LINE_UP
:
3194 case REQ_SCROLL_PAGE_DOWN
:
3195 case REQ_SCROLL_PAGE_UP
:
3196 scroll_view(view
, request
);
3199 case REQ_VIEW_BLAME
:
3201 report("No file chosen, press %s to open tree view",
3202 get_key(REQ_VIEW_TREE
));
3205 open_view(view
, request
, OPEN_DEFAULT
);
3210 report("No file chosen, press %s to open tree view",
3211 get_key(REQ_VIEW_TREE
));
3214 open_view(view
, request
, OPEN_DEFAULT
);
3217 case REQ_VIEW_PAGER
:
3218 if (!VIEW(REQ_VIEW_PAGER
)->pipe
&& !VIEW(REQ_VIEW_PAGER
)->lines
) {
3219 report("No pager content, press %s to run command from prompt",
3220 get_key(REQ_PROMPT
));
3223 open_view(view
, request
, OPEN_DEFAULT
);
3226 case REQ_VIEW_STAGE
:
3227 if (!VIEW(REQ_VIEW_STAGE
)->lines
) {
3228 report("No stage content, press %s to open the status view and choose file",
3229 get_key(REQ_VIEW_STATUS
));
3232 open_view(view
, request
, OPEN_DEFAULT
);
3235 case REQ_VIEW_STATUS
:
3236 if (opt_is_inside_work_tree
== FALSE
) {
3237 report("The status view requires a working tree");
3240 open_view(view
, request
, OPEN_DEFAULT
);
3248 case REQ_VIEW_BRANCH
:
3249 open_view(view
, request
, OPEN_DEFAULT
);
3254 request
= request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
3256 if ((view
== VIEW(REQ_VIEW_DIFF
) &&
3257 view
->parent
== VIEW(REQ_VIEW_MAIN
)) ||
3258 (view
== VIEW(REQ_VIEW_DIFF
) &&
3259 view
->parent
== VIEW(REQ_VIEW_BLAME
)) ||
3260 (view
== VIEW(REQ_VIEW_STAGE
) &&
3261 view
->parent
== VIEW(REQ_VIEW_STATUS
)) ||
3262 (view
== VIEW(REQ_VIEW_BLOB
) &&
3263 view
->parent
== VIEW(REQ_VIEW_TREE
)) ||
3264 (view
== VIEW(REQ_VIEW_MAIN
) &&
3265 view
->parent
== VIEW(REQ_VIEW_BRANCH
))) {
3268 view
= view
->parent
;
3269 line
= view
->lineno
;
3270 move_view(view
, request
);
3271 if (view_is_displayed(view
))
3272 update_view_title(view
);
3273 if (line
!= view
->lineno
)
3274 view
->ops
->request(view
, REQ_ENTER
,
3275 &view
->line
[view
->lineno
]);
3278 move_view(view
, request
);
3284 int nviews
= displayed_views();
3285 int next_view
= (current_view
+ 1) % nviews
;
3287 if (next_view
== current_view
) {
3288 report("Only one view is displayed");
3292 current_view
= next_view
;
3293 /* Blur out the title of the previous view. */
3294 update_view_title(view
);
3299 report("Refreshing is not yet supported for the %s view", view
->name
);
3303 if (displayed_views() == 2)
3304 maximize_view(view
);
3311 case REQ_TOGGLE_LINENO
:
3312 toggle_view_option(&opt_line_number
, "line numbers");
3315 case REQ_TOGGLE_DATE
:
3316 toggle_view_option(&opt_date
, "date display");
3319 case REQ_TOGGLE_AUTHOR
:
3320 toggle_view_option(&opt_author
, "author display");
3323 case REQ_TOGGLE_REV_GRAPH
:
3324 toggle_view_option(&opt_rev_graph
, "revision graph display");
3327 case REQ_TOGGLE_REFS
:
3328 toggle_view_option(&opt_show_refs
, "reference display");
3331 case REQ_TOGGLE_SORT_FIELD
:
3332 case REQ_TOGGLE_SORT_ORDER
:
3333 report("Sorting is not yet supported for the %s view", view
->name
);
3337 case REQ_SEARCH_BACK
:
3338 search_view(view
, request
);
3343 find_next(view
, request
);
3346 case REQ_STOP_LOADING
:
3347 for (i
= 0; i
< ARRAY_SIZE(views
); i
++) {
3350 report("Stopped loading the %s view", view
->name
),
3351 end_update(view
, TRUE
);
3355 case REQ_SHOW_VERSION
:
3356 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
3359 case REQ_SCREEN_REDRAW
:
3360 redraw_display(TRUE
);
3364 report("Nothing to edit");
3368 report("Nothing to enter");
3371 case REQ_VIEW_CLOSE
:
3372 /* XXX: Mark closed views by letting view->parent point to the
3373 * view itself. Parents to closed view should never be
3376 view
->parent
->parent
!= view
->parent
) {
3377 maximize_view(view
->parent
);
3378 view
->parent
= view
;
3386 report("Unknown key, press 'h' for help");
3395 * View backend utilities
3405 const enum sort_field
*fields
;
3406 size_t size
, current
;
3410 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3411 #define get_sort_field(state) ((state).fields[(state).current])
3412 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3415 sort_view(struct view
*view
, enum request request
, struct sort_state
*state
,
3416 int (*compare
)(const void *, const void *))
3419 case REQ_TOGGLE_SORT_FIELD
:
3420 state
->current
= (state
->current
+ 1) % state
->size
;
3423 case REQ_TOGGLE_SORT_ORDER
:
3424 state
->reverse
= !state
->reverse
;
3427 die("Not a sort request");
3430 qsort(view
->line
, view
->lines
, sizeof(*view
->line
), compare
);
3434 DEFINE_ALLOCATOR(realloc_authors
, const char *, 256)
3436 /* Small author cache to reduce memory consumption. It uses binary
3437 * search to lookup or find place to position new entries. No entries
3438 * are ever freed. */
3440 get_author(const char *name
)
3442 static const char **authors
;
3443 static size_t authors_size
;
3444 int from
= 0, to
= authors_size
- 1;
3446 while (from
<= to
) {
3447 size_t pos
= (to
+ from
) / 2;
3448 int cmp
= strcmp(name
, authors
[pos
]);
3451 return authors
[pos
];
3459 if (!realloc_authors(&authors
, authors_size
, 1))
3461 name
= strdup(name
);
3465 memmove(authors
+ from
+ 1, authors
+ from
, (authors_size
- from
) * sizeof(*authors
));
3466 authors
[from
] = name
;
3473 parse_timezone(time_t *time
, const char *zone
)
3477 tz
= ('0' - zone
[1]) * 60 * 60 * 10;
3478 tz
+= ('0' - zone
[2]) * 60 * 60;
3479 tz
+= ('0' - zone
[3]) * 60;
3480 tz
+= ('0' - zone
[4]);
3488 /* Parse author lines where the name may be empty:
3489 * author <email@address.tld> 1138474660 +0100
3492 parse_author_line(char *ident
, const char **author
, time_t *time
)
3494 char *nameend
= strchr(ident
, '<');
3495 char *emailend
= strchr(ident
, '>');
3497 if (nameend
&& emailend
)
3498 *nameend
= *emailend
= 0;
3499 ident
= chomp_string(ident
);
3502 ident
= chomp_string(nameend
+ 1);
3507 *author
= get_author(ident
);
3509 /* Parse epoch and timezone */
3510 if (emailend
&& emailend
[1] == ' ') {
3511 char *secs
= emailend
+ 2;
3512 char *zone
= strchr(secs
, ' ');
3514 *time
= (time_t) atol(secs
);
3516 if (zone
&& strlen(zone
) == STRING_SIZE(" +0700"))
3517 parse_timezone(time
, zone
+ 1);
3522 open_commit_parent_menu(char buf
[SIZEOF_STR
], int *parents
)
3524 char rev
[SIZEOF_REV
];
3525 const char *revlist_argv
[] = {
3526 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev
, NULL
3528 struct menu_item
*items
;
3529 char text
[SIZEOF_STR
];
3533 items
= calloc(*parents
+ 1, sizeof(*items
));
3537 for (i
= 0; i
< *parents
; i
++) {
3538 string_copy_rev(rev
, &buf
[SIZEOF_REV
* i
]);
3539 if (!run_io_buf(revlist_argv
, text
, sizeof(text
)) ||
3540 !(items
[i
].text
= strdup(text
))) {
3548 ok
= prompt_menu("Select parent", items
, parents
);
3550 for (i
= 0; items
[i
].text
; i
++)
3551 free((char *) items
[i
].text
);
3557 select_commit_parent(const char *id
, char rev
[SIZEOF_REV
], const char *path
)
3559 char buf
[SIZEOF_STR
* 4];
3560 const char *revlist_argv
[] = {
3561 "git", "log", "--no-color", "-1",
3562 "--pretty=format:%P", id
, "--", path
, NULL
3566 if (!run_io_buf(revlist_argv
, buf
, sizeof(buf
)) ||
3567 (parents
= strlen(buf
) / 40) < 0) {
3568 report("Failed to get parent information");
3571 } else if (parents
== 0) {
3573 report("Path '%s' does not exist in the parent", path
);
3575 report("The selected commit has no parents");
3579 if (parents
> 1 && !open_commit_parent_menu(buf
, &parents
))
3582 string_copy_rev(rev
, &buf
[41 * parents
]);
3591 pager_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
3593 char text
[SIZEOF_STR
];
3595 if (opt_line_number
&& draw_lineno(view
, lineno
))
3598 string_expand(text
, sizeof(text
), line
->data
, opt_tab_size
);
3599 draw_text(view
, line
->type
, text
, TRUE
);
3604 add_describe_ref(char *buf
, size_t *bufpos
, const char *commit_id
, const char *sep
)
3606 const char *describe_argv
[] = { "git", "describe", commit_id
, NULL
};
3607 char ref
[SIZEOF_STR
];
3609 if (!run_io_buf(describe_argv
, ref
, sizeof(ref
)) || !*ref
)
3612 /* This is the only fatal call, since it can "corrupt" the buffer. */
3613 if (!string_nformat(buf
, SIZEOF_STR
, bufpos
, "%s%s", sep
, ref
))
3620 add_pager_refs(struct view
*view
, struct line
*line
)
3622 char buf
[SIZEOF_STR
];
3623 char *commit_id
= (char *)line
->data
+ STRING_SIZE("commit ");
3624 struct ref_list
*list
;
3625 size_t bufpos
= 0, i
;
3626 const char *sep
= "Refs: ";
3627 bool is_tag
= FALSE
;
3629 assert(line
->type
== LINE_COMMIT
);
3631 list
= get_ref_list(commit_id
);
3633 if (view
== VIEW(REQ_VIEW_DIFF
))
3634 goto try_add_describe_ref
;
3638 for (i
= 0; i
< list
->size
; i
++) {
3639 struct ref
*ref
= list
->refs
[i
];
3640 const char *fmt
= ref
->tag
? "%s[%s]" :
3641 ref
->remote
? "%s<%s>" : "%s%s";
3643 if (!string_format_from(buf
, &bufpos
, fmt
, sep
, ref
->name
))
3650 if (!is_tag
&& view
== VIEW(REQ_VIEW_DIFF
)) {
3651 try_add_describe_ref
:
3652 /* Add <tag>-g<commit_id> "fake" reference. */
3653 if (!add_describe_ref(buf
, &bufpos
, commit_id
, sep
))
3660 add_line_text(view
, buf
, LINE_PP_REFS
);
3664 pager_read(struct view
*view
, char *data
)
3671 line
= add_line_text(view
, data
, get_line_type(data
));
3675 if (line
->type
== LINE_COMMIT
&&
3676 (view
== VIEW(REQ_VIEW_DIFF
) ||
3677 view
== VIEW(REQ_VIEW_LOG
)))
3678 add_pager_refs(view
, line
);
3684 pager_request(struct view
*view
, enum request request
, struct line
*line
)
3688 if (request
!= REQ_ENTER
)
3691 if (line
->type
== LINE_COMMIT
&&
3692 (view
== VIEW(REQ_VIEW_LOG
) ||
3693 view
== VIEW(REQ_VIEW_PAGER
))) {
3694 open_view(view
, REQ_VIEW_DIFF
, OPEN_SPLIT
);
3698 /* Always scroll the view even if it was split. That way
3699 * you can use Enter to scroll through the log view and
3700 * split open each commit diff. */
3701 scroll_view(view
, REQ_SCROLL_LINE_DOWN
);
3703 /* FIXME: A minor workaround. Scrolling the view will call report("")
3704 * but if we are scrolling a non-current view this won't properly
3705 * update the view title. */
3707 update_view_title(view
);
3713 pager_grep(struct view
*view
, struct line
*line
)
3715 const char *text
[] = { line
->data
, NULL
};
3717 return grep_text(view
, text
);
3721 pager_select(struct view
*view
, struct line
*line
)
3723 if (line
->type
== LINE_COMMIT
) {
3724 char *text
= (char *)line
->data
+ STRING_SIZE("commit ");
3726 if (view
!= VIEW(REQ_VIEW_PAGER
))
3727 string_copy_rev(view
->ref
, text
);
3728 string_copy_rev(ref_commit
, text
);
3732 static struct view_ops pager_ops
= {
3743 static const char *log_argv
[SIZEOF_ARG
] = {
3744 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3748 log_request(struct view
*view
, enum request request
, struct line
*line
)
3753 open_view(view
, REQ_VIEW_LOG
, OPEN_REFRESH
);
3756 return pager_request(view
, request
, line
);
3760 static struct view_ops log_ops
= {
3771 static const char *diff_argv
[SIZEOF_ARG
] = {
3772 "git", "show", "--pretty=fuller", "--no-color", "--root",
3773 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3776 static struct view_ops diff_ops
= {
3792 help_open(struct view
*view
)
3794 char buf
[SIZEOF_STR
];
3798 if (view
->lines
> 0)
3801 add_line_text(view
, "Quick reference for tig keybindings:", LINE_DEFAULT
);
3803 for (i
= 0; i
< ARRAY_SIZE(req_info
); i
++) {
3806 if (req_info
[i
].request
== REQ_NONE
)
3809 if (!req_info
[i
].request
) {
3810 add_line_text(view
, "", LINE_DEFAULT
);
3811 add_line_text(view
, req_info
[i
].help
, LINE_DEFAULT
);
3815 key
= get_key(req_info
[i
].request
);
3817 key
= "(no key defined)";
3819 for (bufpos
= 0; bufpos
<= req_info
[i
].namelen
; bufpos
++) {
3820 buf
[bufpos
] = tolower(req_info
[i
].name
[bufpos
]);
3821 if (buf
[bufpos
] == '_')
3825 add_line_format(view
, LINE_DEFAULT
, " %-25s %-20s %s",
3826 key
, buf
, req_info
[i
].help
);
3830 add_line_text(view
, "", LINE_DEFAULT
);
3831 add_line_text(view
, "External commands:", LINE_DEFAULT
);
3834 for (i
= 0; i
< run_requests
; i
++) {
3835 struct run_request
*req
= get_run_request(REQ_NONE
+ i
+ 1);
3842 key
= get_key_name(req
->key
);
3844 key
= "(no key defined)";
3846 for (bufpos
= 0, argc
= 0; req
->argv
[argc
]; argc
++)
3847 if (!string_format_from(buf
, &bufpos
, "%s%s",
3848 argc
? " " : "", req
->argv
[argc
]))
3851 add_line_format(view
, LINE_DEFAULT
, " %-10s %-14s `%s`",
3852 keymap_table
[req
->keymap
].name
, key
, buf
);
3858 static struct view_ops help_ops
= {
3874 struct tree_stack_entry
{
3875 struct tree_stack_entry
*prev
; /* Entry below this in the stack */
3876 unsigned long lineno
; /* Line number to restore */
3877 char *name
; /* Position of name in opt_path */
3880 /* The top of the path stack. */
3881 static struct tree_stack_entry
*tree_stack
= NULL
;
3882 unsigned long tree_lineno
= 0;
3885 pop_tree_stack_entry(void)
3887 struct tree_stack_entry
*entry
= tree_stack
;
3889 tree_lineno
= entry
->lineno
;
3891 tree_stack
= entry
->prev
;
3896 push_tree_stack_entry(const char *name
, unsigned long lineno
)
3898 struct tree_stack_entry
*entry
= calloc(1, sizeof(*entry
));
3899 size_t pathlen
= strlen(opt_path
);
3904 entry
->prev
= tree_stack
;
3905 entry
->name
= opt_path
+ pathlen
;
3908 if (!string_format_from(opt_path
, &pathlen
, "%s/", name
)) {
3909 pop_tree_stack_entry();
3913 /* Move the current line to the first tree entry. */
3915 entry
->lineno
= lineno
;
3918 /* Parse output from git-ls-tree(1):
3920 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3923 #define SIZEOF_TREE_ATTR \
3924 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3926 #define SIZEOF_TREE_MODE \
3927 STRING_SIZE("100644 ")
3929 #define TREE_ID_OFFSET \
3930 STRING_SIZE("100644 blob ")
3933 char id
[SIZEOF_REV
];
3935 time_t time
; /* Date from the author ident. */
3936 const char *author
; /* Author of the commit. */
3941 tree_path(const struct line
*line
)
3943 return ((struct tree_entry
*) line
->data
)->name
;
3947 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
3949 if (line1
->type
!= line2
->type
)
3950 return line1
->type
== LINE_TREE_DIR
? -1 : 1;
3951 return strcmp(tree_path(line1
), tree_path(line2
));
3954 static const enum sort_field tree_sort_fields
[] = {
3955 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
3957 static struct sort_state tree_sort_state
= SORT_STATE(tree_sort_fields
);
3960 tree_compare(const void *l1
, const void *l2
)
3962 const struct line
*line1
= (const struct line
*) l1
;
3963 const struct line
*line2
= (const struct line
*) l2
;
3964 const struct tree_entry
*entry1
= ((const struct line
*) l1
)->data
;
3965 const struct tree_entry
*entry2
= ((const struct line
*) l2
)->data
;
3967 if (line1
->type
== LINE_TREE_HEAD
)
3969 if (line2
->type
== LINE_TREE_HEAD
)
3972 switch (get_sort_field(tree_sort_state
)) {
3974 return sort_order(tree_sort_state
, entry1
->time
- entry2
->time
);
3976 case ORDERBY_AUTHOR
:
3977 return sort_order(tree_sort_state
, strcmp(entry1
->author
, entry2
->author
));
3981 return sort_order(tree_sort_state
, tree_compare_entry(line1
, line2
));
3986 static struct line
*
3987 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
3988 const char *mode
, const char *id
)
3990 struct tree_entry
*entry
= calloc(1, sizeof(*entry
) + strlen(path
));
3991 struct line
*line
= entry
? add_line_data(view
, entry
, type
) : NULL
;
3993 if (!entry
|| !line
) {
3998 strncpy(entry
->name
, path
, strlen(path
));
4000 entry
->mode
= strtoul(mode
, NULL
, 8);
4002 string_copy_rev(entry
->id
, id
);
4008 tree_read_date(struct view
*view
, char *text
, bool *read_date
)
4010 static const char *author_name
;
4011 static time_t author_time
;
4013 if (!text
&& *read_date
) {
4018 char *path
= *opt_path
? opt_path
: ".";
4019 /* Find next entry to process */
4020 const char *log_file
[] = {
4021 "git", "log", "--no-color", "--pretty=raw",
4022 "--cc", "--raw", view
->id
, "--", path
, NULL
4027 tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
);
4028 report("Tree is empty");
4032 if (!run_io_rd(&io
, log_file
, FORMAT_NONE
)) {
4033 report("Failed to load tree data");
4037 done_io(view
->pipe
);
4042 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
4043 parse_author_line(text
+ STRING_SIZE("author "),
4044 &author_name
, &author_time
);
4046 } else if (*text
== ':') {
4048 size_t annotated
= 1;
4051 pos
= strchr(text
, '\t');
4055 if (*opt_prefix
&& !strncmp(text
, opt_prefix
, strlen(opt_prefix
)))
4056 text
+= strlen(opt_prefix
);
4057 if (*opt_path
&& !strncmp(text
, opt_path
, strlen(opt_path
)))
4058 text
+= strlen(opt_path
);
4059 pos
= strchr(text
, '/');
4063 for (i
= 1; i
< view
->lines
; i
++) {
4064 struct line
*line
= &view
->line
[i
];
4065 struct tree_entry
*entry
= line
->data
;
4067 annotated
+= !!entry
->author
;
4068 if (entry
->author
|| strcmp(entry
->name
, text
))
4071 entry
->author
= author_name
;
4072 entry
->time
= author_time
;
4077 if (annotated
== view
->lines
)
4078 kill_io(view
->pipe
);
4084 tree_read(struct view
*view
, char *text
)
4086 static bool read_date
= FALSE
;
4087 struct tree_entry
*data
;
4088 struct line
*entry
, *line
;
4089 enum line_type type
;
4090 size_t textlen
= text
? strlen(text
) : 0;
4091 char *path
= text
+ SIZEOF_TREE_ATTR
;
4093 if (read_date
|| !text
)
4094 return tree_read_date(view
, text
, &read_date
);
4096 if (textlen
<= SIZEOF_TREE_ATTR
)
4098 if (view
->lines
== 0 &&
4099 !tree_entry(view
, LINE_TREE_HEAD
, opt_path
, NULL
, NULL
))
4102 /* Strip the path part ... */
4104 size_t pathlen
= textlen
- SIZEOF_TREE_ATTR
;
4105 size_t striplen
= strlen(opt_path
);
4107 if (pathlen
> striplen
)
4108 memmove(path
, path
+ striplen
,
4109 pathlen
- striplen
+ 1);
4111 /* Insert "link" to parent directory. */
4112 if (view
->lines
== 1 &&
4113 !tree_entry(view
, LINE_TREE_DIR
, "..", "040000", view
->ref
))
4117 type
= text
[SIZEOF_TREE_MODE
] == 't' ? LINE_TREE_DIR
: LINE_TREE_FILE
;
4118 entry
= tree_entry(view
, type
, path
, text
, text
+ TREE_ID_OFFSET
);
4123 /* Skip "Directory ..." and ".." line. */
4124 for (line
= &view
->line
[1 + !!*opt_path
]; line
< entry
; line
++) {
4125 if (tree_compare_entry(line
, entry
) <= 0)
4128 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
4132 for (; line
<= entry
; line
++)
4133 line
->dirty
= line
->cleareol
= 1;
4137 if (tree_lineno
> view
->lineno
) {
4138 view
->lineno
= tree_lineno
;
4146 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4148 struct tree_entry
*entry
= line
->data
;
4150 if (line
->type
== LINE_TREE_HEAD
) {
4151 if (draw_text(view
, line
->type
, "Directory path /", TRUE
))
4154 if (draw_mode(view
, entry
->mode
))
4157 if (opt_author
&& draw_author(view
, entry
->author
))
4160 if (opt_date
&& draw_date(view
, entry
->author
? &entry
->time
: NULL
))
4163 if (draw_text(view
, line
->type
, entry
->name
, TRUE
))
4171 char file
[SIZEOF_STR
] = "/tmp/tigblob.XXXXXX";
4172 int fd
= mkstemp(file
);
4175 report("Failed to create temporary file");
4176 else if (!run_io_append(blob_ops
.argv
, FORMAT_ALL
, fd
))
4177 report("Failed to save blob data to file");
4179 open_editor(FALSE
, file
);
4185 tree_request(struct view
*view
, enum request request
, struct line
*line
)
4187 enum open_flags flags
;
4190 case REQ_VIEW_BLAME
:
4191 if (line
->type
!= LINE_TREE_FILE
) {
4192 report("Blame only supported for files");
4196 string_copy(opt_ref
, view
->vid
);
4200 if (line
->type
!= LINE_TREE_FILE
) {
4201 report("Edit only supported for files");
4202 } else if (!is_head_commit(view
->vid
)) {
4205 open_editor(TRUE
, opt_file
);
4209 case REQ_TOGGLE_SORT_FIELD
:
4210 case REQ_TOGGLE_SORT_ORDER
:
4211 sort_view(view
, request
, &tree_sort_state
, tree_compare
);
4216 /* quit view if at top of tree */
4217 return REQ_VIEW_CLOSE
;
4220 line
= &view
->line
[1];
4230 /* Cleanup the stack if the tree view is at a different tree. */
4231 while (!*opt_path
&& tree_stack
)
4232 pop_tree_stack_entry();
4234 switch (line
->type
) {
4236 /* Depending on whether it is a subdirectory or parent link
4237 * mangle the path buffer. */
4238 if (line
== &view
->line
[1] && *opt_path
) {
4239 pop_tree_stack_entry();
4242 const char *basename
= tree_path(line
);
4244 push_tree_stack_entry(basename
, view
->lineno
);
4247 /* Trees and subtrees share the same ID, so they are not not
4248 * unique like blobs. */
4249 flags
= OPEN_RELOAD
;
4250 request
= REQ_VIEW_TREE
;
4253 case LINE_TREE_FILE
:
4254 flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4255 request
= REQ_VIEW_BLOB
;
4262 open_view(view
, request
, flags
);
4263 if (request
== REQ_VIEW_TREE
)
4264 view
->lineno
= tree_lineno
;
4270 tree_grep(struct view
*view
, struct line
*line
)
4272 struct tree_entry
*entry
= line
->data
;
4273 const char *text
[] = {
4275 opt_author
? entry
->author
: "",
4276 opt_date
? mkdate(&entry
->time
) : "",
4280 return grep_text(view
, text
);
4284 tree_select(struct view
*view
, struct line
*line
)
4286 struct tree_entry
*entry
= line
->data
;
4288 if (line
->type
== LINE_TREE_FILE
) {
4289 string_copy_rev(ref_blob
, entry
->id
);
4290 string_format(opt_file
, "%s%s", opt_path
, tree_path(line
));
4292 } else if (line
->type
!= LINE_TREE_DIR
) {
4296 string_copy_rev(view
->ref
, entry
->id
);
4299 static const char *tree_argv
[SIZEOF_ARG
] = {
4300 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4303 static struct view_ops tree_ops
= {
4315 blob_read(struct view
*view
, char *line
)
4319 return add_line_text(view
, line
, LINE_DEFAULT
) != NULL
;
4323 blob_request(struct view
*view
, enum request request
, struct line
*line
)
4330 return pager_request(view
, request
, line
);
4334 static const char *blob_argv
[SIZEOF_ARG
] = {
4335 "git", "cat-file", "blob", "%(blob)", NULL
4338 static struct view_ops blob_ops
= {
4352 * Loading the blame view is a two phase job:
4354 * 1. File content is read either using opt_file from the
4355 * filesystem or using git-cat-file.
4356 * 2. Then blame information is incrementally added by
4357 * reading output from git-blame.
4360 static const char *blame_head_argv
[] = {
4361 "git", "blame", "--incremental", "--", "%(file)", NULL
4364 static const char *blame_ref_argv
[] = {
4365 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4368 static const char *blame_cat_file_argv
[] = {
4369 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4372 struct blame_commit
{
4373 char id
[SIZEOF_REV
]; /* SHA1 ID. */
4374 char title
[128]; /* First line of the commit message. */
4375 const char *author
; /* Author of the commit. */
4376 time_t time
; /* Date from the author ident. */
4377 char filename
[128]; /* Name of file. */
4378 bool has_previous
; /* Was a "previous" line detected. */
4382 struct blame_commit
*commit
;
4383 unsigned long lineno
;
4388 blame_open(struct view
*view
)
4390 if (*opt_ref
|| !io_open(&view
->io
, opt_file
)) {
4391 if (!run_io_rd(&view
->io
, blame_cat_file_argv
, FORMAT_ALL
))
4395 setup_update(view
, opt_file
);
4396 string_format(view
->ref
, "%s ...", opt_file
);
4401 static struct blame_commit
*
4402 get_blame_commit(struct view
*view
, const char *id
)
4406 for (i
= 0; i
< view
->lines
; i
++) {
4407 struct blame
*blame
= view
->line
[i
].data
;
4412 if (!strncmp(blame
->commit
->id
, id
, SIZEOF_REV
- 1))
4413 return blame
->commit
;
4417 struct blame_commit
*commit
= calloc(1, sizeof(*commit
));
4420 string_ncopy(commit
->id
, id
, SIZEOF_REV
);
4426 parse_number(const char **posref
, size_t *number
, size_t min
, size_t max
)
4428 const char *pos
= *posref
;
4431 pos
= strchr(pos
+ 1, ' ');
4432 if (!pos
|| !isdigit(pos
[1]))
4434 *number
= atoi(pos
+ 1);
4435 if (*number
< min
|| *number
> max
)
4442 static struct blame_commit
*
4443 parse_blame_commit(struct view
*view
, const char *text
, int *blamed
)
4445 struct blame_commit
*commit
;
4446 struct blame
*blame
;
4447 const char *pos
= text
+ SIZEOF_REV
- 2;
4448 size_t orig_lineno
= 0;
4452 if (strlen(text
) <= SIZEOF_REV
|| pos
[1] != ' ')
4455 if (!parse_number(&pos
, &orig_lineno
, 1, 9999999) ||
4456 !parse_number(&pos
, &lineno
, 1, view
->lines
) ||
4457 !parse_number(&pos
, &group
, 1, view
->lines
- lineno
+ 1))
4460 commit
= get_blame_commit(view
, text
);
4466 struct line
*line
= &view
->line
[lineno
+ group
- 1];
4469 blame
->commit
= commit
;
4470 blame
->lineno
= orig_lineno
+ group
- 1;
4478 blame_read_file(struct view
*view
, const char *line
, bool *read_file
)
4481 const char **argv
= *opt_ref
? blame_ref_argv
: blame_head_argv
;
4484 if (view
->lines
== 0 && !view
->parent
)
4485 die("No blame exist for %s", view
->vid
);
4487 if (view
->lines
== 0 || !run_io_rd(&io
, argv
, FORMAT_ALL
)) {
4488 report("Failed to load blame data");
4492 done_io(view
->pipe
);
4498 size_t linelen
= strlen(line
);
4499 struct blame
*blame
= malloc(sizeof(*blame
) + linelen
);
4504 blame
->commit
= NULL
;
4505 strncpy(blame
->text
, line
, linelen
);
4506 blame
->text
[linelen
] = 0;
4507 return add_line_data(view
, blame
, LINE_BLAME_ID
) != NULL
;
4512 match_blame_header(const char *name
, char **line
)
4514 size_t namelen
= strlen(name
);
4515 bool matched
= !strncmp(name
, *line
, namelen
);
4524 blame_read(struct view
*view
, char *line
)
4526 static struct blame_commit
*commit
= NULL
;
4527 static int blamed
= 0;
4528 static bool read_file
= TRUE
;
4531 return blame_read_file(view
, line
, &read_file
);
4538 string_format(view
->ref
, "%s", view
->vid
);
4539 if (view_is_displayed(view
)) {
4540 update_view_title(view
);
4541 redraw_view_from(view
, 0);
4547 commit
= parse_blame_commit(view
, line
, &blamed
);
4548 string_format(view
->ref
, "%s %2d%%", view
->vid
,
4549 view
->lines
? blamed
* 100 / view
->lines
: 0);
4551 } else if (match_blame_header("author ", &line
)) {
4552 commit
->author
= get_author(line
);
4554 } else if (match_blame_header("author-time ", &line
)) {
4555 commit
->time
= (time_t) atol(line
);
4557 } else if (match_blame_header("author-tz ", &line
)) {
4558 parse_timezone(&commit
->time
, line
);
4560 } else if (match_blame_header("summary ", &line
)) {
4561 string_ncopy(commit
->title
, line
, strlen(line
));
4563 } else if (match_blame_header("previous ", &line
)) {
4564 commit
->has_previous
= TRUE
;
4566 } else if (match_blame_header("filename ", &line
)) {
4567 string_ncopy(commit
->filename
, line
, strlen(line
));
4575 blame_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4577 struct blame
*blame
= line
->data
;
4578 time_t *time
= NULL
;
4579 const char *id
= NULL
, *author
= NULL
;
4580 char text
[SIZEOF_STR
];
4582 if (blame
->commit
&& *blame
->commit
->filename
) {
4583 id
= blame
->commit
->id
;
4584 author
= blame
->commit
->author
;
4585 time
= &blame
->commit
->time
;
4588 if (opt_date
&& draw_date(view
, time
))
4591 if (opt_author
&& draw_author(view
, author
))
4594 if (draw_field(view
, LINE_BLAME_ID
, id
, ID_COLS
, FALSE
))
4597 if (draw_lineno(view
, lineno
))
4600 string_expand(text
, sizeof(text
), blame
->text
, opt_tab_size
);
4601 draw_text(view
, LINE_DEFAULT
, text
, TRUE
);
4606 check_blame_commit(struct blame
*blame
, bool check_null_id
)
4609 report("Commit data not loaded yet");
4610 else if (check_null_id
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4611 report("No commit exist for the selected line");
4618 setup_blame_parent_line(struct view
*view
, struct blame
*blame
)
4620 const char *diff_tree_argv
[] = {
4621 "git", "diff-tree", "-U0", blame
->commit
->id
,
4622 "--", blame
->commit
->filename
, NULL
4625 int parent_lineno
= -1;
4626 int blamed_lineno
= -1;
4629 if (!run_io(&io
, diff_tree_argv
, NULL
, IO_RD
))
4632 while ((line
= io_get(&io
, '\n', TRUE
))) {
4634 char *pos
= strchr(line
, '+');
4636 parent_lineno
= atoi(line
+ 4);
4638 blamed_lineno
= atoi(pos
+ 1);
4640 } else if (*line
== '+' && parent_lineno
!= -1) {
4641 if (blame
->lineno
== blamed_lineno
- 1 &&
4642 !strcmp(blame
->text
, line
+ 1)) {
4643 view
->lineno
= parent_lineno
? parent_lineno
- 1 : 0;
4654 blame_request(struct view
*view
, enum request request
, struct line
*line
)
4656 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
4657 struct blame
*blame
= line
->data
;
4660 case REQ_VIEW_BLAME
:
4661 if (check_blame_commit(blame
, TRUE
)) {
4662 string_copy(opt_ref
, blame
->commit
->id
);
4663 string_copy(opt_file
, blame
->commit
->filename
);
4665 view
->lineno
= blame
->lineno
;
4666 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4671 if (check_blame_commit(blame
, TRUE
) &&
4672 select_commit_parent(blame
->commit
->id
, opt_ref
,
4673 blame
->commit
->filename
)) {
4674 string_copy(opt_file
, blame
->commit
->filename
);
4675 setup_blame_parent_line(view
, blame
);
4676 open_view(view
, REQ_VIEW_BLAME
, OPEN_REFRESH
);
4681 if (!check_blame_commit(blame
, FALSE
))
4684 if (view_is_displayed(VIEW(REQ_VIEW_DIFF
)) &&
4685 !strcmp(blame
->commit
->id
, VIEW(REQ_VIEW_DIFF
)->ref
))
4688 if (!strcmp(blame
->commit
->id
, NULL_ID
)) {
4689 struct view
*diff
= VIEW(REQ_VIEW_DIFF
);
4690 const char *diff_index_argv
[] = {
4691 "git", "diff-index", "--root", "--patch-with-stat",
4692 "-C", "-M", "HEAD", "--", view
->vid
, NULL
4695 if (!blame
->commit
->has_previous
) {
4696 diff_index_argv
[1] = "diff";
4697 diff_index_argv
[2] = "--no-color";
4698 diff_index_argv
[6] = "--";
4699 diff_index_argv
[7] = "/dev/null";
4702 if (!prepare_update(diff
, diff_index_argv
, NULL
, FORMAT_DASH
)) {
4703 report("Failed to allocate diff command");
4706 flags
|= OPEN_PREPARED
;
4709 open_view(view
, REQ_VIEW_DIFF
, flags
);
4710 if (VIEW(REQ_VIEW_DIFF
)->pipe
&& !strcmp(blame
->commit
->id
, NULL_ID
))
4711 string_copy_rev(VIEW(REQ_VIEW_DIFF
)->ref
, NULL_ID
);
4722 blame_grep(struct view
*view
, struct line
*line
)
4724 struct blame
*blame
= line
->data
;
4725 struct blame_commit
*commit
= blame
->commit
;
4726 const char *text
[] = {
4728 commit
? commit
->title
: "",
4729 commit
? commit
->id
: "",
4730 commit
&& opt_author
? commit
->author
: "",
4731 commit
&& opt_date
? mkdate(&commit
->time
) : "",
4735 return grep_text(view
, text
);
4739 blame_select(struct view
*view
, struct line
*line
)
4741 struct blame
*blame
= line
->data
;
4742 struct blame_commit
*commit
= blame
->commit
;
4747 if (!strcmp(commit
->id
, NULL_ID
))
4748 string_ncopy(ref_commit
, "HEAD", 4);
4750 string_copy_rev(ref_commit
, commit
->id
);
4753 static struct view_ops blame_ops
= {
4769 const char *author
; /* Author of the last commit. */
4770 time_t time
; /* Date of the last activity. */
4771 struct ref
*ref
; /* Name and commit ID information. */
4774 static const enum sort_field branch_sort_fields
[] = {
4775 ORDERBY_NAME
, ORDERBY_DATE
, ORDERBY_AUTHOR
4777 static struct sort_state branch_sort_state
= SORT_STATE(branch_sort_fields
);
4780 branch_compare(const void *l1
, const void *l2
)
4782 const struct branch
*branch1
= ((const struct line
*) l1
)->data
;
4783 const struct branch
*branch2
= ((const struct line
*) l2
)->data
;
4785 switch (get_sort_field(branch_sort_state
)) {
4787 return sort_order(branch_sort_state
, branch1
->time
- branch2
->time
);
4789 case ORDERBY_AUTHOR
:
4790 return sort_order(branch_sort_state
, strcmp(branch1
->author
, branch2
->author
));
4794 return sort_order(branch_sort_state
, strcmp(branch1
->ref
->name
, branch2
->ref
->name
));
4799 branch_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
4801 struct branch
*branch
= line
->data
;
4802 enum line_type type
= branch
->ref
->head
? LINE_MAIN_HEAD
: LINE_DEFAULT
;
4804 if (opt_date
&& draw_date(view
, branch
->author
? &branch
->time
: NULL
))
4807 if (opt_author
&& draw_author(view
, branch
->author
))
4810 draw_text(view
, type
, branch
->ref
->name
, TRUE
);
4815 branch_request(struct view
*view
, enum request request
, struct line
*line
)
4820 open_view(view
, REQ_VIEW_BRANCH
, OPEN_REFRESH
);
4823 case REQ_TOGGLE_SORT_FIELD
:
4824 case REQ_TOGGLE_SORT_ORDER
:
4825 sort_view(view
, request
, &branch_sort_state
, branch_compare
);
4829 open_view(view
, REQ_VIEW_MAIN
, OPEN_SPLIT
);
4838 branch_read(struct view
*view
, char *line
)
4840 static char id
[SIZEOF_REV
];
4841 struct branch
*reference
;
4847 switch (get_line_type(line
)) {
4849 string_copy_rev(id
, line
+ STRING_SIZE("commit "));
4853 for (i
= 0, reference
= NULL
; i
< view
->lines
; i
++) {
4854 struct branch
*branch
= view
->line
[i
].data
;
4856 if (strcmp(branch
->ref
->id
, id
))
4859 view
->line
[i
].dirty
= TRUE
;
4861 branch
->author
= reference
->author
;
4862 branch
->time
= reference
->time
;
4866 parse_author_line(line
+ STRING_SIZE("author "),
4867 &branch
->author
, &branch
->time
);
4879 branch_open_visitor(void *data
, struct ref
*ref
)
4881 struct view
*view
= data
;
4882 struct branch
*branch
;
4884 if (ref
->tag
|| ref
->ltag
|| ref
->remote
)
4887 branch
= calloc(1, sizeof(*branch
));
4892 return !!add_line_data(view
, branch
, LINE_DEFAULT
);
4896 branch_open(struct view
*view
)
4898 const char *branch_log
[] = {
4899 "git", "log", "--no-color", "--pretty=raw",
4900 "--simplify-by-decoration", "--all", NULL
4903 if (!run_io_rd(&view
->io
, branch_log
, FORMAT_NONE
)) {
4904 report("Failed to load branch data");
4908 setup_update(view
, view
->id
);
4909 foreach_ref(branch_open_visitor
, view
);
4910 view
->p_restore
= TRUE
;
4916 branch_grep(struct view
*view
, struct line
*line
)
4918 struct branch
*branch
= line
->data
;
4919 const char *text
[] = {
4925 return grep_text(view
, text
);
4929 branch_select(struct view
*view
, struct line
*line
)
4931 struct branch
*branch
= line
->data
;
4933 string_copy_rev(view
->ref
, branch
->ref
->id
);
4934 string_copy_rev(ref_commit
, branch
->ref
->id
);
4935 string_copy_rev(ref_head
, branch
->ref
->id
);
4938 static struct view_ops branch_ops
= {
4957 char rev
[SIZEOF_REV
];
4958 char name
[SIZEOF_STR
];
4962 char rev
[SIZEOF_REV
];
4963 char name
[SIZEOF_STR
];
4967 static char status_onbranch
[SIZEOF_STR
];
4968 static struct status stage_status
;
4969 static enum line_type stage_line_type
;
4970 static size_t stage_chunks
;
4971 static int *stage_chunk
;
4973 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
4975 /* This should work even for the "On branch" line. */
4977 status_has_none(struct view
*view
, struct line
*line
)
4979 return line
< view
->line
+ view
->lines
&& !line
[1].data
;
4982 /* Get fields from the diff line:
4983 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4986 status_get_diff(struct status
*file
, const char *buf
, size_t bufsize
)
4988 const char *old_mode
= buf
+ 1;
4989 const char *new_mode
= buf
+ 8;
4990 const char *old_rev
= buf
+ 15;
4991 const char *new_rev
= buf
+ 56;
4992 const char *status
= buf
+ 97;
4995 old_mode
[-1] != ':' ||
4996 new_mode
[-1] != ' ' ||
4997 old_rev
[-1] != ' ' ||
4998 new_rev
[-1] != ' ' ||
5002 file
->status
= *status
;
5004 string_copy_rev(file
->old
.rev
, old_rev
);
5005 string_copy_rev(file
->new.rev
, new_rev
);
5007 file
->old
.mode
= strtoul(old_mode
, NULL
, 8);
5008 file
->new.mode
= strtoul(new_mode
, NULL
, 8);
5010 file
->old
.name
[0] = file
->new.name
[0] = 0;
5016 status_run(struct view
*view
, const char *argv
[], char status
, enum line_type type
)
5018 struct status
*unmerged
= NULL
;
5022 if (!run_io(&io
, argv
, NULL
, IO_RD
))
5025 add_line_data(view
, NULL
, type
);
5027 while ((buf
= io_get(&io
, 0, TRUE
))) {
5028 struct status
*file
= unmerged
;
5031 file
= calloc(1, sizeof(*file
));
5032 if (!file
|| !add_line_data(view
, file
, type
))
5036 /* Parse diff info part. */
5038 file
->status
= status
;
5040 string_copy(file
->old
.rev
, NULL_ID
);
5042 } else if (!file
->status
|| file
== unmerged
) {
5043 if (!status_get_diff(file
, buf
, strlen(buf
)))
5046 buf
= io_get(&io
, 0, TRUE
);
5050 /* Collapse all modified entries that follow an
5051 * associated unmerged entry. */
5052 if (unmerged
== file
) {
5053 unmerged
->status
= 'U';
5055 } else if (file
->status
== 'U') {
5060 /* Grab the old name for rename/copy. */
5061 if (!*file
->old
.name
&&
5062 (file
->status
== 'R' || file
->status
== 'C')) {
5063 string_ncopy(file
->old
.name
, buf
, strlen(buf
));
5065 buf
= io_get(&io
, 0, TRUE
);
5070 /* git-ls-files just delivers a NUL separated list of
5071 * file names similar to the second half of the
5072 * git-diff-* output. */
5073 string_ncopy(file
->new.name
, buf
, strlen(buf
));
5074 if (!*file
->old
.name
)
5075 string_copy(file
->old
.name
, file
->new.name
);
5079 if (io_error(&io
)) {
5085 if (!view
->line
[view
->lines
- 1].data
)
5086 add_line_data(view
, NULL
, LINE_STAT_NONE
);
5092 /* Don't show unmerged entries in the staged section. */
5093 static const char *status_diff_index_argv
[] = {
5094 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5095 "--cached", "-M", "HEAD", NULL
5098 static const char *status_diff_files_argv
[] = {
5099 "git", "diff-files", "-z", NULL
5102 static const char *status_list_other_argv
[] = {
5103 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5106 static const char *status_list_no_head_argv
[] = {
5107 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5110 static const char *update_index_argv
[] = {
5111 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5114 /* Restore the previous line number to stay in the context or select a
5115 * line with something that can be updated. */
5117 status_restore(struct view
*view
)
5119 if (view
->p_lineno
>= view
->lines
)
5120 view
->p_lineno
= view
->lines
- 1;
5121 while (view
->p_lineno
< view
->lines
&& !view
->line
[view
->p_lineno
].data
)
5123 while (view
->p_lineno
> 0 && !view
->line
[view
->p_lineno
].data
)
5126 /* If the above fails, always skip the "On branch" line. */
5127 if (view
->p_lineno
< view
->lines
)
5128 view
->lineno
= view
->p_lineno
;
5132 if (view
->lineno
< view
->offset
)
5133 view
->offset
= view
->lineno
;
5134 else if (view
->offset
+ view
->height
<= view
->lineno
)
5135 view
->offset
= view
->lineno
- view
->height
+ 1;
5137 view
->p_restore
= FALSE
;
5141 status_update_onbranch(void)
5143 static const char *paths
[][2] = {
5144 { "rebase-apply/rebasing", "Rebasing" },
5145 { "rebase-apply/applying", "Applying mailbox" },
5146 { "rebase-apply/", "Rebasing mailbox" },
5147 { "rebase-merge/interactive", "Interactive rebase" },
5148 { "rebase-merge/", "Rebase merge" },
5149 { "MERGE_HEAD", "Merging" },
5150 { "BISECT_LOG", "Bisecting" },
5151 { "HEAD", "On branch" },
5153 char buf
[SIZEOF_STR
];
5157 if (is_initial_commit()) {
5158 string_copy(status_onbranch
, "Initial commit");
5162 for (i
= 0; i
< ARRAY_SIZE(paths
); i
++) {
5163 char *head
= opt_head
;
5165 if (!string_format(buf
, "%s/%s", opt_git_dir
, paths
[i
][0]) ||
5166 lstat(buf
, &stat
) < 0)
5172 if (string_format(buf
, "%s/rebase-merge/head-name", opt_git_dir
) &&
5173 io_open(&io
, buf
) &&
5174 io_read_buf(&io
, buf
, sizeof(buf
))) {
5176 if (!prefixcmp(head
, "refs/heads/"))
5177 head
+= STRING_SIZE("refs/heads/");
5181 if (!string_format(status_onbranch
, "%s %s", paths
[i
][1], head
))
5182 string_copy(status_onbranch
, opt_head
);
5186 string_copy(status_onbranch
, "Not currently on any branch");
5189 /* First parse staged info using git-diff-index(1), then parse unstaged
5190 * info using git-diff-files(1), and finally untracked files using
5191 * git-ls-files(1). */
5193 status_open(struct view
*view
)
5197 add_line_data(view
, NULL
, LINE_STAT_HEAD
);
5198 status_update_onbranch();
5200 run_io_bg(update_index_argv
);
5202 if (is_initial_commit()) {
5203 if (!status_run(view
, status_list_no_head_argv
, 'A', LINE_STAT_STAGED
))
5205 } else if (!status_run(view
, status_diff_index_argv
, 0, LINE_STAT_STAGED
)) {
5209 if (!status_run(view
, status_diff_files_argv
, 0, LINE_STAT_UNSTAGED
) ||
5210 !status_run(view
, status_list_other_argv
, '?', LINE_STAT_UNTRACKED
))
5213 /* Restore the exact position or use the specialized restore
5215 if (!view
->p_restore
)
5216 status_restore(view
);
5221 status_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
5223 struct status
*status
= line
->data
;
5224 enum line_type type
;
5228 switch (line
->type
) {
5229 case LINE_STAT_STAGED
:
5230 type
= LINE_STAT_SECTION
;
5231 text
= "Changes to be committed:";
5234 case LINE_STAT_UNSTAGED
:
5235 type
= LINE_STAT_SECTION
;
5236 text
= "Changed but not updated:";
5239 case LINE_STAT_UNTRACKED
:
5240 type
= LINE_STAT_SECTION
;
5241 text
= "Untracked files:";
5244 case LINE_STAT_NONE
:
5245 type
= LINE_DEFAULT
;
5246 text
= " (no files)";
5249 case LINE_STAT_HEAD
:
5250 type
= LINE_STAT_HEAD
;
5251 text
= status_onbranch
;
5258 static char buf
[] = { '?', ' ', ' ', ' ', 0 };
5260 buf
[0] = status
->status
;
5261 if (draw_text(view
, line
->type
, buf
, TRUE
))
5263 type
= LINE_DEFAULT
;
5264 text
= status
->new.name
;
5267 draw_text(view
, type
, text
, TRUE
);
5272 status_load_error(struct view
*view
, struct view
*stage
, const char *path
)
5274 if (displayed_views() == 2 || display
[current_view
] != view
)
5275 maximize_view(view
);
5276 report("Failed to load '%s': %s", path
, io_strerror(&stage
->io
));
5281 status_enter(struct view
*view
, struct line
*line
)
5283 struct status
*status
= line
->data
;
5284 const char *oldpath
= status
? status
->old
.name
: NULL
;
5285 /* Diffs for unmerged entries are empty when passing the new
5286 * path, so leave it empty. */
5287 const char *newpath
= status
&& status
->status
!= 'U' ? status
->new.name
: NULL
;
5289 enum open_flags split
;
5290 struct view
*stage
= VIEW(REQ_VIEW_STAGE
);
5292 if (line
->type
== LINE_STAT_NONE
||
5293 (!status
&& line
[1].type
== LINE_STAT_NONE
)) {
5294 report("No file to diff");
5298 switch (line
->type
) {
5299 case LINE_STAT_STAGED
:
5300 if (is_initial_commit()) {
5301 const char *no_head_diff_argv
[] = {
5302 "git", "diff", "--no-color", "--patch-with-stat",
5303 "--", "/dev/null", newpath
, NULL
5306 if (!prepare_update(stage
, no_head_diff_argv
, opt_cdup
, FORMAT_DASH
))
5307 return status_load_error(view
, stage
, newpath
);
5309 const char *index_show_argv
[] = {
5310 "git", "diff-index", "--root", "--patch-with-stat",
5311 "-C", "-M", "--cached", "HEAD", "--",
5312 oldpath
, newpath
, NULL
5315 if (!prepare_update(stage
, index_show_argv
, opt_cdup
, FORMAT_DASH
))
5316 return status_load_error(view
, stage
, newpath
);
5320 info
= "Staged changes to %s";
5322 info
= "Staged changes";
5325 case LINE_STAT_UNSTAGED
:
5327 const char *files_show_argv
[] = {
5328 "git", "diff-files", "--root", "--patch-with-stat",
5329 "-C", "-M", "--", oldpath
, newpath
, NULL
5332 if (!prepare_update(stage
, files_show_argv
, opt_cdup
, FORMAT_DASH
))
5333 return status_load_error(view
, stage
, newpath
);
5335 info
= "Unstaged changes to %s";
5337 info
= "Unstaged changes";
5340 case LINE_STAT_UNTRACKED
:
5342 report("No file to show");
5346 if (!suffixcmp(status
->new.name
, -1, "/")) {
5347 report("Cannot display a directory");
5351 if (!prepare_update_file(stage
, newpath
))
5352 return status_load_error(view
, stage
, newpath
);
5353 info
= "Untracked file %s";
5356 case LINE_STAT_HEAD
:
5360 die("line type %d not handled in switch", line
->type
);
5363 split
= view_is_displayed(view
) ? OPEN_SPLIT
: 0;
5364 open_view(view
, REQ_VIEW_STAGE
, OPEN_PREPARED
| split
);
5365 if (view_is_displayed(VIEW(REQ_VIEW_STAGE
))) {
5367 stage_status
= *status
;
5369 memset(&stage_status
, 0, sizeof(stage_status
));
5372 stage_line_type
= line
->type
;
5374 string_format(VIEW(REQ_VIEW_STAGE
)->ref
, info
, stage_status
.new.name
);
5381 status_exists(struct status
*status
, enum line_type type
)
5383 struct view
*view
= VIEW(REQ_VIEW_STATUS
);
5384 unsigned long lineno
;
5386 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
5387 struct line
*line
= &view
->line
[lineno
];
5388 struct status
*pos
= line
->data
;
5390 if (line
->type
!= type
)
5392 if (!pos
&& (!status
|| !status
->status
) && line
[1].data
) {
5393 select_view_line(view
, lineno
);
5396 if (pos
&& !strcmp(status
->new.name
, pos
->new.name
)) {
5397 select_view_line(view
, lineno
);
5407 status_update_prepare(struct io
*io
, enum line_type type
)
5409 const char *staged_argv
[] = {
5410 "git", "update-index", "-z", "--index-info", NULL
5412 const char *others_argv
[] = {
5413 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5417 case LINE_STAT_STAGED
:
5418 return run_io(io
, staged_argv
, opt_cdup
, IO_WR
);
5420 case LINE_STAT_UNSTAGED
:
5421 return run_io(io
, others_argv
, opt_cdup
, IO_WR
);
5423 case LINE_STAT_UNTRACKED
:
5424 return run_io(io
, others_argv
, NULL
, IO_WR
);
5427 die("line type %d not handled in switch", type
);
5433 status_update_write(struct io
*io
, struct status
*status
, enum line_type type
)
5435 char buf
[SIZEOF_STR
];
5439 case LINE_STAT_STAGED
:
5440 if (!string_format_from(buf
, &bufsize
, "%06o %s\t%s%c",
5443 status
->old
.name
, 0))
5447 case LINE_STAT_UNSTAGED
:
5448 case LINE_STAT_UNTRACKED
:
5449 if (!string_format_from(buf
, &bufsize
, "%s%c", status
->new.name
, 0))
5454 die("line type %d not handled in switch", type
);
5457 return io_write(io
, buf
, bufsize
);
5461 status_update_file(struct status
*status
, enum line_type type
)
5466 if (!status_update_prepare(&io
, type
))
5469 result
= status_update_write(&io
, status
, type
);
5470 return done_io(&io
) && result
;
5474 status_update_files(struct view
*view
, struct line
*line
)
5476 char buf
[sizeof(view
->ref
)];
5479 struct line
*pos
= view
->line
+ view
->lines
;
5482 int cursor_y
, cursor_x
;
5484 if (!status_update_prepare(&io
, line
->type
))
5487 for (pos
= line
; pos
< view
->line
+ view
->lines
&& pos
->data
; pos
++)
5490 string_copy(buf
, view
->ref
);
5491 getsyx(cursor_y
, cursor_x
);
5492 for (file
= 0, done
= 5; result
&& file
< files
; line
++, file
++) {
5493 int almost_done
= file
* 100 / files
;
5495 if (almost_done
> done
) {
5497 string_format(view
->ref
, "updating file %u of %u (%d%% done)",
5499 update_view_title(view
);
5500 setsyx(cursor_y
, cursor_x
);
5503 result
= status_update_write(&io
, line
->data
, line
->type
);
5505 string_copy(view
->ref
, buf
);
5507 return done_io(&io
) && result
;
5511 status_update(struct view
*view
)
5513 struct line
*line
= &view
->line
[view
->lineno
];
5515 assert(view
->lines
);
5518 /* This should work even for the "On branch" line. */
5519 if (line
< view
->line
+ view
->lines
&& !line
[1].data
) {
5520 report("Nothing to update");
5524 if (!status_update_files(view
, line
+ 1)) {
5525 report("Failed to update file status");
5529 } else if (!status_update_file(line
->data
, line
->type
)) {
5530 report("Failed to update file status");
5538 status_revert(struct status
*status
, enum line_type type
, bool has_none
)
5540 if (!status
|| type
!= LINE_STAT_UNSTAGED
) {
5541 if (type
== LINE_STAT_STAGED
) {
5542 report("Cannot revert changes to staged files");
5543 } else if (type
== LINE_STAT_UNTRACKED
) {
5544 report("Cannot revert changes to untracked files");
5545 } else if (has_none
) {
5546 report("Nothing to revert");
5548 report("Cannot revert changes to multiple files");
5553 char mode
[10] = "100644";
5554 const char *reset_argv
[] = {
5555 "git", "update-index", "--cacheinfo", mode
,
5556 status
->old
.rev
, status
->old
.name
, NULL
5558 const char *checkout_argv
[] = {
5559 "git", "checkout", "--", status
->old
.name
, NULL
5562 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5564 string_format(mode
, "%o", status
->old
.mode
);
5565 return (status
->status
!= 'U' || run_io_fg(reset_argv
, opt_cdup
)) &&
5566 run_io_fg(checkout_argv
, opt_cdup
);
5571 status_request(struct view
*view
, enum request request
, struct line
*line
)
5573 struct status
*status
= line
->data
;
5576 case REQ_STATUS_UPDATE
:
5577 if (!status_update(view
))
5581 case REQ_STATUS_REVERT
:
5582 if (!status_revert(status
, line
->type
, status_has_none(view
, line
)))
5586 case REQ_STATUS_MERGE
:
5587 if (!status
|| status
->status
!= 'U') {
5588 report("Merging only possible for files with unmerged status ('U').");
5591 open_mergetool(status
->new.name
);
5597 if (status
->status
== 'D') {
5598 report("File has been deleted.");
5602 open_editor(status
->status
!= '?', status
->new.name
);
5605 case REQ_VIEW_BLAME
:
5607 string_copy(opt_file
, status
->new.name
);
5613 /* After returning the status view has been split to
5614 * show the stage view. No further reloading is
5616 return status_enter(view
, line
);
5619 /* Simply reload the view. */
5626 open_view(view
, REQ_VIEW_STATUS
, OPEN_RELOAD
);
5632 status_select(struct view
*view
, struct line
*line
)
5634 struct status
*status
= line
->data
;
5635 char file
[SIZEOF_STR
] = "all files";
5639 if (status
&& !string_format(file
, "'%s'", status
->new.name
))
5642 if (!status
&& line
[1].type
== LINE_STAT_NONE
)
5645 switch (line
->type
) {
5646 case LINE_STAT_STAGED
:
5647 text
= "Press %s to unstage %s for commit";
5650 case LINE_STAT_UNSTAGED
:
5651 text
= "Press %s to stage %s for commit";
5654 case LINE_STAT_UNTRACKED
:
5655 text
= "Press %s to stage %s for addition";
5658 case LINE_STAT_HEAD
:
5659 case LINE_STAT_NONE
:
5660 text
= "Nothing to update";
5664 die("line type %d not handled in switch", line
->type
);
5667 if (status
&& status
->status
== 'U') {
5668 text
= "Press %s to resolve conflict in %s";
5669 key
= get_key(REQ_STATUS_MERGE
);
5672 key
= get_key(REQ_STATUS_UPDATE
);
5675 string_format(view
->ref
, text
, key
, file
);
5679 status_grep(struct view
*view
, struct line
*line
)
5681 struct status
*status
= line
->data
;
5684 const char buf
[2] = { status
->status
, 0 };
5685 const char *text
[] = { status
->new.name
, buf
, NULL
};
5687 return grep_text(view
, text
);
5693 static struct view_ops status_ops
= {
5706 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
5708 while (line
< end
) {
5709 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
5710 !io_write(io
, "\n", 1))
5713 if (line
->type
== LINE_DIFF_CHUNK
||
5714 line
->type
== LINE_DIFF_HEADER
)
5721 static struct line
*
5722 stage_diff_find(struct view
*view
, struct line
*line
, enum line_type type
)
5724 for (; view
->line
< line
; line
--)
5725 if (line
->type
== type
)
5732 stage_apply_chunk(struct view
*view
, struct line
*chunk
, bool revert
)
5734 const char *apply_argv
[SIZEOF_ARG
] = {
5735 "git", "apply", "--whitespace=nowarn", NULL
5737 struct line
*diff_hdr
;
5741 diff_hdr
= stage_diff_find(view
, chunk
, LINE_DIFF_HEADER
);
5746 apply_argv
[argc
++] = "--cached";
5747 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
5748 apply_argv
[argc
++] = "-R";
5749 apply_argv
[argc
++] = "-";
5750 apply_argv
[argc
++] = NULL
;
5751 if (!run_io(&io
, apply_argv
, opt_cdup
, IO_WR
))
5754 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
5755 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
5759 run_io_bg(update_index_argv
);
5761 return chunk
? TRUE
: FALSE
;
5765 stage_update(struct view
*view
, struct line
*line
)
5767 struct line
*chunk
= NULL
;
5769 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
5770 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5773 if (!stage_apply_chunk(view
, chunk
, FALSE
)) {
5774 report("Failed to apply chunk");
5778 } else if (!stage_status
.status
) {
5779 view
= VIEW(REQ_VIEW_STATUS
);
5781 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++)
5782 if (line
->type
== stage_line_type
)
5785 if (!status_update_files(view
, line
+ 1)) {
5786 report("Failed to update files");
5790 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
5791 report("Failed to update file");
5799 stage_revert(struct view
*view
, struct line
*line
)
5801 struct line
*chunk
= NULL
;
5803 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
5804 chunk
= stage_diff_find(view
, line
, LINE_DIFF_CHUNK
);
5807 if (!prompt_yesno("Are you sure you want to revert changes?"))
5810 if (!stage_apply_chunk(view
, chunk
, TRUE
)) {
5811 report("Failed to revert chunk");
5817 return status_revert(stage_status
.status
? &stage_status
: NULL
,
5818 stage_line_type
, FALSE
);
5824 stage_next(struct view
*view
, struct line
*line
)
5828 if (!stage_chunks
) {
5829 for (line
= view
->line
; line
< view
->line
+ view
->lines
; line
++) {
5830 if (line
->type
!= LINE_DIFF_CHUNK
)
5833 if (!realloc_ints(&stage_chunk
, stage_chunks
, 1)) {
5834 report("Allocation failure");
5838 stage_chunk
[stage_chunks
++] = line
- view
->line
;
5842 for (i
= 0; i
< stage_chunks
; i
++) {
5843 if (stage_chunk
[i
] > view
->lineno
) {
5844 do_scroll_view(view
, stage_chunk
[i
] - view
->lineno
);
5845 report("Chunk %d of %d", i
+ 1, stage_chunks
);
5850 report("No next chunk found");
5854 stage_request(struct view
*view
, enum request request
, struct line
*line
)
5857 case REQ_STATUS_UPDATE
:
5858 if (!stage_update(view
, line
))
5862 case REQ_STATUS_REVERT
:
5863 if (!stage_revert(view
, line
))
5867 case REQ_STAGE_NEXT
:
5868 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5869 report("File is untracked; press %s to add",
5870 get_key(REQ_STATUS_UPDATE
));
5873 stage_next(view
, line
);
5877 if (!stage_status
.new.name
[0])
5879 if (stage_status
.status
== 'D') {
5880 report("File has been deleted.");
5884 open_editor(stage_status
.status
!= '?', stage_status
.new.name
);
5888 /* Reload everything ... */
5891 case REQ_VIEW_BLAME
:
5892 if (stage_status
.new.name
[0]) {
5893 string_copy(opt_file
, stage_status
.new.name
);
5899 return pager_request(view
, request
, line
);
5905 VIEW(REQ_VIEW_STATUS
)->p_restore
= TRUE
;
5906 open_view(view
, REQ_VIEW_STATUS
, OPEN_REFRESH
);
5908 /* Check whether the staged entry still exists, and close the
5909 * stage view if it doesn't. */
5910 if (!status_exists(&stage_status
, stage_line_type
)) {
5911 status_restore(VIEW(REQ_VIEW_STATUS
));
5912 return REQ_VIEW_CLOSE
;
5915 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
5916 if (!suffixcmp(stage_status
.new.name
, -1, "/")) {
5917 report("Cannot display a directory");
5921 if (!prepare_update_file(view
, stage_status
.new.name
)) {
5922 report("Failed to open file: %s", strerror(errno
));
5926 open_view(view
, REQ_VIEW_STAGE
, OPEN_REFRESH
);
5931 static struct view_ops stage_ops
= {
5948 char id
[SIZEOF_REV
]; /* SHA1 ID. */
5949 char title
[128]; /* First line of the commit message. */
5950 const char *author
; /* Author of the commit. */
5951 time_t time
; /* Date from the author ident. */
5952 struct ref_list
*refs
; /* Repository references. */
5953 chtype graph
[SIZEOF_REVGRAPH
]; /* Ancestry chain graphics. */
5954 size_t graph_size
; /* The width of the graph array. */
5955 bool has_parents
; /* Rewritten --parents seen. */
5958 /* Size of rev graph with no "padding" columns */
5959 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5962 struct rev_graph
*prev
, *next
, *parents
;
5963 char rev
[SIZEOF_REVITEMS
][SIZEOF_REV
];
5965 struct commit
*commit
;
5967 unsigned int boundary
:1;
5970 /* Parents of the commit being visualized. */
5971 static struct rev_graph graph_parents
[4];
5973 /* The current stack of revisions on the graph. */
5974 static struct rev_graph graph_stacks
[4] = {
5975 { &graph_stacks
[3], &graph_stacks
[1], &graph_parents
[0] },
5976 { &graph_stacks
[0], &graph_stacks
[2], &graph_parents
[1] },
5977 { &graph_stacks
[1], &graph_stacks
[3], &graph_parents
[2] },
5978 { &graph_stacks
[2], &graph_stacks
[0], &graph_parents
[3] },
5982 graph_parent_is_merge(struct rev_graph
*graph
)
5984 return graph
->parents
->size
> 1;
5988 append_to_rev_graph(struct rev_graph
*graph
, chtype symbol
)
5990 struct commit
*commit
= graph
->commit
;
5992 if (commit
->graph_size
< ARRAY_SIZE(commit
->graph
) - 1)
5993 commit
->graph
[commit
->graph_size
++] = symbol
;
5997 clear_rev_graph(struct rev_graph
*graph
)
5999 graph
->boundary
= 0;
6000 graph
->size
= graph
->pos
= 0;
6001 graph
->commit
= NULL
;
6002 memset(graph
->parents
, 0, sizeof(*graph
->parents
));
6006 done_rev_graph(struct rev_graph
*graph
)
6008 if (graph_parent_is_merge(graph
) &&
6009 graph
->pos
< graph
->size
- 1 &&
6010 graph
->next
->size
== graph
->size
+ graph
->parents
->size
- 1) {
6011 size_t i
= graph
->pos
+ graph
->parents
->size
- 1;
6013 graph
->commit
->graph_size
= i
* 2;
6014 while (i
< graph
->next
->size
- 1) {
6015 append_to_rev_graph(graph
, ' ');
6016 append_to_rev_graph(graph
, '\\');
6021 clear_rev_graph(graph
);
6025 push_rev_graph(struct rev_graph
*graph
, const char *parent
)
6029 /* "Collapse" duplicate parents lines.
6031 * FIXME: This needs to also update update the drawn graph but
6032 * for now it just serves as a method for pruning graph lines. */
6033 for (i
= 0; i
< graph
->size
; i
++)
6034 if (!strncmp(graph
->rev
[i
], parent
, SIZEOF_REV
))
6037 if (graph
->size
< SIZEOF_REVITEMS
) {
6038 string_copy_rev(graph
->rev
[graph
->size
++], parent
);
6043 get_rev_graph_symbol(struct rev_graph
*graph
)
6047 if (graph
->boundary
)
6048 symbol
= REVGRAPH_BOUND
;
6049 else if (graph
->parents
->size
== 0)
6050 symbol
= REVGRAPH_INIT
;
6051 else if (graph_parent_is_merge(graph
))
6052 symbol
= REVGRAPH_MERGE
;
6053 else if (graph
->pos
>= graph
->size
)
6054 symbol
= REVGRAPH_BRANCH
;
6056 symbol
= REVGRAPH_COMMIT
;
6062 draw_rev_graph(struct rev_graph
*graph
)
6065 chtype separator
, line
;
6067 enum { DEFAULT
, RSHARP
, RDIAG
, LDIAG
};
6068 static struct rev_filler fillers
[] = {
6074 chtype symbol
= get_rev_graph_symbol(graph
);
6075 struct rev_filler
*filler
;
6078 if (opt_line_graphics
)
6079 fillers
[DEFAULT
].line
= line_graphics
[LINE_GRAPHIC_VLINE
];
6081 filler
= &fillers
[DEFAULT
];
6083 for (i
= 0; i
< graph
->pos
; i
++) {
6084 append_to_rev_graph(graph
, filler
->line
);
6085 if (graph_parent_is_merge(graph
->prev
) &&
6086 graph
->prev
->pos
== i
)
6087 filler
= &fillers
[RSHARP
];
6089 append_to_rev_graph(graph
, filler
->separator
);
6092 /* Place the symbol for this revision. */
6093 append_to_rev_graph(graph
, symbol
);
6095 if (graph
->prev
->size
> graph
->size
)
6096 filler
= &fillers
[RDIAG
];
6098 filler
= &fillers
[DEFAULT
];
6102 for (; i
< graph
->size
; i
++) {
6103 append_to_rev_graph(graph
, filler
->separator
);
6104 append_to_rev_graph(graph
, filler
->line
);
6105 if (graph_parent_is_merge(graph
->prev
) &&
6106 i
< graph
->prev
->pos
+ graph
->parents
->size
)
6107 filler
= &fillers
[RSHARP
];
6108 if (graph
->prev
->size
> graph
->size
)
6109 filler
= &fillers
[LDIAG
];
6112 if (graph
->prev
->size
> graph
->size
) {
6113 append_to_rev_graph(graph
, filler
->separator
);
6114 if (filler
->line
!= ' ')
6115 append_to_rev_graph(graph
, filler
->line
);
6119 /* Prepare the next rev graph */
6121 prepare_rev_graph(struct rev_graph
*graph
)
6125 /* First, traverse all lines of revisions up to the active one. */
6126 for (graph
->pos
= 0; graph
->pos
< graph
->size
; graph
->pos
++) {
6127 if (!strcmp(graph
->rev
[graph
->pos
], graph
->commit
->id
))
6130 push_rev_graph(graph
->next
, graph
->rev
[graph
->pos
]);
6133 /* Interleave the new revision parent(s). */
6134 for (i
= 0; !graph
->boundary
&& i
< graph
->parents
->size
; i
++)
6135 push_rev_graph(graph
->next
, graph
->parents
->rev
[i
]);
6137 /* Lastly, put any remaining revisions. */
6138 for (i
= graph
->pos
+ 1; i
< graph
->size
; i
++)
6139 push_rev_graph(graph
->next
, graph
->rev
[i
]);
6143 update_rev_graph(struct view
*view
, struct rev_graph
*graph
)
6145 /* If this is the finalizing update ... */
6147 prepare_rev_graph(graph
);
6149 /* Graph visualization needs a one rev look-ahead,
6150 * so the first update doesn't visualize anything. */
6151 if (!graph
->prev
->commit
)
6154 if (view
->lines
> 2)
6155 view
->line
[view
->lines
- 3].dirty
= 1;
6156 if (view
->lines
> 1)
6157 view
->line
[view
->lines
- 2].dirty
= 1;
6158 draw_rev_graph(graph
->prev
);
6159 done_rev_graph(graph
->prev
->prev
);
6167 static const char *main_argv
[SIZEOF_ARG
] = {
6168 "git", "log", "--no-color", "--pretty=raw", "--parents",
6169 "--topo-order", "%(head)", NULL
6173 main_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
6175 struct commit
*commit
= line
->data
;
6177 if (!commit
->author
)
6180 if (opt_date
&& draw_date(view
, &commit
->time
))
6183 if (opt_author
&& draw_author(view
, commit
->author
))
6186 if (opt_rev_graph
&& commit
->graph_size
&&
6187 draw_graphic(view
, LINE_MAIN_REVGRAPH
, commit
->graph
, commit
->graph_size
))
6190 if (opt_show_refs
&& commit
->refs
) {
6193 for (i
= 0; i
< commit
->refs
->size
; i
++) {
6194 struct ref
*ref
= commit
->refs
->refs
[i
];
6195 enum line_type type
;
6198 type
= LINE_MAIN_HEAD
;
6200 type
= LINE_MAIN_LOCAL_TAG
;
6202 type
= LINE_MAIN_TAG
;
6203 else if (ref
->tracked
)
6204 type
= LINE_MAIN_TRACKED
;
6205 else if (ref
->remote
)
6206 type
= LINE_MAIN_REMOTE
;
6208 type
= LINE_MAIN_REF
;
6210 if (draw_text(view
, type
, "[", TRUE
) ||
6211 draw_text(view
, type
, ref
->name
, TRUE
) ||
6212 draw_text(view
, type
, "]", TRUE
))
6215 if (draw_text(view
, LINE_DEFAULT
, " ", TRUE
))
6220 draw_text(view
, LINE_DEFAULT
, commit
->title
, TRUE
);
6224 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6226 main_read(struct view
*view
, char *line
)
6228 static struct rev_graph
*graph
= graph_stacks
;
6229 enum line_type type
;
6230 struct commit
*commit
;
6235 if (!view
->lines
&& !view
->parent
)
6236 die("No revisions match the given arguments.");
6237 if (view
->lines
> 0) {
6238 commit
= view
->line
[view
->lines
- 1].data
;
6239 view
->line
[view
->lines
- 1].dirty
= 1;
6240 if (!commit
->author
) {
6243 graph
->commit
= NULL
;
6246 update_rev_graph(view
, graph
);
6248 for (i
= 0; i
< ARRAY_SIZE(graph_stacks
); i
++)
6249 clear_rev_graph(&graph_stacks
[i
]);
6253 type
= get_line_type(line
);
6254 if (type
== LINE_COMMIT
) {
6255 commit
= calloc(1, sizeof(struct commit
));
6259 line
+= STRING_SIZE("commit ");
6261 graph
->boundary
= 1;
6265 string_copy_rev(commit
->id
, line
);
6266 commit
->refs
= get_ref_list(commit
->id
);
6267 graph
->commit
= commit
;
6268 add_line_data(view
, commit
, LINE_MAIN_COMMIT
);
6270 while ((line
= strchr(line
, ' '))) {
6272 push_rev_graph(graph
->parents
, line
);
6273 commit
->has_parents
= TRUE
;
6280 commit
= view
->line
[view
->lines
- 1].data
;
6284 if (commit
->has_parents
)
6286 push_rev_graph(graph
->parents
, line
+ STRING_SIZE("parent "));
6290 parse_author_line(line
+ STRING_SIZE("author "),
6291 &commit
->author
, &commit
->time
);
6292 update_rev_graph(view
, graph
);
6293 graph
= graph
->next
;
6297 /* Fill in the commit title if it has not already been set. */
6298 if (commit
->title
[0])
6301 /* Require titles to start with a non-space character at the
6302 * offset used by git log. */
6303 if (strncmp(line
, " ", 4))
6306 /* Well, if the title starts with a whitespace character,
6307 * try to be forgiving. Otherwise we end up with no title. */
6308 while (isspace(*line
))
6312 /* FIXME: More graceful handling of titles; append "..." to
6313 * shortened titles, etc. */
6315 string_expand(commit
->title
, sizeof(commit
->title
), line
, 1);
6316 view
->line
[view
->lines
- 1].dirty
= 1;
6323 main_request(struct view
*view
, enum request request
, struct line
*line
)
6325 enum open_flags flags
= display
[0] == view
? OPEN_SPLIT
: OPEN_DEFAULT
;
6329 open_view(view
, REQ_VIEW_DIFF
, flags
);
6333 open_view(view
, REQ_VIEW_MAIN
, OPEN_REFRESH
);
6343 grep_refs(struct ref_list
*list
, regex_t
*regex
)
6348 if (!opt_show_refs
|| !list
)
6351 for (i
= 0; i
< list
->size
; i
++) {
6352 if (regexec(regex
, list
->refs
[i
]->name
, 1, &pmatch
, 0) != REG_NOMATCH
)
6360 main_grep(struct view
*view
, struct line
*line
)
6362 struct commit
*commit
= line
->data
;
6363 const char *text
[] = {
6365 opt_author
? commit
->author
: "",
6366 opt_date
? mkdate(&commit
->time
) : "",
6370 return grep_text(view
, text
) || grep_refs(commit
->refs
, view
->regex
);
6374 main_select(struct view
*view
, struct line
*line
)
6376 struct commit
*commit
= line
->data
;
6378 string_copy_rev(view
->ref
, commit
->id
);
6379 string_copy_rev(ref_commit
, view
->ref
);
6382 static struct view_ops main_ops
= {
6395 * Unicode / UTF-8 handling
6397 * NOTE: Much of the following code for dealing with Unicode is derived from
6398 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6399 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6403 unicode_width(unsigned long c
)
6406 (c
<= 0x115f /* Hangul Jamo */
6409 || (c
>= 0x2e80 && c
<= 0xa4cf && c
!= 0x303f)
6411 || (c
>= 0xac00 && c
<= 0xd7a3) /* Hangul Syllables */
6412 || (c
>= 0xf900 && c
<= 0xfaff) /* CJK Compatibility Ideographs */
6413 || (c
>= 0xfe30 && c
<= 0xfe6f) /* CJK Compatibility Forms */
6414 || (c
>= 0xff00 && c
<= 0xff60) /* Fullwidth Forms */
6415 || (c
>= 0xffe0 && c
<= 0xffe6)
6416 || (c
>= 0x20000 && c
<= 0x2fffd)
6417 || (c
>= 0x30000 && c
<= 0x3fffd)))
6421 return opt_tab_size
;
6426 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6427 * Illegal bytes are set one. */
6428 static const unsigned char utf8_bytes
[256] = {
6429 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,
6430 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,
6431 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,
6432 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,
6433 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,
6434 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,
6435 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,
6436 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,
6439 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6440 static inline unsigned long
6441 utf8_to_unicode(const char *string
, size_t length
)
6443 unsigned long unicode
;
6447 unicode
= string
[0];
6450 unicode
= (string
[0] & 0x1f) << 6;
6451 unicode
+= (string
[1] & 0x3f);
6454 unicode
= (string
[0] & 0x0f) << 12;
6455 unicode
+= ((string
[1] & 0x3f) << 6);
6456 unicode
+= (string
[2] & 0x3f);
6459 unicode
= (string
[0] & 0x0f) << 18;
6460 unicode
+= ((string
[1] & 0x3f) << 12);
6461 unicode
+= ((string
[2] & 0x3f) << 6);
6462 unicode
+= (string
[3] & 0x3f);
6465 unicode
= (string
[0] & 0x0f) << 24;
6466 unicode
+= ((string
[1] & 0x3f) << 18);
6467 unicode
+= ((string
[2] & 0x3f) << 12);
6468 unicode
+= ((string
[3] & 0x3f) << 6);
6469 unicode
+= (string
[4] & 0x3f);
6472 unicode
= (string
[0] & 0x01) << 30;
6473 unicode
+= ((string
[1] & 0x3f) << 24);
6474 unicode
+= ((string
[2] & 0x3f) << 18);
6475 unicode
+= ((string
[3] & 0x3f) << 12);
6476 unicode
+= ((string
[4] & 0x3f) << 6);
6477 unicode
+= (string
[5] & 0x3f);
6480 die("Invalid Unicode length");
6483 /* Invalid characters could return the special 0xfffd value but NUL
6484 * should be just as good. */
6485 return unicode
> 0xffff ? 0 : unicode
;
6488 /* Calculates how much of string can be shown within the given maximum width
6489 * and sets trimmed parameter to non-zero value if all of string could not be
6490 * shown. If the reserve flag is TRUE, it will reserve at least one
6491 * trailing character, which can be useful when drawing a delimiter.
6493 * Returns the number of bytes to output from string to satisfy max_width. */
6495 utf8_length(const char **start
, size_t skip
, int *width
, size_t max_width
, int *trimmed
, bool reserve
)
6497 const char *string
= *start
;
6498 const char *end
= strchr(string
, '\0');
6499 unsigned char last_bytes
= 0;
6500 size_t last_ucwidth
= 0;
6505 while (string
< end
) {
6506 int c
= *(unsigned char *) string
;
6507 unsigned char bytes
= utf8_bytes
[c
];
6509 unsigned long unicode
;
6511 if (string
+ bytes
> end
)
6514 /* Change representation to figure out whether
6515 * it is a single- or double-width character. */
6517 unicode
= utf8_to_unicode(string
, bytes
);
6518 /* FIXME: Graceful handling of invalid Unicode character. */
6522 ucwidth
= unicode_width(unicode
);
6524 skip
-= ucwidth
<= skip
? ucwidth
: skip
;
6528 if (*width
> max_width
) {
6531 if (reserve
&& *width
== max_width
) {
6532 string
-= last_bytes
;
6533 *width
-= last_ucwidth
;
6539 last_bytes
= ucwidth
? bytes
: 0;
6540 last_ucwidth
= ucwidth
;
6543 return string
- *start
;
6551 /* Whether or not the curses interface has been initialized. */
6552 static bool cursed
= FALSE
;
6554 /* Terminal hacks and workarounds. */
6555 static bool use_scroll_redrawwin
;
6556 static bool use_scroll_status_wclear
;
6558 /* The status window is used for polling keystrokes. */
6559 static WINDOW
*status_win
;
6561 /* Reading from the prompt? */
6562 static bool input_mode
= FALSE
;
6564 static bool status_empty
= FALSE
;
6566 /* Update status and title window. */
6568 report(const char *msg
, ...)
6570 struct view
*view
= display
[current_view
];
6576 char buf
[SIZEOF_STR
];
6579 va_start(args
, msg
);
6580 if (vsnprintf(buf
, sizeof(buf
), msg
, args
) >= sizeof(buf
)) {
6581 buf
[sizeof(buf
) - 1] = 0;
6582 buf
[sizeof(buf
) - 2] = '.';
6583 buf
[sizeof(buf
) - 3] = '.';
6584 buf
[sizeof(buf
) - 4] = '.';
6590 if (!status_empty
|| *msg
) {
6593 va_start(args
, msg
);
6595 wmove(status_win
, 0, 0);
6596 if (view
->has_scrolled
&& use_scroll_status_wclear
)
6599 vwprintw(status_win
, msg
, args
);
6600 status_empty
= FALSE
;
6602 status_empty
= TRUE
;
6604 wclrtoeol(status_win
);
6605 wnoutrefresh(status_win
);
6610 update_view_title(view
);
6613 /* Controls when nodelay should be in effect when polling user input. */
6615 set_nonblocking_input(bool loading
)
6617 static unsigned int loading_views
;
6619 if ((loading
== FALSE
&& loading_views
-- == 1) ||
6620 (loading
== TRUE
&& loading_views
++ == 0))
6621 nodelay(status_win
, loading
);
6630 /* Initialize the curses library */
6631 if (isatty(STDIN_FILENO
)) {
6632 cursed
= !!initscr();
6635 /* Leave stdin and stdout alone when acting as a pager. */
6636 opt_tty
= fopen("/dev/tty", "r+");
6638 die("Failed to open /dev/tty");
6639 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
6643 die("Failed to initialize curses");
6645 nonl(); /* Disable conversion and detect newlines from input. */
6646 cbreak(); /* Take input chars one at a time, no wait for \n */
6647 noecho(); /* Don't echo input */
6648 leaveok(stdscr
, FALSE
);
6653 getmaxyx(stdscr
, y
, x
);
6654 status_win
= newwin(1, 0, y
- 1, 0);
6656 die("Failed to create status window");
6658 /* Enable keyboard mapping */
6659 keypad(status_win
, TRUE
);
6660 wbkgdset(status_win
, get_line_attr(LINE_STATUS
));
6662 TABSIZE
= opt_tab_size
;
6663 if (opt_line_graphics
) {
6664 line_graphics
[LINE_GRAPHIC_VLINE
] = ACS_VLINE
;
6667 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
6668 if (term
&& !strcmp(term
, "gnome-terminal")) {
6669 /* In the gnome-terminal-emulator, the message from
6670 * scrolling up one line when impossible followed by
6671 * scrolling down one line causes corruption of the
6672 * status line. This is fixed by calling wclear. */
6673 use_scroll_status_wclear
= TRUE
;
6674 use_scroll_redrawwin
= FALSE
;
6676 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
6677 /* No problems with full optimizations in xrvt-(unicode)
6679 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
6682 /* When scrolling in (u)xterm the last line in the
6683 * scrolling direction will update slowly. */
6684 use_scroll_redrawwin
= TRUE
;
6685 use_scroll_status_wclear
= FALSE
;
6690 get_input(int prompt_position
)
6693 int i
, key
, cursor_y
, cursor_x
;
6695 if (prompt_position
)
6699 foreach_view (view
, i
) {
6701 if (view_is_displayed(view
) && view
->has_scrolled
&&
6702 use_scroll_redrawwin
)
6703 redrawwin(view
->win
);
6704 view
->has_scrolled
= FALSE
;
6707 /* Update the cursor position. */
6708 if (prompt_position
) {
6709 getbegyx(status_win
, cursor_y
, cursor_x
);
6710 cursor_x
= prompt_position
;
6712 view
= display
[current_view
];
6713 getbegyx(view
->win
, cursor_y
, cursor_x
);
6714 cursor_x
= view
->width
- 1;
6715 cursor_y
+= view
->lineno
- view
->offset
;
6717 setsyx(cursor_y
, cursor_x
);
6719 /* Refresh, accept single keystroke of input */
6721 key
= wgetch(status_win
);
6723 /* wgetch() with nodelay() enabled returns ERR when
6724 * there's no input. */
6727 } else if (key
== KEY_RESIZE
) {
6730 getmaxyx(stdscr
, height
, width
);
6732 wresize(status_win
, 1, width
);
6733 mvwin(status_win
, height
- 1, 0);
6734 wnoutrefresh(status_win
);
6736 redraw_display(TRUE
);
6746 prompt_input(const char *prompt
, input_handler handler
, void *data
)
6748 enum input_status status
= INPUT_OK
;
6749 static char buf
[SIZEOF_STR
];
6754 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
6757 mvwprintw(status_win
, 0, 0, "%s%.*s", prompt
, pos
, buf
);
6758 wclrtoeol(status_win
);
6760 key
= get_input(pos
+ 1);
6765 status
= pos
? INPUT_STOP
: INPUT_CANCEL
;
6772 status
= INPUT_CANCEL
;
6776 status
= INPUT_CANCEL
;
6780 if (pos
>= sizeof(buf
)) {
6781 report("Input string too long");
6785 status
= handler(data
, buf
, key
);
6786 if (status
== INPUT_OK
)
6787 buf
[pos
++] = (char) key
;
6791 /* Clear the status window */
6792 status_empty
= FALSE
;
6795 if (status
== INPUT_CANCEL
)
6803 static enum input_status
6804 prompt_yesno_handler(void *data
, char *buf
, int c
)
6806 if (c
== 'y' || c
== 'Y')
6808 if (c
== 'n' || c
== 'N')
6809 return INPUT_CANCEL
;
6814 prompt_yesno(const char *prompt
)
6816 char prompt2
[SIZEOF_STR
];
6818 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
6821 return !!prompt_input(prompt2
, prompt_yesno_handler
, NULL
);
6824 static enum input_status
6825 read_prompt_handler(void *data
, char *buf
, int c
)
6827 return isprint(c
) ? INPUT_OK
: INPUT_SKIP
;
6831 read_prompt(const char *prompt
)
6833 return prompt_input(prompt
, read_prompt_handler
, NULL
);
6836 static bool prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
6838 enum input_status status
= INPUT_OK
;
6841 while (items
[size
].text
)
6844 while (status
== INPUT_OK
) {
6845 const struct menu_item
*item
= &items
[*selected
];
6849 mvwprintw(status_win
, 0, 0, "%s (%d of %d) ",
6850 prompt
, *selected
+ 1, size
);
6852 wprintw(status_win
, "[%c] ", (char) item
->hotkey
);
6853 wprintw(status_win
, "%s", item
->text
);
6854 wclrtoeol(status_win
);
6856 key
= get_input(COLS
- 1);
6861 status
= INPUT_STOP
;
6866 *selected
= *selected
- 1;
6868 *selected
= size
- 1;
6873 *selected
= (*selected
+ 1) % size
;
6877 status
= INPUT_CANCEL
;
6881 for (i
= 0; items
[i
].text
; i
++)
6882 if (items
[i
].hotkey
== key
) {
6884 status
= INPUT_STOP
;
6890 /* Clear the status window */
6891 status_empty
= FALSE
;
6894 return status
!= INPUT_CANCEL
;
6898 * Repository properties
6901 static struct ref
**refs
= NULL
;
6902 static size_t refs_size
= 0;
6904 static struct ref_list
**ref_lists
= NULL
;
6905 static size_t ref_lists_size
= 0;
6907 DEFINE_ALLOCATOR(realloc_refs
, struct ref
*, 256)
6908 DEFINE_ALLOCATOR(realloc_refs_list
, struct ref
*, 8)
6909 DEFINE_ALLOCATOR(realloc_ref_lists
, struct ref_list
*, 8)
6912 compare_refs(const void *ref1_
, const void *ref2_
)
6914 const struct ref
*ref1
= *(const struct ref
**)ref1_
;
6915 const struct ref
*ref2
= *(const struct ref
**)ref2_
;
6917 if (ref1
->tag
!= ref2
->tag
)
6918 return ref2
->tag
- ref1
->tag
;
6919 if (ref1
->ltag
!= ref2
->ltag
)
6920 return ref2
->ltag
- ref2
->ltag
;
6921 if (ref1
->head
!= ref2
->head
)
6922 return ref2
->head
- ref1
->head
;
6923 if (ref1
->tracked
!= ref2
->tracked
)
6924 return ref2
->tracked
- ref1
->tracked
;
6925 if (ref1
->remote
!= ref2
->remote
)
6926 return ref2
->remote
- ref1
->remote
;
6927 return strcmp(ref1
->name
, ref2
->name
);
6931 foreach_ref(bool (*visitor
)(void *data
, struct ref
*ref
), void *data
)
6935 for (i
= 0; i
< refs_size
; i
++)
6936 if (!visitor(data
, refs
[i
]))
6940 static struct ref_list
*
6941 get_ref_list(const char *id
)
6943 struct ref_list
*list
;
6946 for (i
= 0; i
< ref_lists_size
; i
++)
6947 if (!strcmp(id
, ref_lists
[i
]->id
))
6948 return ref_lists
[i
];
6950 if (!realloc_ref_lists(&ref_lists
, ref_lists_size
, 1))
6952 list
= calloc(1, sizeof(*list
));
6956 for (i
= 0; i
< refs_size
; i
++) {
6957 if (!strcmp(id
, refs
[i
]->id
) &&
6958 realloc_refs_list(&list
->refs
, list
->size
, 1))
6959 list
->refs
[list
->size
++] = refs
[i
];
6967 qsort(list
->refs
, list
->size
, sizeof(*list
->refs
), compare_refs
);
6968 ref_lists
[ref_lists_size
++] = list
;
6973 read_ref(char *id
, size_t idlen
, char *name
, size_t namelen
)
6975 struct ref
*ref
= NULL
;
6978 bool remote
= FALSE
;
6979 bool tracked
= FALSE
;
6981 int from
= 0, to
= refs_size
- 1;
6983 if (!prefixcmp(name
, "refs/tags/")) {
6984 if (!suffixcmp(name
, namelen
, "^{}")) {
6992 namelen
-= STRING_SIZE("refs/tags/");
6993 name
+= STRING_SIZE("refs/tags/");
6995 } else if (!prefixcmp(name
, "refs/remotes/")) {
6997 namelen
-= STRING_SIZE("refs/remotes/");
6998 name
+= STRING_SIZE("refs/remotes/");
6999 tracked
= !strcmp(opt_remote
, name
);
7001 } else if (!prefixcmp(name
, "refs/heads/")) {
7002 namelen
-= STRING_SIZE("refs/heads/");
7003 name
+= STRING_SIZE("refs/heads/");
7004 head
= !strncmp(opt_head
, name
, namelen
);
7006 } else if (!strcmp(name
, "HEAD")) {
7007 string_ncopy(opt_head_rev
, id
, idlen
);
7011 /* If we are reloading or it's an annotated tag, replace the
7012 * previous SHA1 with the resolved commit id; relies on the fact
7013 * git-ls-remote lists the commit id of an annotated tag right
7014 * before the commit id it points to. */
7015 while (from
<= to
) {
7016 size_t pos
= (to
+ from
) / 2;
7017 int cmp
= strcmp(name
, refs
[pos
]->name
);
7031 if (!realloc_refs(&refs
, refs_size
, 1))
7033 ref
= calloc(1, sizeof(*ref
) + namelen
);
7036 memmove(refs
+ from
+ 1, refs
+ from
,
7037 (refs_size
- from
) * sizeof(*refs
));
7039 strncpy(ref
->name
, name
, namelen
);
7046 ref
->remote
= remote
;
7047 ref
->tracked
= tracked
;
7048 string_copy_rev(ref
->id
, id
);
7056 const char *head_argv
[] = {
7057 "git", "symbolic-ref", "HEAD", NULL
7059 static const char *ls_remote_argv
[SIZEOF_ARG
] = {
7060 "git", "ls-remote", opt_git_dir
, NULL
7062 static bool init
= FALSE
;
7066 argv_from_env(ls_remote_argv
, "TIG_LS_REMOTE");
7073 if (run_io_buf(head_argv
, opt_head
, sizeof(opt_head
)) &&
7074 !prefixcmp(opt_head
, "refs/heads/")) {
7075 char *offset
= opt_head
+ STRING_SIZE("refs/heads/");
7077 memmove(opt_head
, offset
, strlen(offset
) + 1);
7080 for (i
= 0; i
< refs_size
; i
++)
7083 if (run_io_load(ls_remote_argv
, "\t", read_ref
) == ERR
)
7086 /* Update the ref lists to reflect changes. */
7087 for (i
= 0; i
< ref_lists_size
; i
++) {
7088 struct ref_list
*list
= ref_lists
[i
];
7091 for (old
= new = 0; old
< list
->size
; old
++)
7092 if (!strcmp(list
->id
, list
->refs
[old
]->id
))
7093 list
->refs
[new++] = list
->refs
[old
];
7101 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
7103 if (!strcmp(name
, ".remote")) {
7104 string_ncopy(opt_remote
, value
, valuelen
);
7106 } else if (*opt_remote
&& !strcmp(name
, ".merge")) {
7107 size_t from
= strlen(opt_remote
);
7109 if (!prefixcmp(value
, "refs/heads/"))
7110 value
+= STRING_SIZE("refs/heads/");
7112 if (!string_format_from(opt_remote
, &from
, "/%s", value
))
7118 set_repo_config_option(char *name
, char *value
, int (*cmd
)(int, const char **))
7120 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
7121 int argc
= 1 + (cmd
== option_set_command
);
7124 if (!argv_from_string(argv
, &argc
, value
))
7125 config_msg
= "Too many option arguments";
7127 error
= cmd(argc
, argv
);
7130 warn("Option 'tig.%s': %s", name
, config_msg
);
7134 set_environment_variable(const char *name
, const char *value
)
7136 size_t len
= strlen(name
) + 1 + strlen(value
) + 1;
7137 char *env
= malloc(len
);
7140 string_nformat(env
, len
, NULL
, "%s=%s", name
, value
) &&
7148 set_work_tree(const char *value
)
7150 char cwd
[SIZEOF_STR
];
7152 if (!getcwd(cwd
, sizeof(cwd
)))
7153 die("Failed to get cwd path: %s", strerror(errno
));
7154 if (chdir(opt_git_dir
) < 0)
7155 die("Failed to chdir(%s): %s", strerror(errno
));
7156 if (!getcwd(opt_git_dir
, sizeof(opt_git_dir
)))
7157 die("Failed to get git path: %s", strerror(errno
));
7159 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
7160 if (chdir(value
) < 0)
7161 die("Failed to chdir(%s): %s", value
, strerror(errno
));
7162 if (!getcwd(cwd
, sizeof(cwd
)))
7163 die("Failed to get cwd path: %s", strerror(errno
));
7164 if (!set_environment_variable("GIT_WORK_TREE", cwd
))
7165 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
7166 if (!set_environment_variable("GIT_DIR", opt_git_dir
))
7167 die("Failed to set GIT_DIR to '%s'", opt_git_dir
);
7168 opt_is_inside_work_tree
= TRUE
;
7172 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7174 if (!strcmp(name
, "i18n.commitencoding"))
7175 string_ncopy(opt_encoding
, value
, valuelen
);
7177 else if (!strcmp(name
, "core.editor"))
7178 string_ncopy(opt_editor
, value
, valuelen
);
7180 else if (!strcmp(name
, "core.worktree"))
7181 set_work_tree(value
);
7183 else if (!prefixcmp(name
, "tig.color."))
7184 set_repo_config_option(name
+ 10, value
, option_color_command
);
7186 else if (!prefixcmp(name
, "tig.bind."))
7187 set_repo_config_option(name
+ 9, value
, option_bind_command
);
7189 else if (!prefixcmp(name
, "tig."))
7190 set_repo_config_option(name
+ 4, value
, option_set_command
);
7192 else if (*opt_head
&& !prefixcmp(name
, "branch.") &&
7193 !strncmp(name
+ 7, opt_head
, strlen(opt_head
)))
7194 set_remote_branch(name
+ 7 + strlen(opt_head
), value
, valuelen
);
7200 load_git_config(void)
7202 const char *config_list_argv
[] = { "git", GIT_CONFIG
, "--list", NULL
};
7204 return run_io_load(config_list_argv
, "=", read_repo_config_option
);
7208 read_repo_info(char *name
, size_t namelen
, char *value
, size_t valuelen
)
7210 if (!opt_git_dir
[0]) {
7211 string_ncopy(opt_git_dir
, name
, namelen
);
7213 } else if (opt_is_inside_work_tree
== -1) {
7214 /* This can be 3 different values depending on the
7215 * version of git being used. If git-rev-parse does not
7216 * understand --is-inside-work-tree it will simply echo
7217 * the option else either "true" or "false" is printed.
7218 * Default to true for the unknown case. */
7219 opt_is_inside_work_tree
= strcmp(name
, "false") ? TRUE
: FALSE
;
7221 } else if (*name
== '.') {
7222 string_ncopy(opt_cdup
, name
, namelen
);
7225 string_ncopy(opt_prefix
, name
, namelen
);
7232 load_repo_info(void)
7234 const char *rev_parse_argv
[] = {
7235 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7236 "--show-cdup", "--show-prefix", NULL
7239 return run_io_load(rev_parse_argv
, "=", read_repo_info
);
7247 static const char usage
[] =
7248 "tig " TIG_VERSION
" (" __DATE__
")\n"
7250 "Usage: tig [options] [revs] [--] [paths]\n"
7251 " or: tig show [options] [revs] [--] [paths]\n"
7252 " or: tig blame [rev] path\n"
7254 " or: tig < [git command output]\n"
7257 " -v, --version Show version and exit\n"
7258 " -h, --help Show help message and exit";
7260 static void __NORETURN
7263 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7269 static void __NORETURN
7270 die(const char *err
, ...)
7276 va_start(args
, err
);
7277 fputs("tig: ", stderr
);
7278 vfprintf(stderr
, err
, args
);
7279 fputs("\n", stderr
);
7286 warn(const char *msg
, ...)
7290 va_start(args
, msg
);
7291 fputs("tig warning: ", stderr
);
7292 vfprintf(stderr
, msg
, args
);
7293 fputs("\n", stderr
);
7298 parse_options(int argc
, const char *argv
[])
7300 enum request request
= REQ_VIEW_MAIN
;
7301 const char *subcommand
;
7302 bool seen_dashdash
= FALSE
;
7303 /* XXX: This is vulnerable to the user overriding options
7304 * required for the main view parser. */
7305 const char *custom_argv
[SIZEOF_ARG
] = {
7306 "git", "log", "--no-color", "--pretty=raw", "--parents",
7307 "--topo-order", NULL
7311 if (!isatty(STDIN_FILENO
)) {
7312 io_open(&VIEW(REQ_VIEW_PAGER
)->io
, "");
7313 return REQ_VIEW_PAGER
;
7319 subcommand
= argv
[1];
7320 if (!strcmp(subcommand
, "status")) {
7322 warn("ignoring arguments after `%s'", subcommand
);
7323 return REQ_VIEW_STATUS
;
7325 } else if (!strcmp(subcommand
, "blame")) {
7326 if (argc
<= 2 || argc
> 4)
7327 die("invalid number of options to blame\n\n%s", usage
);
7331 string_ncopy(opt_ref
, argv
[i
], strlen(argv
[i
]));
7335 string_ncopy(opt_file
, argv
[i
], strlen(argv
[i
]));
7336 return REQ_VIEW_BLAME
;
7338 } else if (!strcmp(subcommand
, "show")) {
7339 request
= REQ_VIEW_DIFF
;
7346 custom_argv
[1] = subcommand
;
7350 for (i
= 1 + !!subcommand
; i
< argc
; i
++) {
7351 const char *opt
= argv
[i
];
7353 if (seen_dashdash
|| !strcmp(opt
, "--")) {
7354 seen_dashdash
= TRUE
;
7356 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
7357 printf("tig version %s\n", TIG_VERSION
);
7360 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
7361 printf("%s\n", usage
);
7365 custom_argv
[j
++] = opt
;
7366 if (j
>= ARRAY_SIZE(custom_argv
))
7367 die("command too long");
7370 if (!prepare_update(VIEW(request
), custom_argv
, NULL
, FORMAT_NONE
))
7371 die("Failed to format arguments");
7377 main(int argc
, const char *argv
[])
7379 enum request request
= parse_options(argc
, argv
);
7383 signal(SIGINT
, quit
);
7384 signal(SIGPIPE
, SIG_IGN
);
7386 if (setlocale(LC_ALL
, "")) {
7387 char *codeset
= nl_langinfo(CODESET
);
7389 string_ncopy(opt_codeset
, codeset
, strlen(codeset
));
7392 if (load_repo_info() == ERR
)
7393 die("Failed to load repo info.");
7395 if (load_options() == ERR
)
7396 die("Failed to load user config.");
7398 if (load_git_config() == ERR
)
7399 die("Failed to load repo config.");
7401 /* Require a git repository unless when running in pager mode. */
7402 if (!opt_git_dir
[0] && request
!= REQ_VIEW_PAGER
)
7403 die("Not a git repository");
7405 if (*opt_encoding
&& strcasecmp(opt_encoding
, "UTF-8"))
7408 if (*opt_codeset
&& strcmp(opt_codeset
, opt_encoding
)) {
7409 opt_iconv
= iconv_open(opt_codeset
, opt_encoding
);
7410 if (opt_iconv
== ICONV_NONE
)
7411 die("Failed to initialize character set conversion");
7414 if (load_refs() == ERR
)
7415 die("Failed to load refs.");
7417 foreach_view (view
, i
)
7418 argv_from_env(view
->ops
->argv
, view
->cmd_env
);
7422 if (request
!= REQ_NONE
)
7423 open_view(NULL
, request
, OPEN_PREPARED
);
7424 request
= request
== REQ_NONE
? REQ_VIEW_MAIN
: REQ_NONE
;
7426 while (view_driver(display
[current_view
], request
)) {
7427 int key
= get_input(0);
7429 view
= display
[current_view
];
7430 request
= get_keybinding(view
->keymap
, key
);
7432 /* Some low-level request handling. This keeps access to
7433 * status_win restricted. */
7437 char *cmd
= read_prompt(":");
7439 if (cmd
&& isdigit(*cmd
)) {
7440 int lineno
= view
->lineno
+ 1;
7442 if (parse_int(&lineno
, cmd
, 1, view
->lines
+ 1) == OK
) {
7443 select_view_line(view
, lineno
- 1);
7446 report("Unable to parse '%s' as a line number", cmd
);
7450 struct view
*next
= VIEW(REQ_VIEW_PAGER
);
7451 const char *argv
[SIZEOF_ARG
] = { "git" };
7454 /* When running random commands, initially show the
7455 * command in the title. However, it maybe later be
7456 * overwritten if a commit line is selected. */
7457 string_ncopy(next
->ref
, cmd
, strlen(cmd
));
7459 if (!argv_from_string(argv
, &argc
, cmd
)) {
7460 report("Too many arguments");
7461 } else if (!prepare_update(next
, argv
, NULL
, FORMAT_DASH
)) {
7462 report("Failed to format command");
7464 open_view(view
, REQ_VIEW_PAGER
, OPEN_PREPARED
);
7472 case REQ_SEARCH_BACK
:
7474 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
7475 char *search
= read_prompt(prompt
);
7478 string_ncopy(opt_search
, search
, strlen(search
));
7479 else if (*opt_search
)
7480 request
= request
== REQ_SEARCH
?