index 60d73d52cdb0e8d58629c41b5bfa798268e1a883..0d16f767be7d2375875d26c68b75060834364401 100644 (file)
--- a/tig.c
+++ b/tig.c
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
+#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#define STRING_SIZE(x) (sizeof(x) - 1)
@@ -102,25 +104,17 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
+#define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
-#define AUTHOR_COLS 20
#define ID_COLS 8
+#define AUTHOR_COLS 19
-/* The default interval between line numbers. */
-#define NUMBER_INTERVAL 5
-
-#define TAB_SIZE 8
-
-#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
+#define MIN_VIEW_HEIGHT 4
#define NULL_ID "0000000000000000000000000000000000000000"
#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
-#ifndef GIT_CONFIG
-#define GIT_CONFIG "config"
-#endif
-
/* Some ASCII-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
};
static struct ref_list *get_ref_list(const char *id);
-static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
+static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
static int load_refs(void);
enum format_flags {
return 0;
}
+#define enum_equals(entry, str, len) \
+ ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
+
struct enum_map {
const char *name;
int namelen;
#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
+static char *
+enum_map_name(const char *name, size_t namelen)
+{
+ static char buf[SIZEOF_STR];
+ int bufpos;
+
+ for (bufpos = 0; bufpos <= namelen; bufpos++) {
+ buf[bufpos] = tolower(name[bufpos]);
+ if (buf[bufpos] == '_')
+ buf[bufpos] = '-';
+ }
+
+ buf[bufpos] = 0;
+ return buf;
+}
+
+#define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
+
static bool
map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
{
int i;
for (i = 0; i < map_size; i++)
- if (namelen == map[i].namelen &&
- !string_enum_compare(name, map[i].name, namelen)) {
+ if (enum_equals(map[i], name, namelen)) {
*value = map[i].value;
return TRUE;
}
}
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(time_t time)
+{
+ time_t t, t_local;
+ struct tm tm;
+ int offset, eastwest;
+
+ t = time;
+ localtime_r(&t, &tm);
+ t_local = mktime(&tm);
+
+ if (t_local < t) {
+ eastwest = -1;
+ offset = t - t_local;
+ } else {
+ eastwest = 1;
+ offset = t_local - t;
+ }
+ offset /= 60; /* in minutes */
+ offset = (offset % 60) + ((offset / 60) * 100);
+ return offset * eastwest;
+}
+
+#define DATE_INFO \
+ DATE_(NO), \
+ DATE_(DEFAULT), \
+ DATE_(RELATIVE), \
+ DATE_(SHORT)
+
+enum date {
+#define DATE_(name) DATE_##name
+ DATE_INFO
+#undef DATE_
+};
+
+static const struct enum_map date_map[] = {
+#define DATE_(name) ENUM_MAP(#name, DATE_##name)
+ DATE_INFO
+#undef DATE_
+};
+
static const char *
-mkdate(const time_t *time)
+string_date(const time_t *time, enum date date)
{
static char buf[DATE_COLS + 1];
+ static const struct enum_map reldate[] = {
+ { "second", 1, 60 * 2 },
+ { "minute", 60, 60 * 60 * 2 },
+ { "hour", 60 * 60, 60 * 60 * 24 * 2 },
+ { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
+ { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
+ { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
+ };
struct tm tm;
+ if (date == DATE_RELATIVE) {
+ struct timeval now;
+ time_t date = *time + local_tzoffset(*time);
+ time_t seconds;
+ int i;
+
+ gettimeofday(&now, NULL);
+ seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
+ for (i = 0; i < ARRAY_SIZE(reldate); i++) {
+ if (seconds >= reldate[i].value)
+ continue;
+
+ seconds /= reldate[i].namelen;
+ if (!string_format(buf, "%ld %s%s %s",
+ seconds, reldate[i].name,
+ seconds > 1 ? "s" : "",
+ now.tv_sec >= date ? "ago" : "ahead"))
+ break;
+ return buf;
+ }
+ }
+
gmtime_r(time, &tm);
return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
}
+#define AUTHOR_VALUES \
+ AUTHOR_(NO), \
+ AUTHOR_(FULL), \
+ AUTHOR_(ABBREVIATED)
+
+enum author {
+#define AUTHOR_(name) AUTHOR_##name
+ AUTHOR_VALUES,
+#undef AUTHOR_
+ AUTHOR_DEFAULT = AUTHOR_FULL
+};
+
+static const struct enum_map author_map[] = {
+#define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
+ AUTHOR_VALUES
+#undef AUTHOR_
+};
+
+/* FIXME: Handle multi-byte and multi-column characters. */
+static const char *
+get_author_initials(const char *author, size_t max_columns)
+{
+ static char initials[AUTHOR_COLS];
+ size_t pos;
+
+#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
+
+ memset(initials, 0, sizeof(initials));
+ for (pos = 0; *author && pos < sizeof(initials) - 1; author++, pos++) {
+ while (is_initial_sep(*author))
+ author++;
+ strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
+ while (*author && author[1] && !is_initial_sep(author[1]))
+ author++;
+ }
+
+ return initials;
+}
+
+
static bool
argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
{
}
static bool
-io_open(struct io *io, const char *name)
+io_open(struct io *io, const char *fmt, ...)
{
+ char name[SIZEOF_STR] = "";
+ bool fits;
+ va_list args;
+
init_io(io, NULL, IO_FD);
+
+ va_start(args, fmt);
+ fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
+ va_end(args);
+
+ if (!fits) {
+ io->error = ENAMETOOLONG;
+ return FALSE;
+ }
io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
if (io->pipe == -1)
io->error = errno;
}
static bool
-run_io_rd(struct io *io, const char **argv, enum format_flags flags)
+run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
{
- return init_io_rd(io, argv, NULL, flags) && start_io(io);
+ return init_io_rd(io, argv, dir, flags) && start_io(io);
}
static bool
{
struct io io = {};
- return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
+ return run_io_rd(&io, argv, NULL, FORMAT_NONE)
+ && io_read_buf(&io, buf, bufsize);
}
static int
REQ_(OPTIONS, "Open option menu"), \
REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
+ REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
int i;
for (i = 0; i < ARRAY_SIZE(req_info); i++)
- if (req_info[i].namelen == namelen &&
- !string_enum_compare(req_info[i].name, name, namelen))
+ if (enum_equals(req_info[i], name, namelen))
return req_info[i].request;
return REQ_NONE;
*/
/* Option and state variables. */
-static bool opt_date = TRUE;
-static bool opt_author = TRUE;
+static enum date opt_date = DATE_DEFAULT;
+static enum author opt_author = AUTHOR_DEFAULT;
static bool opt_line_number = FALSE;
static bool opt_line_graphics = TRUE;
static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
-static int opt_num_interval = NUMBER_INTERVAL;
+static int opt_num_interval = 5;
static double opt_hscroll = 0.50;
-static int opt_tab_size = TAB_SIZE;
-static int opt_author_cols = AUTHOR_COLS-1;
+static double opt_scale_split_view = 2.0 / 3.0;
+static int opt_tab_size = 8;
+static int opt_author_cols = AUTHOR_COLS;
static char opt_path[SIZEOF_STR] = "";
static char opt_file[SIZEOF_STR] = "";
static char opt_ref[SIZEOF_REF] = "";
static char opt_head_rev[SIZEOF_REV] = "";
static char opt_remote[SIZEOF_REF] = "";
static char opt_encoding[20] = "UTF-8";
-static bool opt_utf8 = TRUE;
static char opt_codeset[20] = "UTF-8";
-static iconv_t opt_iconv = ICONV_NONE;
+static iconv_t opt_iconv_in = ICONV_NONE;
+static iconv_t opt_iconv_out = ICONV_NONE;
static char opt_search[SIZEOF_STR] = "";
static char opt_cdup[SIZEOF_STR] = "";
static char opt_prefix[SIZEOF_STR] = "";
#define is_initial_commit() (!*opt_head_rev)
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
+#define mkdate(time) string_date(time, opt_date)
/*
LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
enum line_type {
enum line_type type;
for (type = 0; type < ARRAY_SIZE(line_info); type++)
- if (namelen == line_info[type].namelen &&
- !string_enum_compare(line_info[type].name, name, namelen))
+ if (enum_equals(line_info[type], name, namelen))
return &line_info[type];
return NULL;
unsigned int selected:1;
unsigned int dirty:1;
unsigned int cleareol:1;
+ unsigned int other:16;
void *data; /* User data */
};
return seq ? seq : "(no key)";
}
+static bool
+append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
+{
+ const char *sep = *pos > 0 ? ", " : "";
+ const char *keyname = get_key_name(keybinding->alias);
+
+ return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
+}
+
+static bool
+append_keymap_request_keys(char *buf, size_t *pos, enum request request,
+ enum keymap keymap, bool all)
+{
+ int i;
+
+ for (i = 0; i < keybindings[keymap].size; i++) {
+ if (keybindings[keymap].data[i].request == request) {
+ if (!append_key(buf, pos, &keybindings[keymap].data[i]))
+ return FALSE;
+ if (!all)
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+#define get_key(keymap, request) get_keys(keymap, request, FALSE)
+
static const char *
-get_key(enum request request)
+get_keys(enum keymap keymap, enum request request, bool all)
{
static char buf[BUFSIZ];
size_t pos = 0;
- char *sep = "";
int i;
buf[pos] = 0;
- for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
- const struct keybinding *keybinding = &default_keybindings[i];
+ if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
+ return "Too many keybindings!";
+ if (pos > 0 && !all)
+ return buf;
- if (keybinding->request != request)
- continue;
+ if (keymap != KEYMAP_GENERIC) {
+ /* Only the generic keymap includes the default keybindings when
+ * listing all keys. */
+ if (all)
+ return buf;
- if (!string_format_from(buf, &pos, "%s%s", sep,
- get_key_name(keybinding->alias)))
+ if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
return "Too many keybindings!";
- sep = ", ";
+ if (pos)
+ return buf;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
+ if (default_keybindings[i].request == request) {
+ if (!append_key(buf, &pos, &default_keybindings[i]))
+ return "Too many keybindings!";
+ if (!all)
+ return buf;
+ }
}
return buf;
{
struct line_info *info;
- if (argc != 3 && argc != 4) {
+ if (argc < 3) {
config_msg = "Wrong number of arguments given to color command";
return ERR;
}
return ERR;
}
- if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
- config_msg = "Unknown attribute";
- return ERR;
+ info->attr = 0;
+ while (argc-- > 3) {
+ int attr;
+
+ if (!set_attribute(&attr, argv[argc])) {
+ config_msg = "Unknown attribute";
+ return ERR;
+ }
+ info->attr |= attr;
}
return OK;
return OK;
}
+static int parse_enum_do(unsigned int *opt, const char *arg,
+ const struct enum_map *map, size_t map_size)
+{
+ bool is_true;
+
+ assert(map_size > 1);
+
+ if (map_enum_do(map, map_size, (int *) opt, arg))
+ return OK;
+
+ if (parse_bool(&is_true, arg) != OK)
+ return ERR;
+
+ *opt = is_true ? map[1].value : map[0].value;
+ return OK;
+}
+
+#define parse_enum(opt, arg, map) \
+ parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
+
static int
parse_string(char *opt, const char *arg, size_t optsize)
{
}
if (!strcmp(argv[0], "show-author"))
- return parse_bool(&opt_author, argv[2]);
+ return parse_enum(&opt_author, argv[2], author_map);
if (!strcmp(argv[0], "show-date"))
- return parse_bool(&opt_date, argv[2]);
+ return parse_enum(&opt_date, argv[2], date_map);
if (!strcmp(argv[0], "show-rev-graph"))
return parse_bool(&opt_rev_graph, argv[2]);
if (!strcmp(argv[0], "horizontal-scroll"))
return parse_step(&opt_hscroll, argv[2]);
+ if (!strcmp(argv[0], "split-view-height"))
+ return parse_step(&opt_scale_split_view, argv[2]);
+
if (!strcmp(argv[0], "tab-size"))
return parse_int(&opt_tab_size, argv[2], 1, 1024);
option_bind_command(int argc, const char *argv[])
{
enum request request;
- int keymap;
+ int keymap = -1;
int key;
if (argc < 3) {
struct io io = {};
/* It's OK that the file doesn't exist. */
- if (!io_open(&io, path))
+ if (!io_open(&io, "%s", path))
return;
config_lineno = 0;
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
+ /* Prepare view for loading */
+ bool (*prepare)(struct view *view);
};
static struct view_ops blame_ops;
draw_chars(struct view *view, enum line_type type, const char *string,
int max_len, bool use_tilde)
{
+ static char out_buffer[BUFSIZ * 2];
int len = 0;
int col = 0;
int trimmed = FALSE;
if (max_len <= 0)
return 0;
- if (opt_utf8) {
- len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
- } else {
- col = len = strlen(string);
- if (len > max_len) {
- if (use_tilde) {
- max_len -= 1;
+ len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
+
+ set_view_attr(view, type);
+ if (len > 0) {
+ if (opt_iconv_out != ICONV_NONE) {
+ ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
+ size_t inlen = len + 1;
+
+ char *outbuf = out_buffer;
+ size_t outlen = sizeof(out_buffer);
+
+ size_t ret;
+
+ ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ string = out_buffer;
+ len = sizeof(out_buffer) - outlen;
}
- col = len = max_len;
- trimmed = TRUE;
}
- }
- set_view_attr(view, type);
- if (len > 0)
waddnstr(view->win, string, len);
+ }
if (trimmed && use_tilde) {
set_view_attr(view, LINE_DELIMITER);
waddch(view->win, '~');
@@ -2020,33 +2246,20 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
static bool
draw_date(struct view *view, time_t *time)
{
- const char *date = mkdate(time);
+ const char *date = time ? mkdate(time) : "";
+ int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
- return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
+ return draw_field(view, LINE_DATE, date, cols, FALSE);
}
static bool
draw_author(struct view *view, const char *author)
{
- bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
-
- if (!trim) {
- static char initials[10];
- size_t pos;
-
-#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
-
- memset(initials, 0, sizeof(initials));
- for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
- while (is_initial_sep(*author))
- author++;
- strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
- while (*author && !is_initial_sep(author[1]))
- author++;
- }
+ bool trim = opt_author_cols == 0 || opt_author_cols > 5;
+ bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
- author = initials;
- }
+ if (abbreviate && author)
+ author = get_author_initials(author, opt_author_cols);
return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
}
wnoutrefresh(view->title);
}
+static int
+apply_step(double step, int value)
+{
+ if (step >= 1)
+ return (int) step;
+ value *= step + 0.01;
+ return value ? value : 1;
+}
+
static void
resize_display(void)
{
if (view != base) {
/* Horizontal split. */
view->width = base->width;
- view->height = SCALE_SPLIT_VIEW(base->height);
+ view->height = apply_step(opt_scale_split_view, base->height);
+ view->height = MAX(view->height, MIN_VIEW_HEIGHT);
+ view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
base->height -= view->height;
/* Make room for the title bar. */
}
}
+static void
+toggle_enum_option_do(unsigned int *opt, const char *help,
+ const struct enum_map *map, size_t size)
+{
+ *opt = (*opt + 1) % size;
+ redraw_display(FALSE);
+ report("Displaying %s %s", enum_name(map[*opt]), help);
+}
+
+#define toggle_enum_option(opt, help, map) \
+ toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
+
+#define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
+#define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
+
static void
toggle_view_option(bool *option, const char *help)
{
};
int selected = 0;
- if (prompt_menu("Toggle option", menu, &selected))
- toggle_view_option(menu[selected].data, menu[selected].text);
+ if (prompt_menu("Toggle option", menu, &selected)) {
+ if (menu[selected].data == &opt_date)
+ toggle_date();
+ else if (menu[selected].data == &opt_author)
+ toggle_author();
+ else
+ toggle_view_option(menu[selected].data, menu[selected].text);
+ }
}
static void
@@ -2353,15 +2598,6 @@ goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
return FALSE;
}
-static int
-apply_step(double step, int value)
-{
- if (step >= 1)
- return (int) step;
- value *= step + 0.01;
- return value ? value : 1;
-}
-
/* Scrolling backend */
static void
do_scroll_view(struct view *view, int lines)
free((void *) argv[argc]);
}
+static const char *
+format_arg(const char *name)
+{
+ static struct {
+ const char *name;
+ size_t namelen;
+ const char *value;
+ const char *value_if_empty;
+ } vars[] = {
+#define FORMAT_VAR(name, value, value_if_empty) \
+ { name, STRING_SIZE(name), value, value_if_empty }
+ FORMAT_VAR("%(directory)", opt_path, ""),
+ FORMAT_VAR("%(file)", opt_file, ""),
+ FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
+ FORMAT_VAR("%(head)", ref_head, ""),
+ FORMAT_VAR("%(commit)", ref_commit, ""),
+ FORMAT_VAR("%(blob)", ref_blob, ""),
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(vars); i++)
+ if (!strncmp(name, vars[i].name, vars[i].namelen))
+ return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
+
+ return NULL;
+}
static bool
format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
{
@@ -2732,27 +2994,13 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl
len = strlen(arg);
value = "";
- } else if (!prefixcmp(next, "%(directory)")) {
- value = opt_path;
-
- } else if (!prefixcmp(next, "%(file)")) {
- value = opt_file;
-
- } else if (!prefixcmp(next, "%(ref)")) {
- value = *opt_ref ? opt_ref : "HEAD";
-
- } else if (!prefixcmp(next, "%(head)")) {
- value = ref_head;
-
- } else if (!prefixcmp(next, "%(commit)")) {
- value = ref_commit;
-
- } else if (!prefixcmp(next, "%(blob)")) {
- value = ref_blob;
-
} else {
- report("Unknown replacement: `%s`", next);
- return FALSE;
+ value = format_arg(next);
+
+ if (!value) {
+ report("Unknown replacement: `%s`", next);
+ return FALSE;
+ }
}
if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
{
if (view->pipe)
end_update(view, TRUE);
- return io_open(&view->io, name);
+ return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
}
static bool
if (view->pipe)
end_update(view, TRUE);
- if (refresh) {
- if (!start_io(&view->io))
- return FALSE;
-
- } else {
- if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
- opt_path[0] = 0;
-
- if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
+ if (!refresh) {
+ if (view->ops->prepare) {
+ if (!view->ops->prepare(view))
+ return FALSE;
+ } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
return FALSE;
+ }
/* Put the current ref_* value to the view title ref
* member. This is needed by the blob view. Most other
string_copy_rev(view->ref, view->id);
}
+ if (!start_io(&view->io))
+ return FALSE;
+
setup_update(view, view->id);
return TRUE;
}
for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
- if (opt_iconv != ICONV_NONE) {
+ if (opt_iconv_in != ICONV_NONE) {
ICONV_CONST char *inbuf = line;
size_t inlen = strlen(line) + 1;
size_t ret;
- ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
+ ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
if (ret != (size_t) -1)
line = out_buffer;
}
display[current_view] = view;
}
+ /* No parent signals that this is the first loaded view. */
+ if (prev && view != prev) {
+ view->parent = prev;
+ }
+
/* Resize the view when switching between split- and full-screen,
* or when switching between two different full-screen views. */
if (nviews != displayed_views() ||
do_scroll_view(prev, lines);
}
- if (prev && view != prev) {
- if (split) {
- /* "Blur" the previous view. */
- update_view_title(prev);
- }
-
- view->parent = prev;
+ if (prev && view != prev && split && view_is_displayed(prev)) {
+ /* "Blur" the previous view. */
+ update_view_title(prev);
}
if (view->pipe && view->lines == 0) {
}
static void
-open_editor(bool from_root, const char *file)
+open_editor(const char *file)
{
const char *editor_argv[] = { "vi", file, NULL };
const char *editor;
editor = "vi";
editor_argv[0] = editor;
- open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
+ open_external_viewer(editor_argv, opt_cdup);
}
static void
case REQ_VIEW_BLAME:
if (!opt_file[0]) {
report("No file chosen, press %s to open tree view",
- get_key(REQ_VIEW_TREE));
+ get_key(view->keymap, REQ_VIEW_TREE));
break;
}
open_view(view, request, OPEN_DEFAULT);
case REQ_VIEW_BLOB:
if (!ref_blob[0]) {
report("No file chosen, press %s to open tree view",
- get_key(REQ_VIEW_TREE));
+ get_key(view->keymap, REQ_VIEW_TREE));
break;
}
open_view(view, request, OPEN_DEFAULT);
case REQ_VIEW_PAGER:
if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
report("No pager content, press %s to run command from prompt",
- get_key(REQ_PROMPT));
+ get_key(view->keymap, REQ_PROMPT));
break;
}
open_view(view, request, OPEN_DEFAULT);
case REQ_VIEW_STAGE:
if (!VIEW(REQ_VIEW_STAGE)->lines) {
report("No stage content, press %s to open the status view and choose file",
- get_key(REQ_VIEW_STATUS));
+ get_key(view->keymap, REQ_VIEW_STATUS));
break;
}
open_view(view, request, OPEN_DEFAULT);
break;
case REQ_TOGGLE_DATE:
- toggle_view_option(&opt_date, "date display");
+ toggle_date();
break;
case REQ_TOGGLE_AUTHOR:
- toggle_view_option(&opt_author, "author display");
+ toggle_author();
break;
case REQ_TOGGLE_REV_GRAPH:
return FALSE;
default:
- report("Unknown key, press 'h' for help");
+ report("Unknown key, press %s for help",
+ get_key(view->keymap, REQ_VIEW_HELP));
return TRUE;
}
int i;
items = calloc(*parents + 1, sizeof(*items));
- if (items)
+ if (!items)
return FALSE;
for (i = 0; i < *parents; i++) {
* Help backend
*/
+static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
+
static bool
-help_open(struct view *view)
+help_open_keymap_title(struct view *view, enum keymap keymap)
+{
+ struct line *line;
+
+ line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
+ help_keymap_hidden[keymap] ? '+' : '-',
+ enum_name(keymap_table[keymap]));
+ if (line)
+ line->other = keymap;
+
+ return help_keymap_hidden[keymap];
+}
+
+static void
+help_open_keymap(struct view *view, enum keymap keymap)
{
+ const char *group = NULL;
char buf[SIZEOF_STR];
size_t bufpos;
+ bool add_title = TRUE;
int i;
- if (view->lines > 0)
- return TRUE;
-
- add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
-
for (i = 0; i < ARRAY_SIZE(req_info); i++) {
- const char *key;
+ const char *key = NULL;
if (req_info[i].request == REQ_NONE)
continue;
if (!req_info[i].request) {
- add_line_text(view, "", LINE_DEFAULT);
- add_line_text(view, req_info[i].help, LINE_DEFAULT);
+ group = req_info[i].help;
continue;
}
- key = get_key(req_info[i].request);
- if (!*key)
- key = "(no key defined)";
+ key = get_keys(keymap, req_info[i].request, TRUE);
+ if (!key || !*key)
+ continue;
+
+ if (add_title && help_open_keymap_title(view, keymap))
+ return;
+ add_title = FALSE;
- for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
- buf[bufpos] = tolower(req_info[i].name[bufpos]);
- if (buf[bufpos] == '_')
- buf[bufpos] = '-';
+ if (group) {
+ add_line_text(view, group, LINE_HELP_GROUP);
+ group = NULL;
}
- add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
- key, buf, req_info[i].help);
+ add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
+ enum_name(req_info[i]), req_info[i].help);
}
- if (run_requests) {
- add_line_text(view, "", LINE_DEFAULT);
- add_line_text(view, "External commands:", LINE_DEFAULT);
- }
+ group = "External commands:";
for (i = 0; i < run_requests; i++) {
struct run_request *req = get_run_request(REQ_NONE + i + 1);
const char *key;
int argc;
- if (!req)
+ if (!req || req->keymap != keymap)
continue;
key = get_key_name(req->key);
if (!*key)
key = "(no key defined)";
+ if (add_title && help_open_keymap_title(view, keymap))
+ return;
+ if (group) {
+ add_line_text(view, group, LINE_HELP_GROUP);
+ group = NULL;
+ }
+
for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
if (!string_format_from(buf, &bufpos, "%s%s",
argc ? " " : "", req->argv[argc]))
- return REQ_NONE;
+ return;
- add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
- keymap_table[req->keymap].name, key, buf);
+ add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
}
+}
+
+static bool
+help_open(struct view *view)
+{
+ enum keymap keymap;
+
+ reset_view(view);
+ add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
+ add_line_text(view, "", LINE_DEFAULT);
+
+ for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
+ help_open_keymap(view, keymap);
return TRUE;
}
+static enum request
+help_request(struct view *view, enum request request, struct line *line)
+{
+ switch (request) {
+ case REQ_ENTER:
+ if (line->type == LINE_HELP_KEYMAP) {
+ help_keymap_hidden[line->other] =
+ !help_keymap_hidden[line->other];
+ view->p_restore = TRUE;
+ open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
+ }
+
+ return REQ_NONE;
+ default:
+ return pager_request(view, request, line);
+ }
+}
+
static struct view_ops help_ops = {
"line",
NULL,
help_open,
NULL,
pager_draw,
- pager_request,
+ help_request,
pager_grep,
pager_select,
};
return TRUE;
}
- if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
+ if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
report("Failed to load tree data");
return TRUE;
}
if (!pos)
return TRUE;
text = pos + 1;
- if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
- text += strlen(opt_prefix);
if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
text += strlen(opt_path);
pos = strchr(text, '/');
else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
report("Failed to save blob data to file");
else
- open_editor(FALSE, file);
+ open_editor(file);
if (fd != -1)
unlink(file);
}
} else if (!is_head_commit(view->vid)) {
open_blob_editor();
} else {
- open_editor(TRUE, opt_file);
+ open_editor(opt_file);
}
return REQ_NONE;
string_copy_rev(view->ref, entry->id);
}
+static bool
+tree_prepare(struct view *view)
+{
+ if (view->lines == 0 && opt_prefix[0]) {
+ char *pos = opt_prefix;
+
+ while (pos && *pos) {
+ char *end = strchr(pos, '/');
+
+ if (end)
+ *end = 0;
+ push_tree_stack_entry(pos, 0);
+ pos = end;
+ if (end) {
+ *end = '/';
+ pos++;
+ }
+ }
+
+ } else if (strcmp(view->vid, view->id)) {
+ opt_path[0] = 0;
+ }
+
+ return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
+}
+
static const char *tree_argv[SIZEOF_ARG] = {
"git", "ls-tree", "%(commit)", "%(directory)", NULL
};
tree_request,
tree_grep,
tree_select,
+ tree_prepare,
};
static bool
static bool
blame_open(struct view *view)
{
- if (*opt_ref || !io_open(&view->io, opt_file)) {
- if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
+ char path[SIZEOF_STR];
+
+ if (!view->parent && *opt_prefix) {
+ string_copy(path, opt_file);
+ if (!string_format(opt_file, "%s%s", opt_prefix, path))
+ return FALSE;
+ }
+
+ if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
+ if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
return FALSE;
}
if (view->lines == 0 && !view->parent)
die("No blame exist for %s", view->vid);
- if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
+ if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
report("Failed to load blame data");
return TRUE;
}
struct branch {
const char *author; /* Author of the last commit. */
time_t time; /* Date of the last activity. */
- struct ref *ref; /* Name and commit ID information. */
+ const struct ref *ref; /* Name and commit ID information. */
};
+static const struct ref branch_all;
+
static const enum sort_field branch_sort_fields[] = {
ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
};
if (opt_author && draw_author(view, branch->author))
return TRUE;
- draw_text(view, type, branch->ref->name, TRUE);
+ draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
return TRUE;
}
static enum request
branch_request(struct view *view, enum request request, struct line *line)
{
+ struct branch *branch = line->data;
+
switch (request) {
case REQ_REFRESH:
load_refs();
return REQ_NONE;
case REQ_ENTER:
- open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
+ if (branch->ref == &branch_all) {
+ const char *all_branches_argv[] = {
+ "git", "log", "--no-color", "--pretty=raw", "--parents",
+ "--topo-order", "--all", NULL
+ };
+ struct view *main_view = VIEW(REQ_VIEW_MAIN);
+
+ if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
+ report("Failed to load view of all branches");
+ return REQ_NONE;
+ }
+ open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
+ } else {
+ open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
+ }
return REQ_NONE;
default:
}
static bool
-branch_open_visitor(void *data, struct ref *ref)
+branch_open_visitor(void *data, const struct ref *ref)
{
struct view *view = data;
struct branch *branch;
"--simplify-by-decoration", "--all", NULL
};
- if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
+ if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
report("Failed to load branch data");
return TRUE;
}
setup_update(view, view->id);
+ branch_open_visitor(view, &branch_all);
foreach_ref(branch_open_visitor, view);
view->p_restore = TRUE;
@@ -5018,7 +5369,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
char *buf;
struct io io = {};
- if (!run_io(&io, argv, NULL, IO_RD))
+ if (!run_io(&io, argv, opt_cdup, IO_RD))
return FALSE;
add_line_data(view, NULL, type);
};
static const char *status_list_other_argv[] = {
- "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
+ "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
};
static const char *status_list_no_head_argv[] = {
if (!*opt_head) {
struct io io = {};
- if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
- io_open(&io, buf) &&
+ if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
io_read_buf(&io, buf, sizeof(buf))) {
head = buf;
if (!prefixcmp(head, "refs/heads/"))
return run_io(io, staged_argv, opt_cdup, IO_WR);
case LINE_STAT_UNSTAGED:
- return run_io(io, others_argv, opt_cdup, IO_WR);
-
case LINE_STAT_UNTRACKED:
- return run_io(io, others_argv, NULL, IO_WR);
+ return run_io(io, others_argv, opt_cdup, IO_WR);
default:
die("line type %d not handled in switch", type);
struct line *pos = view->line + view->lines;
int files = 0;
int file, done;
- int cursor_y, cursor_x;
+ int cursor_y = -1, cursor_x = -1;
if (!status_update_prepare(&io, line->type))
return FALSE;
} else {
report("Cannot revert changes to multiple files");
}
- return FALSE;
- } else {
+ } else if (prompt_yesno("Are you sure you want to revert changes?")) {
char mode[10] = "100644";
const char *reset_argv[] = {
"git", "update-index", "--cacheinfo", mode,
"git", "checkout", "--", status->old.name, NULL
};
- if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
- return FALSE;
- string_format(mode, "%o", status->old.mode);
- return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
- run_io_fg(checkout_argv, opt_cdup);
+ if (status->status == 'U') {
+ string_format(mode, "%5o", status->old.mode);
+
+ if (status->old.mode == 0 && status->new.mode == 0) {
+ reset_argv[2] = "--force-remove";
+ reset_argv[3] = status->old.name;
+ reset_argv[4] = NULL;
+ }
+
+ if (!run_io_fg(reset_argv, opt_cdup))
+ return FALSE;
+ if (status->old.mode == 0 && status->new.mode == 0)
+ return TRUE;
+ }
+
+ return run_io_fg(checkout_argv, opt_cdup);
}
+
+ return FALSE;
}
static enum request
return REQ_NONE;
}
- open_editor(status->status != '?', status->new.name);
+ open_editor(status->new.name);
break;
case REQ_VIEW_BLAME:
- if (status) {
- string_copy(opt_file, status->new.name);
+ if (status)
opt_ref[0] = 0;
- }
return request;
case REQ_ENTER:
if (status && status->status == 'U') {
text = "Press %s to resolve conflict in %s";
- key = get_key(REQ_STATUS_MERGE);
+ key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
} else {
- key = get_key(REQ_STATUS_UPDATE);
+ key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
}
string_format(view->ref, text, key, file);
+ if (status)
+ string_copy(opt_file, status->new.name);
}
static bool
case REQ_STAGE_NEXT:
if (stage_line_type == LINE_STAT_UNTRACKED) {
report("File is untracked; press %s to add",
- get_key(REQ_STATUS_UPDATE));
+ get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
return REQ_NONE;
}
stage_next(view, line);
return REQ_NONE;
}
- open_editor(stage_status.status != '?', stage_status.new.name);
+ open_editor(stage_status.new.name);
break;
case REQ_REFRESH:
}
static void
-foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
+foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
{
size_t i;
@@ -7198,7 +7558,7 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
static int
load_git_config(void)
{
- const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
+ const char *config_list_argv[] = { "git", "config", "--list", NULL };
return run_io_load(config_list_argv, "=", read_repo_config_option);
}
die("command too long");
}
- if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
- die("Failed to format arguments");
+ if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
+ die("Failed to format arguments");
return request;
}
if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
die("Not a git repository");
- if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
- opt_utf8 = FALSE;
+ if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
+ opt_iconv_in = iconv_open("UTF-8", opt_encoding);
+ if (opt_iconv_in == ICONV_NONE)
+ die("Failed to initialize character set conversion");
+ }
- if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
- opt_iconv = iconv_open(opt_codeset, opt_encoding);
- if (opt_iconv == ICONV_NONE)
+ if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
+ opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
+ if (opt_iconv_out == ICONV_NONE)
die("Failed to initialize character set conversion");
}