index 83ee8b95e76dd615bdb3513356e9cb6d37a8f267..fb6231f256fbbfbcbb1545dc7caaa6d0a69dd3c1 100644 (file)
--- a/tig.c
+++ b/tig.c
static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
-struct int_map {
- const char *name;
- int namelen;
- int value;
-};
-
-static int
-set_from_int_map(struct int_map *map, size_t map_size,
- int *value, const char *name, int namelen)
-{
-
- int i;
-
- for (i = 0; i < map_size; i++)
- if (namelen == map[i].namelen &&
- !strncasecmp(name, map[i].name, namelen)) {
- *value = map[i].value;
- return OK;
- }
-
- return ERR;
-}
-
enum input_status {
INPUT_OK,
INPUT_SKIP,
#define string_add(dst, from, src) \
string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
-static size_t
-string_expand_length(const char *line, int tabsize)
-{
- size_t size, pos;
-
- for (pos = 0; line[pos]; pos++) {
- if (line[pos] == '\t' && tabsize > 0)
- size += tabsize - (size % tabsize);
- else
- size++;
- }
- return size;
-}
-
static void
string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
{
return 0;
}
+struct enum_map {
+ const char *name;
+ int namelen;
+ int value;
+};
+
+#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
+
+static bool
+map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
+{
+ size_t namelen = strlen(name);
+ int i;
+
+ for (i = 0; i < map_size; i++)
+ if (namelen == map[i].namelen &&
+ !string_enum_compare(name, map[i].name, namelen)) {
+ *value = map[i].value;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#define map_enum(attr, map, name) \
+ map_enum_do(map, ARRAY_SIZE(map), attr, name)
+
#define prefixcmp(str1, str2) \
strncmp(str1, str2, STRING_SIZE(str2))
}
static bool
-run_io_buf(const char **argv, char buf[], size_t bufsize)
+io_read_buf(struct io *io, char buf[], size_t bufsize)
{
- struct io io = {};
bool error;
- if (!run_io_rd(&io, argv, FORMAT_NONE))
- return FALSE;
+ io->buf = io->bufpos = buf;
+ io->bufalloc = bufsize;
+ error = !io_get(io, '\n', TRUE) && io_error(io);
+ io->buf = NULL;
- io.buf = io.bufpos = buf;
- io.bufalloc = bufsize;
- error = !io_get(&io, '\n', TRUE) && io_error(&io);
- io.buf = NULL;
+ return done_io(io) || error;
+}
+
+static bool
+run_io_buf(const char **argv, char buf[], size_t bufsize)
+{
+ struct io io = {};
- return done_io(&io) || error;
+ return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
}
static int
#undef KEYMAP_
};
-static struct int_map keymap_table[] = {
-#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
+static struct enum_map keymap_table[] = {
+#define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
KEYMAP_INFO
#undef KEYMAP_
};
-#define set_keymap(map, name) \
- set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
+#define set_keymap(map, name) map_enum(map, keymap_table, name)
struct keybinding_table {
struct keybinding *data;
* User config file handling.
*/
-static struct int_map color_map[] = {
-#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
+static int config_lineno;
+static bool config_errors;
+static const char *config_msg;
+
+static struct enum_map color_map[] = {
+#define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
COLOR_MAP(DEFAULT),
COLOR_MAP(BLACK),
COLOR_MAP(BLUE),
COLOR_MAP(YELLOW),
};
-#define set_color(color, name) \
- set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
-
-static struct int_map attr_map[] = {
-#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
+static struct enum_map attr_map[] = {
+#define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
ATTR_MAP(NORMAL),
ATTR_MAP(BLINK),
ATTR_MAP(BOLD),
ATTR_MAP(UNDERLINE),
};
-#define set_attribute(attr, name) \
- set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
+#define set_attribute(attr, name) map_enum(attr, attr_map, name)
-static int config_lineno;
-static bool config_errors;
-static const char *config_msg;
+static int
+parse_int(int *opt, const char *arg, int min, int max)
+{
+ int value = atoi(arg);
+
+ if (min <= value && value <= max) {
+ *opt = value;
+ return OK;
+ }
+
+ config_msg = "Integer value out of bound";
+ return ERR;
+}
+
+static bool
+set_color(int *color, const char *name)
+{
+ if (map_enum(color, color_map, name))
+ return TRUE;
+ if (!prefixcmp(name, "color"))
+ return parse_int(color, name + 5, 0, 255) == OK;
+ return FALSE;
+}
/* Wants: object fgcolor bgcolor [attribute] */
static int
info = get_line_info(argv[0]);
if (!info) {
- if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
- info = get_line_info("delimiter");
-
- } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
- info = get_line_info("date");
-
- } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
- info = get_line_info("author");
+ static struct enum_map obsolete[] = {
+ ENUM_MAP("main-delim", LINE_DELIMITER),
+ ENUM_MAP("main-date", LINE_DATE),
+ ENUM_MAP("main-author", LINE_AUTHOR),
+ };
+ int index;
- } else {
+ if (!map_enum(&index, obsolete, argv[0])) {
config_msg = "Unknown color name";
return ERR;
}
+ info = &line_info[index];
}
- if (set_color(&info->fg, argv[1]) == ERR ||
- set_color(&info->bg, argv[2]) == ERR) {
+ if (!set_color(&info->fg, argv[1]) ||
+ !set_color(&info->bg, argv[2])) {
config_msg = "Unknown color";
return ERR;
}
- if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
+ if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
config_msg = "Unknown attribute";
return ERR;
}
return OK;
}
-static bool parse_bool(const char *s)
+static int parse_bool(bool *opt, const char *arg)
{
- return (!strcmp(s, "1") || !strcmp(s, "true") ||
- !strcmp(s, "yes")) ? TRUE : FALSE;
+ *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
+ ? TRUE : FALSE;
+ return OK;
}
static int
-parse_int(const char *s, int default_value, int min, int max)
+parse_string(char *opt, const char *arg, size_t optsize)
{
- int value = atoi(s);
+ int arglen = strlen(arg);
- return (value < min || value > max) ? default_value : value;
+ switch (arg[0]) {
+ case '\"':
+ case '\'':
+ if (arglen == 1 || arg[arglen - 1] != arg[0]) {
+ config_msg = "Unmatched quotation";
+ return ERR;
+ }
+ arg += 1; arglen -= 2;
+ default:
+ string_ncopy_do(opt, optsize, arg, strlen(arg));
+ return OK;
+ }
}
/* Wants: name = value */
return ERR;
}
- if (!strcmp(argv[0], "show-author")) {
- opt_author = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-author"))
+ return parse_bool(&opt_author, argv[2]);
- if (!strcmp(argv[0], "show-date")) {
- opt_date = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-date"))
+ return parse_bool(&opt_date, argv[2]);
- if (!strcmp(argv[0], "show-rev-graph")) {
- opt_rev_graph = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-rev-graph"))
+ return parse_bool(&opt_rev_graph, argv[2]);
- if (!strcmp(argv[0], "show-refs")) {
- opt_show_refs = parse_bool(argv[2]);
- return OK;
- }
-
- if (!strcmp(argv[0], "show-line-numbers")) {
- opt_line_number = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-refs"))
+ return parse_bool(&opt_show_refs, argv[2]);
- if (!strcmp(argv[0], "line-graphics")) {
- opt_line_graphics = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-line-numbers"))
+ return parse_bool(&opt_line_number, argv[2]);
- if (!strcmp(argv[0], "line-number-interval")) {
- opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "line-graphics"))
+ return parse_bool(&opt_line_graphics, argv[2]);
- if (!strcmp(argv[0], "author-width")) {
- opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "line-number-interval"))
+ return parse_int(&opt_num_interval, argv[2], 1, 1024);
- if (!strcmp(argv[0], "tab-size")) {
- opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "author-width"))
+ return parse_int(&opt_author_cols, argv[2], 0, 1024);
- if (!strcmp(argv[0], "commit-encoding")) {
- const char *arg = argv[2];
- int arglen = strlen(arg);
+ if (!strcmp(argv[0], "tab-size"))
+ return parse_int(&opt_tab_size, argv[2], 1, 1024);
- switch (arg[0]) {
- case '\"':
- case '\'':
- if (arglen == 1 || arg[arglen - 1] != arg[0]) {
- config_msg = "Unmatched quotation";
- return ERR;
- }
- arg += 1; arglen -= 2;
- default:
- string_ncopy(opt_encoding, arg, strlen(arg));
- return OK;
- }
- }
+ if (!strcmp(argv[0], "commit-encoding"))
+ return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
config_msg = "Unknown variable name";
return ERR;
request = get_request(argv[2]);
if (request == REQ_NONE) {
- struct {
- const char *name;
- enum request request;
- } obsolete[] = {
- { "cherry-pick", REQ_NONE },
- { "screen-resize", REQ_NONE },
- { "tree-parent", REQ_PARENT },
+ static struct enum_map obsolete[] = {
+ ENUM_MAP("cherry-pick", REQ_NONE),
+ ENUM_MAP("screen-resize", REQ_NONE),
+ ENUM_MAP("tree-parent", REQ_PARENT),
};
- size_t namelen = strlen(argv[2]);
- int i;
+ int alias;
- for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
- if (namelen != strlen(obsolete[i].name) ||
- string_enum_compare(obsolete[i].name, argv[2], namelen))
- continue;
- if (obsolete[i].request != REQ_NONE)
- add_keybinding(keymap, obsolete[i].request, key);
+ if (map_enum(&alias, obsolete, argv[2])) {
+ if (alias != REQ_NONE)
+ add_keybinding(keymap, alias, key);
config_msg = "Obsolete request name";
return ERR;
}
add_builtin_run_requests();
- if (!tigrc_system) {
- if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
- return ERR;
- tigrc_system = buf;
- }
+ if (!tigrc_system)
+ tigrc_system = SYSCONFDIR "/tigrc";
load_option_file(tigrc_system);
if (!tigrc_user) {
* Navigation
*/
+static bool
+goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
+{
+ if (lineno >= view->lines)
+ lineno = view->lines > 0 ? view->lines - 1 : 0;
+
+ if (offset > lineno || offset + view->height <= lineno) {
+ unsigned long half = view->height / 2;
+
+ if (lineno > half)
+ offset = lineno - half;
+ else
+ offset = 0;
+ }
+
+ if (offset != view->offset || lineno != view->lineno) {
+ view->offset = offset;
+ view->lineno = lineno;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
/* Scrolling backend */
static void
do_scroll_view(struct view *view, int lines)
static void
select_view_line(struct view *view, unsigned long lineno)
{
- if (lineno - view->offset >= view->height) {
- view->offset = lineno;
- view->lineno = lineno;
- if (view_is_displayed(view))
- redraw_view(view);
+ unsigned long old_lineno = view->lineno;
+ unsigned long old_offset = view->offset;
- } else {
- unsigned long old_lineno = view->lineno - view->offset;
-
- view->lineno = lineno;
+ if (goto_view_line(view, view->offset, lineno)) {
if (view_is_displayed(view)) {
- draw_view_line(view, old_lineno);
- draw_view_line(view, view->lineno - view->offset);
- wnoutrefresh(view->win);
+ if (old_offset != view->offset) {
+ redraw_view(view);
+ } else {
+ draw_view_line(view, old_lineno - view->offset);
+ draw_view_line(view, view->lineno - view->offset);
+ wnoutrefresh(view->win);
+ }
} else {
view->ops->select(view, &view->line[view->lineno]);
}
return FALSE;
}
- if (view->p_lineno >= view->lines) {
- view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
- if (view->p_offset >= view->p_lineno) {
- unsigned long half = view->height / 2;
-
- if (view->p_lineno > half)
- view->p_offset = view->p_lineno - half;
- else
- view->p_offset = 0;
- }
- }
-
- if (view_is_displayed(view) &&
- view->offset != view->p_offset &&
- view->lineno != view->p_lineno)
+ if (goto_view_line(view, view->p_offset, view->p_lineno) &&
+ view_is_displayed(view))
werase(view->win);
- view->offset = view->p_offset;
view->yoffset = view->p_yoffset;
- view->lineno = view->p_lineno;
view->p_restore = FALSE;
return TRUE;
enum open_flags {
OPEN_DEFAULT = 0, /* Use default view switching. */
OPEN_SPLIT = 1, /* Split current view. */
- OPEN_BACKGROUNDED = 2, /* Backgrounded. */
OPEN_RELOAD = 4, /* Reload view even if it is the current. */
- OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
OPEN_REFRESH = 16, /* Refresh view using previous command. */
OPEN_PREPARED = 32, /* Open already prepared command. */
};
static void
open_view(struct view *prev, enum request request, enum open_flags flags)
{
- bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
bool split = !!(flags & OPEN_SPLIT);
bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
- bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
+ bool nomaximize = !!(flags & OPEN_REFRESH);
struct view *view = VIEW(request);
int nviews = displayed_views();
struct view *base_view = display[0];
if (split) {
display[1] = view;
- if (!backgrounded)
- current_view = 1;
+ current_view = 1;
} else if (!nomaximize) {
/* Maximize the current view. */
memset(display, 0, sizeof(display));
}
if (prev && view != prev) {
- if (split && !backgrounded) {
+ if (split) {
/* "Blur" the previous view. */
update_view_title(prev);
}
redraw_view(view);
report("");
}
-
- /* If the view is backgrounded the above calls to report()
- * won't redraw the view title. */
- if (backgrounded)
- update_view_title(view);
}
static void
* View backend utilities
*/
+static void
+parse_timezone(time_t *time, const char *zone)
+{
+ long tz;
+
+ tz = ('0' - zone[1]) * 60 * 60 * 10;
+ tz += ('0' - zone[2]) * 60 * 60;
+ tz += ('0' - zone[3]) * 60;
+ tz += ('0' - zone[4]);
+
+ if (zone[0] == '-')
+ tz = -tz;
+
+ *time -= tz;
+}
+
/* Parse author lines where the name may be empty:
* author <email@address.tld> 1138474660 +0100
*/
@@ -3363,20 +3358,8 @@ parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
char *zone = strchr(secs, ' ');
time_t time = (time_t) atol(secs);
- if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
- long tz;
-
- zone++;
- tz = ('0' - zone[1]) * 60 * 60 * 10;
- tz += ('0' - zone[2]) * 60 * 60;
- tz += ('0' - zone[3]) * 60;
- tz += ('0' - zone[4]) * 60;
-
- if (zone[0] == '-')
- tz = -tz;
-
- time -= tz;
- }
+ if (zone && strlen(zone) == STRING_SIZE(" +0700"))
+ parse_timezone(&time, zone + 1);
gmtime_r(&time, tm);
}
struct blame {
struct blame_commit *commit;
+ unsigned long lineno;
char text[1];
};
{
struct blame_commit *commit;
struct blame *blame;
- const char *pos = text + SIZEOF_REV - 1;
+ const char *pos = text + SIZEOF_REV - 2;
+ size_t orig_lineno = 0;
size_t lineno;
size_t group;
- if (strlen(text) <= SIZEOF_REV || *pos != ' ')
+ if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
return NULL;
- if (!parse_number(&pos, &lineno, 1, view->lines) ||
+ if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
+ !parse_number(&pos, &lineno, 1, view->lines) ||
!parse_number(&pos, &group, 1, view->lines - lineno + 1))
return NULL;
blame = line->data;
blame->commit = commit;
+ blame->lineno = orig_lineno + group - 1;
line->dirty = 1;
}
return FALSE;
} else {
- size_t linelen = string_expand_length(line, opt_tab_size);
+ size_t linelen = strlen(line);
struct blame *blame = malloc(sizeof(*blame) + linelen);
if (!blame)
return FALSE;
blame->commit = NULL;
- string_expand(blame->text, linelen + 1, line, opt_tab_size);
+ strncpy(blame->text, line, linelen);
+ blame->text[linelen] = 0;
return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
}
}
author_time = (time_t) atol(line);
} else if (match_blame_header("author-tz ", &line)) {
- long tz;
-
- tz = ('0' - line[1]) * 60 * 60 * 10;
- tz += ('0' - line[2]) * 60 * 60;
- tz += ('0' - line[3]) * 60;
- tz += ('0' - line[4]) * 60;
-
- if (line[0] == '-')
- tz = -tz;
-
- author_time -= tz;
+ parse_timezone(&author_time, line);
gmtime_r(&author_time, &commit->time);
} else if (match_blame_header("summary ", &line)) {
struct blame *blame = line->data;
struct tm *time = NULL;
const char *id = NULL, *author = NULL;
+ char text[SIZEOF_STR];
if (blame->commit && *blame->commit->filename) {
id = blame->commit->id;
if (draw_lineno(view, lineno))
return TRUE;
- draw_text(view, LINE_DEFAULT, blame->text, TRUE);
+ string_expand(text, sizeof(text), blame->text, opt_tab_size);
+ draw_text(view, LINE_DEFAULT, text, TRUE);
return TRUE;
}
case REQ_VIEW_BLAME:
if (check_blame_commit(blame)) {
string_copy(opt_ref, blame->commit->id);
+ string_copy(opt_file, blame->commit->filename);
+ if (blame->lineno)
+ view->lineno = blame->lineno;
open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
}
break;
view->p_restore = FALSE;
}
+static void
+status_update_onbranch(void)
+{
+ static const char *paths[][2] = {
+ { "rebase-apply/rebasing", "Rebasing" },
+ { "rebase-apply/applying", "Applying mailbox" },
+ { "rebase-apply/", "Rebasing mailbox" },
+ { "rebase-merge/interactive", "Interactive rebase" },
+ { "rebase-merge/", "Rebase merge" },
+ { "MERGE_HEAD", "Merging" },
+ { "BISECT_LOG", "Bisecting" },
+ { "HEAD", "On branch" },
+ };
+ char buf[SIZEOF_STR];
+ struct stat stat;
+ int i;
+
+ if (is_initial_commit()) {
+ string_copy(status_onbranch, "Initial commit");
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++) {
+ char *head = opt_head;
+
+ if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
+ lstat(buf, &stat) < 0)
+ continue;
+
+ if (!*opt_head) {
+ struct io io = {};
+
+ if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
+ io_open(&io, buf) &&
+ io_read_buf(&io, buf, sizeof(buf))) {
+ head = chomp_string(buf);
+ if (!prefixcmp(head, "refs/heads/"))
+ head += STRING_SIZE("refs/heads/");
+ }
+ }
+
+ if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
+ string_copy(status_onbranch, opt_head);
+ return;
+ }
+
+ string_copy(status_onbranch, "Not currently on any branch");
+}
+
/* First parse staged info using git-diff-index(1), then parse unstaged
* info using git-diff-files(1), and finally untracked files using
* git-ls-files(1). */
reset_view(view);
add_line_data(view, NULL, LINE_STAT_HEAD);
- if (is_initial_commit())
- string_copy(status_onbranch, "Initial commit");
- else if (!*opt_head)
- string_copy(status_onbranch, "Not currently on any branch");
- else if (!string_format(status_onbranch, "On branch %s", opt_head))
- return FALSE;
+ status_update_onbranch();
run_io_bg(update_index_argv);
}
VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
- open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
+ open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
/* Check whether the staged entry still exists, and close the
* stage view if it doesn't. */
{
char *cmd = read_prompt(":");
- if (cmd) {
+ if (cmd && isdigit(*cmd)) {
+ int lineno = view->lineno + 1;
+
+ if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
+ select_view_line(view, lineno - 1);
+ report("");
+ } else {
+ report("Unable to parse '%s' as a line number", cmd);
+ }
+
+ } else if (cmd) {
struct view *next = VIEW(REQ_VIEW_PAGER);
const char *argv[SIZEOF_ARG] = { "git" };
int argc = 1;