index 544fd8544331ddf143c2659e9f747d89171c5da9..4fac53286e2beeecda9b688d90fda3bac10f3eec 100644 (file)
--- a/tig.c
+++ b/tig.c
-/* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
+/* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
-static bool prompt_yesno(const char *prompt);
static int load_refs(void);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
return ERR;
}
+enum input_status {
+ INPUT_OK,
+ INPUT_SKIP,
+ INPUT_STOP,
+ INPUT_CANCEL
+};
+
+typedef enum input_status (*input_handler)(void *data, char *buf, int c);
+
+static char *prompt_input(const char *prompt, input_handler handler, void *data);
+static bool prompt_yesno(const char *prompt);
/*
* String helpers
IO_FG, /* Execute command with same std{in,out,err}. */
IO_RD, /* Read only fork+exec IO. */
IO_WR, /* Write only fork+exec IO. */
+ IO_AP, /* Append fork+exec output to file. */
};
struct io {
static bool
kill_io(struct io *io)
{
- return kill(io->pid, SIGKILL) != -1;
+ return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
}
static bool
if ((io->type == IO_RD || io->type == IO_WR) &&
pipe(pipefds) < 0)
return FALSE;
+ else if (io->type == IO_AP)
+ pipefds[1] = io->pipe;
if ((io->pid = fork())) {
if (pipefds[!(io->type == IO_WR)] != -1)
if (io->type != IO_FG) {
int devnull = open("/dev/null", O_RDWR);
int readfd = io->type == IO_WR ? pipefds[0] : devnull;
- int writefd = io->type == IO_RD ? pipefds[1] : devnull;
+ int writefd = (io->type == IO_RD || io->type == IO_AP)
+ ? pipefds[1] : devnull;
dup2(readfd, STDIN_FILENO);
dup2(writefd, STDOUT_FILENO);
return run_io_do(&io);
}
+static bool
+run_io_append(const char **argv, enum format_flags flags, int fd)
+{
+ struct io io = {};
+
+ init_io(&io, NULL, IO_AP);
+ io.pipe = fd;
+ if (format_argv(io.argv, argv, flags))
+ return run_io_do(&io);
+ close(fd);
+ return FALSE;
+}
+
static bool
run_io_rd(struct io *io, const char **argv, enum format_flags flags)
{
@@ -649,6 +676,7 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(ENTER, "Enter current line and scroll"), \
REQ_(NEXT, "Move to next"), \
REQ_(PREVIOUS, "Move to previous"), \
+ REQ_(PARENT, "Move to parent"), \
REQ_(VIEW_NEXT, "Move focus to next view"), \
REQ_(REFRESH, "Reload and refresh"), \
REQ_(MAXIMIZE, "Maximize the current view"), \
@@ -660,7 +688,6 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(STATUS_REVERT, "Revert file changes"), \
REQ_(STATUS_MERGE, "Merge file using external tool"), \
REQ_(STAGE_NEXT, "Find next chunk to stage"), \
- REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
\
REQ_GROUP("Cursor navigation") \
REQ_(MOVE_UP, "Move cursor one line up"), \
@@ -692,7 +719,6 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_GROUP("Misc") \
REQ_(PROMPT, "Bring up the prompt"), \
REQ_(SCREEN_REDRAW, "Redraw the screen"), \
- REQ_(SCREEN_RESIZE, "Resize the screen"), \
REQ_(SHOW_VERSION, "Show version information"), \
REQ_(STOP_LOADING, "Stop all loading views"), \
REQ_(EDIT, "Open in editor"), \
static iconv_t opt_iconv = ICONV_NONE;
static char opt_search[SIZEOF_STR] = "";
static char opt_cdup[SIZEOF_STR] = "";
+static char opt_prefix[SIZEOF_STR] = "";
static char opt_git_dir[SIZEOF_STR] = "";
static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
static char opt_editor[SIZEOF_STR] = "";
bool seen_dashdash = FALSE;
/* XXX: This is vulnerable to the user overriding options
* required for the main view parser. */
- const char *custom_argv[SIZEOF_ARG] = {
+ static const char *custom_argv[SIZEOF_ARG] = {
"git", "log", "--no-color", "--pretty=raw", "--parents",
"--topo-order", NULL
};
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
-LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
+LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
+LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
/* State flags */
unsigned int selected:1;
unsigned int dirty:1;
+ unsigned int cleareol:1;
void *data; /* User data */
};
{ '!', REQ_STATUS_REVERT },
{ 'M', REQ_STATUS_MERGE },
{ '@', REQ_STAGE_NEXT },
- { ',', REQ_TREE_PARENT },
+ { ',', REQ_PARENT },
{ 'e', REQ_EDIT },
-
- /* Using the ncurses SIGWINCH handler. */
- { KEY_RESIZE, REQ_SCREEN_RESIZE },
};
#define KEYMAP_INFO \
request = get_request(argv[2]);
if (request == REQ_NONE) {
- const char *obsolete[] = { "cherry-pick" };
+ struct {
+ const char *name;
+ enum request request;
+ } obsolete[] = {
+ { "cherry-pick", REQ_NONE },
+ { "screen-resize", REQ_NONE },
+ { "tree-parent", REQ_PARENT },
+ };
size_t namelen = strlen(argv[2]);
int i;
for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
- if (namelen == strlen(obsolete[i]) &&
- !string_enum_compare(obsolete[i], argv[2], namelen)) {
- config_msg = "Obsolete request name";
- return ERR;
- }
+ 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);
+ config_msg = "Obsolete request name";
+ return ERR;
}
}
if (request == REQ_NONE && *argv[2]++ == '!')
/* Navigation */
unsigned long offset; /* Offset of the window top */
unsigned long lineno; /* Current line number */
+ unsigned long p_offset; /* Previous offset of the window top */
+ unsigned long p_lineno; /* Previous current line number */
+ bool p_restore; /* Should the previous position be restored. */
/* Searching */
char grep[SIZEOF_STR]; /* Search string */
size_t lines; /* Total number of lines */
struct line *line; /* Line index */
size_t line_alloc; /* Total number of allocated lines */
- size_t line_size; /* Total number of used lines */
unsigned int digits; /* Number of digits in the lines member. */
/* Drawing */
struct io io;
struct io *pipe;
time_t start_time;
+ time_t update_secs;
};
struct view_ops {
line = &view->line[view->offset + lineno];
wmove(view->win, lineno, 0);
+ if (line->cleareol)
+ wclrtoeol(view->win);
view->col = 0;
view->curline = line;
view->curtype = LINE_NONE;
line->selected = FALSE;
+ line->dirty = line->cleareol = 0;
if (selected) {
set_view_attr(view, LINE_CURSOR);
line->selected = TRUE;
view->ops->select(view, line);
- } else if (line->selected) {
- wclrtoeol(view->win);
}
scrollok(view->win, FALSE);
int lineno;
for (lineno = 0; lineno < view->height; lineno++) {
- struct line *line = &view->line[view->offset + lineno];
-
- if (!line->dirty)
+ if (view->offset + lineno >= view->lines)
+ break;
+ if (!view->line[view->offset + lineno].dirty)
continue;
- line->dirty = 0;
dirty = TRUE;
if (!draw_view_line(view, lineno))
break;
if (!dirty)
return;
- redrawwin(view->win);
if (input_mode)
wnoutrefresh(view->win);
else
break;
}
- redrawwin(view->win);
if (input_mode)
wnoutrefresh(view->win);
else
static void
redraw_view(struct view *view)
{
- wclear(view->win);
+ werase(view->win);
redraw_view_from(view, 0);
}
assert(view_is_displayed(view));
- if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
+ if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
unsigned int view_lines = view->offset + view->height;
unsigned int lines = view->lines
? MIN(view_lines, view->lines) * 100 / view->lines
: 0;
- string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+ string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
view->ops->type,
view->lineno + 1,
view->lines,
lines);
- if (view->pipe) {
- time_t secs = time(NULL) - view->start_time;
+ }
- /* Three git seconds are a long time ... */
- if (secs > 2)
- string_format_from(state, &statelen, " %lds", secs);
- }
+ if (view->pipe) {
+ time_t secs = time(NULL) - view->start_time;
+
+ /* Three git seconds are a long time ... */
+ if (secs > 2)
+ string_format_from(state, &statelen, " loading %lds", secs);
}
string_format_from(buf, &bufpos, "[%s]", view->name);
}
if (statelen && bufpos < view->width) {
- string_format_from(buf, &bufpos, " %s", state);
+ string_format_from(buf, &bufpos, "%s", state);
}
if (view == display[current_view])
}
static void
-redraw_display(void)
+redraw_display(bool clear)
{
struct view *view;
int i;
foreach_displayed_view (view, i) {
+ if (clear)
+ wclear(view->win);
redraw_view(view);
update_view_title(view);
}
}
}
+static void
+toggle_view_option(bool *option, const char *help)
+{
+ *option = !*option;
+ redraw_display(FALSE);
+ report("%sabling %s", *option ? "En" : "Dis", help);
+}
+
/*
* Navigation
*/
draw_view_line(view, view->lineno - view->offset);
}
- redrawwin(view->win);
wrefresh(view->win);
report("");
}
/* Draw the current line */
draw_view_line(view, view->lineno - view->offset);
- redrawwin(view->win);
wrefresh(view->win);
report("");
}
static void search_view(struct view *view, enum request request);
-static bool
-find_next_line(struct view *view, unsigned long lineno, struct line *line)
+static void
+select_view_line(struct view *view, unsigned long lineno)
{
- assert(view_is_displayed(view));
-
- if (!view->ops->grep(view, line))
- return FALSE;
-
if (lineno - view->offset >= view->height) {
view->offset = lineno;
view->lineno = lineno;
- redraw_view(view);
+ if (view_is_displayed(view))
+ redraw_view(view);
} else {
unsigned long old_lineno = view->lineno - view->offset;
view->lineno = lineno;
- draw_view_line(view, old_lineno);
-
- draw_view_line(view, view->lineno - view->offset);
- redrawwin(view->win);
- wrefresh(view->win);
+ if (view_is_displayed(view)) {
+ draw_view_line(view, old_lineno);
+ draw_view_line(view, view->lineno - view->offset);
+ wrefresh(view->win);
+ } else {
+ view->ops->select(view, &view->line[view->lineno]);
+ }
}
-
- report("Line %ld matches '%s'", lineno + 1, view->grep);
- return TRUE;
}
static void
/* Note, lineno is unsigned long so will wrap around in which case it
* will become bigger than view->lines. */
for (; lineno < view->lines; lineno += direction) {
- struct line *line = &view->line[lineno];
-
- if (find_next_line(view, lineno, line))
+ if (view->ops->grep(view, &view->line[lineno])) {
+ select_view_line(view, lineno);
+ report("Line %ld matches '%s'", lineno + 1, view->grep);
return;
+ }
}
report("No match found for '%s'", view->grep);
free(view->line[i].data);
free(view->line);
+ view->p_offset = view->offset;
+ view->p_lineno = view->lineno;
+
view->line = NULL;
view->offset = 0;
view->lines = 0;
view->lineno = 0;
- view->line_size = 0;
view->line_alloc = 0;
view->vid[0] = 0;
+ view->update_secs = 0;
}
static void
@@ -2575,6 +2620,43 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl
return src_argv[argc] == NULL;
}
+static bool
+restore_view_position(struct view *view)
+{
+ if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
+ return FALSE;
+
+ /* Changing the view position cancels the restoring. */
+ /* FIXME: Changing back to the first line is not detected. */
+ if (view->offset != 0 || view->lineno != 0) {
+ view->p_restore = FALSE;
+ 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)
+ werase(view->win);
+
+ view->offset = view->p_offset;
+ view->lineno = view->p_lineno;
+ view->p_restore = FALSE;
+
+ return TRUE;
+}
+
static void
end_update(struct view *view, bool force)
{
static bool
begin_update(struct view *view, bool refresh)
{
+ if (view->pipe)
+ end_update(view, TRUE);
+
if (refresh) {
if (!start_io(&view->io))
return FALSE;
view->line = tmp;
view->line_alloc = alloc;
- view->line_size = line_size;
return view->line;
}
{
char out_buffer[BUFSIZ * 2];
char *line;
- int redraw_from = -1;
+ /* Clear the view and redraw everything since the tree sorting
+ * might have rearranged things. */
+ bool redraw = view->lines == 0;
bool can_read = TRUE;
if (!view->pipe)
return TRUE;
- if (!io_can_read(view->pipe))
- return TRUE;
+ if (!io_can_read(view->pipe)) {
+ if (view->lines == 0) {
+ time_t secs = time(NULL) - view->start_time;
- /* Only redraw if lines are visible. */
- if (view->offset + view->height >= view->lines)
- redraw_from = view->lines - view->offset;
+ if (secs > view->update_secs) {
+ if (view->update_secs == 0)
+ redraw_view(view);
+ update_view_title(view);
+ view->update_secs = secs;
+ }
+ }
+ return TRUE;
+ }
for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
- size_t linelen = strlen(line);
-
if (opt_iconv != ICONV_NONE) {
ICONV_CONST char *inbuf = line;
- size_t inlen = linelen;
+ size_t inlen = strlen(line) + 1;
char *outbuf = out_buffer;
size_t outlen = sizeof(out_buffer);
size_t ret;
ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
- if (ret != (size_t) -1) {
+ if (ret != (size_t) -1)
line = out_buffer;
- linelen = strlen(out_buffer);
- }
}
- if (!realloc_lines(view, view->lines + 1) ||
- !view->ops->read(view, line))
- goto alloc_error;
+ if (!view->ops->read(view, line)) {
+ report("Allocation failure");
+ end_update(view, TRUE);
+ return FALSE;
+ }
}
{
/* Keep the displayed view in sync with line number scaling. */
if (digits != view->digits) {
view->digits = digits;
- redraw_from = 0;
+ if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
+ redraw = TRUE;
}
}
end_update(view, FALSE);
}
+ if (restore_view_position(view))
+ redraw = TRUE;
+
if (!view_is_displayed(view))
return TRUE;
- if (view == VIEW(REQ_VIEW_TREE)) {
- /* Clear the view and redraw everything since the tree sorting
- * might have rearranged things. */
- redraw_view(view);
-
- } else if (redraw_from >= 0) {
- /* If this is an incremental update, redraw the previous line
- * since for commits some members could have changed when
- * loading the main view. */
- if (redraw_from > 0)
- redraw_from--;
-
- /* Since revision graph visualization requires knowledge
- * about the parent commit, it causes a further one-off
- * needed to be redrawn for incremental updates. */
- if (redraw_from > 0 && opt_rev_graph)
- redraw_from--;
-
- /* Incrementally draw avoids flickering. */
- redraw_view_from(view, redraw_from);
- }
-
- if (view == VIEW(REQ_VIEW_BLAME))
+ if (redraw)
+ redraw_view_from(view, 0);
+ else
redraw_view_dirty(view);
/* Update the title _after_ the redraw so that if the redraw picks up a
* commit reference in view->ref it'll be available here. */
update_view_title(view);
return TRUE;
-
-alloc_error:
- report("Allocation failure");
- end_update(view, TRUE);
- return FALSE;
}
static struct line *
add_line_data(struct view *view, void *data, enum line_type type)
{
- struct line *line = &view->line[view->lines++];
+ struct line *line;
+ if (!realloc_lines(view, view->lines + 1))
+ return NULL;
+
+ line = &view->line[view->lines++];
memset(line, 0, sizeof(*line));
line->type = type;
line->data = data;
+ line->dirty = 1;
return line;
}
return data ? add_line_data(view, data, type) : NULL;
}
+static struct line *
+add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
+{
+ char buf[SIZEOF_STR];
+ va_list args;
+
+ va_start(args, fmt);
+ if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
+ buf[0] = 0;
+ va_end(args);
+
+ return buf[0] ? add_line_text(view, buf, type) : NULL;
+}
/*
* View opening
(nviews == 1 && base_view != display[0]))
resize_display();
- if (view->pipe)
- end_update(view, TRUE);
-
if (view->ops->open) {
+ if (view->pipe)
+ end_update(view, TRUE);
if (!view->ops->open(view)) {
report("Failed to load %s view", view->name);
return;
}
+ restore_view_position(view);
} else if ((reload || strcmp(view->vid, view->id)) &&
!begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
/* Clear the old view and let the incremental updating refill
* the screen. */
werase(view->win);
+ view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
report("");
} else if (view_is_displayed(view)) {
redraw_view(view);
fprintf(stderr, "Press Enter to continue");
getc(opt_tty);
reset_prog_mode();
- redraw_display();
+ redraw_display(TRUE);
}
static void
{
const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
- open_external_viewer(mergetool_argv, NULL);
+ open_external_viewer(mergetool_argv, opt_cdup);
}
static void
break;
case REQ_TOGGLE_LINENO:
- opt_line_number = !opt_line_number;
- redraw_display();
+ toggle_view_option(&opt_line_number, "line numbers");
break;
case REQ_TOGGLE_DATE:
- opt_date = !opt_date;
- redraw_display();
+ toggle_view_option(&opt_date, "date display");
break;
case REQ_TOGGLE_AUTHOR:
- opt_author = !opt_author;
- redraw_display();
+ toggle_view_option(&opt_author, "author display");
break;
case REQ_TOGGLE_REV_GRAPH:
- opt_rev_graph = !opt_rev_graph;
- redraw_display();
+ toggle_view_option(&opt_rev_graph, "revision graph display");
break;
case REQ_TOGGLE_REFS:
- opt_show_refs = !opt_show_refs;
- redraw_display();
+ toggle_view_option(&opt_show_refs, "reference display");
break;
case REQ_SEARCH:
report("tig-%s (built %s)", TIG_VERSION, __DATE__);
return TRUE;
- case REQ_SCREEN_RESIZE:
- resize_display();
- /* Fall-through */
case REQ_SCREEN_REDRAW:
- redraw_display();
+ redraw_display(TRUE);
break;
case REQ_EDIT:
display[current_view] = view->parent;
view->parent = view;
resize_display();
- redraw_display();
+ redraw_display(FALSE);
report("");
break;
}
}
+/*
+ * View backend utilities
+ */
+
+/* Parse author lines where the name may be empty:
+ * author <email@address.tld> 1138474660 +0100
+ */
+static void
+parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
+{
+ char *nameend = strchr(ident, '<');
+ char *emailend = strchr(ident, '>');
+
+ if (nameend && emailend)
+ *nameend = *emailend = 0;
+ ident = chomp_string(ident);
+ if (!*ident) {
+ if (nameend)
+ ident = chomp_string(nameend + 1);
+ if (!*ident)
+ ident = "Unknown";
+ }
+
+ string_ncopy_do(author, authorsize, ident, strlen(ident));
+
+ /* Parse epoch and timezone */
+ if (emailend && emailend[1] == ' ') {
+ char *secs = emailend + 2;
+ 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;
+ }
+
+ gmtime_r(&time, tm);
+ }
+}
+
+static enum input_status
+select_commit_parent_handler(void *data, char *buf, int c)
+{
+ size_t parents = *(size_t *) data;
+ int parent = 0;
+
+ if (!isdigit(c))
+ return INPUT_SKIP;
+
+ if (*buf)
+ parent = atoi(buf) * 10;
+ parent += c - '0';
+
+ if (parent > parents)
+ return INPUT_SKIP;
+ return INPUT_OK;
+}
+
+static bool
+select_commit_parent(const char *id, char rev[SIZEOF_REV])
+{
+ char buf[SIZEOF_STR * 4];
+ const char *revlist_argv[] = {
+ "git", "rev-list", "-1", "--parents", id, NULL
+ };
+ int parents;
+
+ if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
+ !*chomp_string(buf) ||
+ (parents = (strlen(buf) / 40) - 1) < 0) {
+ report("Failed to get parent information");
+ return FALSE;
+
+ } else if (parents == 0) {
+ report("The selected commit has no parents");
+ return FALSE;
+ }
+
+ if (parents > 1) {
+ char prompt[SIZEOF_STR];
+ char *result;
+
+ if (!string_format(prompt, "Which parent? [1..%d] ", parents))
+ return FALSE;
+ result = prompt_input(prompt, select_commit_parent_handler, &parents);
+ if (!result)
+ return FALSE;
+ parents = atoi(result);
+ }
+
+ string_copy_rev(rev, &buf[41 * parents]);
+ return TRUE;
+}
+
/*
* Pager backend
*/
if (bufpos == 0)
return;
- if (!realloc_lines(view, view->line_size + 1))
- return;
-
add_line_text(view, buf, LINE_PP_REFS);
}
static bool
help_open(struct view *view)
{
- char buf[BUFSIZ];
- int lines = ARRAY_SIZE(req_info) + 2;
+ char buf[SIZEOF_STR];
+ size_t bufpos;
int i;
if (view->lines > 0)
return TRUE;
- for (i = 0; i < ARRAY_SIZE(req_info); i++)
- if (!req_info[i].request)
- lines++;
-
- lines += run_requests + 1;
-
- view->line = calloc(lines, sizeof(*view->line));
- if (!view->line)
- return FALSE;
-
add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
for (i = 0; i < ARRAY_SIZE(req_info); i++) {
if (!*key)
key = "(no key defined)";
- if (!string_format(buf, " %-25s %s", key, req_info[i].help))
- continue;
+ for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
+ buf[bufpos] = tolower(req_info[i].name[bufpos]);
+ if (buf[bufpos] == '_')
+ buf[bufpos] = '-';
+ }
- add_line_text(view, buf, LINE_DEFAULT);
+ add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
+ key, buf, req_info[i].help);
}
if (run_requests) {
for (i = 0; i < run_requests; i++) {
struct run_request *req = get_run_request(REQ_NONE + i + 1);
const char *key;
- char cmd[SIZEOF_STR];
- size_t bufpos;
int argc;
if (!req)
key = "(no key defined)";
for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
- if (!string_format_from(cmd, &bufpos, "%s%s",
+ if (!string_format_from(buf, &bufpos, "%s%s",
argc ? " " : "", req->argv[argc]))
return REQ_NONE;
- if (!string_format(buf, " %-10s %-14s `%s`",
- keymap_table[req->keymap].name, key, cmd))
- continue;
-
- add_line_text(view, buf, LINE_DEFAULT);
+ add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
+ keymap_table[req->keymap].name, key, buf);
}
return TRUE;
#define SIZEOF_TREE_ATTR \
STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
-#define TREE_UP_FORMAT "040000 tree %s\t.."
+#define SIZEOF_TREE_MODE \
+ STRING_SIZE("100644 ")
-static int
-tree_compare_entry(enum line_type type1, const char *name1,
- enum line_type type2, const char *name2)
-{
- if (type1 != type2) {
- if (type1 == LINE_TREE_DIR)
- return -1;
- return 1;
- }
+#define TREE_ID_OFFSET \
+ STRING_SIZE("100644 blob ")
- return strcmp(name1, name2);
-}
+struct tree_entry {
+ char id[SIZEOF_REV];
+ mode_t mode;
+ struct tm time; /* Date from the author ident. */
+ char author[75]; /* Author of the commit. */
+ char name[1];
+};
static const char *
tree_path(struct line *line)
{
- const char *path = line->data;
+ return ((struct tree_entry *) line->data)->name;
+}
+
+
+static int
+tree_compare_entry(struct line *line1, struct line *line2)
+{
+ if (line1->type != line2->type)
+ return line1->type == LINE_TREE_DIR ? -1 : 1;
+ return strcmp(tree_path(line1), tree_path(line2));
+}
+
+static struct line *
+tree_entry(struct view *view, enum line_type type, const char *path,
+ const char *mode, const char *id)
+{
+ struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
+ struct line *line = entry ? add_line_data(view, entry, type) : NULL;
+
+ if (!entry || !line) {
+ free(entry);
+ return NULL;
+ }
- return path + SIZEOF_TREE_ATTR;
+ strncpy(entry->name, path, strlen(path));
+ if (mode)
+ entry->mode = strtoul(mode, NULL, 8);
+ if (id)
+ string_copy_rev(entry->id, id);
+
+ return line;
}
static bool
-tree_read(struct view *view, char *text)
+tree_read_date(struct view *view, char *text, bool *read_date)
{
- size_t textlen = text ? strlen(text) : 0;
- char buf[SIZEOF_STR];
- unsigned long pos;
- enum line_type type;
- bool first_read = view->lines == 0;
+ static char author_name[SIZEOF_STR];
+ static struct tm author_time;
- if (!text)
+ if (!text && *read_date) {
+ *read_date = FALSE;
return TRUE;
- if (textlen <= SIZEOF_TREE_ATTR)
+
+ } else if (!text) {
+ char *path = *opt_path ? opt_path : ".";
+ /* Find next entry to process */
+ const char *log_file[] = {
+ "git", "log", "--no-color", "--pretty=raw",
+ "--cc", "--raw", view->id, "--", path, NULL
+ };
+ struct io io = {};
+
+ if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
+ report("Failed to load tree data");
+ return TRUE;
+ }
+
+ done_io(view->pipe);
+ view->io = io;
+ *read_date = TRUE;
return FALSE;
- type = text[STRING_SIZE("100644 ")] == 't'
- ? LINE_TREE_DIR : LINE_TREE_FILE;
+ } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
+ parse_author_line(text + STRING_SIZE("author "),
+ author_name, sizeof(author_name), &author_time);
- if (first_read) {
- /* Add path info line */
- if (!string_format(buf, "Directory path /%s", opt_path) ||
- !realloc_lines(view, view->line_size + 1) ||
- !add_line_text(view, buf, LINE_DEFAULT))
- return FALSE;
+ } else if (*text == ':') {
+ char *pos;
+ size_t annotated = 1;
+ size_t i;
- /* Insert "link" to parent directory. */
- if (*opt_path) {
- if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
- !realloc_lines(view, view->line_size + 1) ||
- !add_line_text(view, buf, LINE_TREE_DIR))
- return FALSE;
+ pos = strchr(text, '\t');
+ 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, '/');
+ if (pos)
+ *pos = 0;
+
+ for (i = 1; i < view->lines; i++) {
+ struct line *line = &view->line[i];
+ struct tree_entry *entry = line->data;
+
+ annotated += !!*entry->author;
+ if (*entry->author || strcmp(entry->name, text))
+ continue;
+
+ string_copy(entry->author, author_name);
+ memcpy(&entry->time, &author_time, sizeof(entry->time));
+ line->dirty = 1;
+ break;
}
+
+ if (annotated == view->lines)
+ kill_io(view->pipe);
}
+ return TRUE;
+}
+
+static bool
+tree_read(struct view *view, char *text)
+{
+ static bool read_date = FALSE;
+ struct tree_entry *data;
+ struct line *entry, *line;
+ enum line_type type;
+ size_t textlen = text ? strlen(text) : 0;
+ char *path = text + SIZEOF_TREE_ATTR;
+
+ if (read_date || !text)
+ return tree_read_date(view, text, &read_date);
+
+ if (textlen <= SIZEOF_TREE_ATTR)
+ return FALSE;
+ if (view->lines == 0 &&
+ !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
+ return FALSE;
/* Strip the path part ... */
if (*opt_path) {
size_t pathlen = textlen - SIZEOF_TREE_ATTR;
size_t striplen = strlen(opt_path);
- char *path = text + SIZEOF_TREE_ATTR;
if (pathlen > striplen)
memmove(path, path + striplen,
pathlen - striplen + 1);
+
+ /* Insert "link" to parent directory. */
+ if (view->lines == 1 &&
+ !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
+ return FALSE;
}
- /* Skip "Directory ..." and ".." line. */
- for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
- struct line *line = &view->line[pos];
- const char *path1 = tree_path(line);
- char *path2 = text + SIZEOF_TREE_ATTR;
- int cmp = tree_compare_entry(line->type, path1, type, path2);
+ type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
+ entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
+ if (!entry)
+ return FALSE;
+ data = entry->data;
- if (cmp <= 0)
+ /* Skip "Directory ..." and ".." line. */
+ for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
+ if (tree_compare_entry(line, entry) <= 0)
continue;
- text = strdup(text);
- if (!text)
- return FALSE;
+ memmove(line + 1, line, (entry - line) * sizeof(*entry));
- if (view->lines > pos)
- memmove(&view->line[pos + 1], &view->line[pos],
- (view->lines - pos) * sizeof(*line));
-
- line = &view->line[pos];
- line->data = text;
+ line->data = data;
line->type = type;
- view->lines++;
+ for (; line <= entry; line++)
+ line->dirty = line->cleareol = 1;
return TRUE;
}
- if (!add_line_text(view, text, type))
- return FALSE;
-
if (tree_lineno > view->lineno) {
view->lineno = tree_lineno;
tree_lineno = 0;
return TRUE;
}
+static bool
+tree_draw(struct view *view, struct line *line, unsigned int lineno)
+{
+ struct tree_entry *entry = line->data;
+
+ if (line->type == LINE_TREE_PARENT) {
+ if (draw_text(view, line->type, "Directory path /", TRUE))
+ return TRUE;
+ } else {
+ char mode[11] = "-r--r--r--";
+
+ if (S_ISDIR(entry->mode)) {
+ mode[3] = mode[6] = mode[9] = 'x';
+ mode[0] = 'd';
+ }
+ if (S_ISLNK(entry->mode))
+ mode[0] = 'l';
+ if (entry->mode & S_IWUSR)
+ mode[2] = 'w';
+ if (entry->mode & S_IXUSR)
+ mode[3] = 'x';
+ if (entry->mode & S_IXGRP)
+ mode[6] = 'x';
+ if (entry->mode & S_IXOTH)
+ mode[9] = 'x';
+ if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
+ return TRUE;
+
+ if (opt_author &&
+ draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
+ return TRUE;
+
+ if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
+ return TRUE;
+ }
+ if (draw_text(view, line->type, entry->name, TRUE))
+ return TRUE;
+ return TRUE;
+}
+
+static void
+open_blob_editor()
+{
+ char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
+ int fd = mkstemp(file);
+
+ if (fd == -1)
+ report("Failed to create temporary file");
+ else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
+ report("Failed to save blob data to file");
+ else
+ open_editor(FALSE, file);
+ if (fd != -1)
+ unlink(file);
+}
+
static enum request
tree_request(struct view *view, enum request request, struct line *line)
{
if (line->type != LINE_TREE_FILE) {
report("Edit only supported for files");
} else if (!is_head_commit(view->vid)) {
- report("Edit only supported for files in the current work tree");
+ open_blob_editor();
} else {
open_editor(TRUE, opt_file);
}
return REQ_NONE;
- case REQ_TREE_PARENT:
+ case REQ_PARENT:
if (!*opt_path) {
/* quit view if at top of tree */
return REQ_VIEW_CLOSE;
break;
default:
- return TRUE;
+ return REQ_NONE;
}
open_view(view, request, flags);
- if (request == REQ_VIEW_TREE) {
+ if (request == REQ_VIEW_TREE)
view->lineno = tree_lineno;
- }
return REQ_NONE;
}
static void
tree_select(struct view *view, struct line *line)
{
- char *text = (char *)line->data + STRING_SIZE("100644 blob ");
+ struct tree_entry *entry = line->data;
if (line->type == LINE_TREE_FILE) {
- string_copy_rev(ref_blob, text);
+ string_copy_rev(ref_blob, entry->id);
string_format(opt_file, "%s%s", opt_path, tree_path(line));
} else if (line->type != LINE_TREE_DIR) {
return;
}
- string_copy_rev(view->ref, text);
+ string_copy_rev(view->ref, entry->id);
}
static const char *tree_argv[SIZEOF_ARG] = {
tree_argv,
NULL,
tree_read,
- pager_draw,
+ tree_draw,
tree_request,
pager_grep,
tree_select,
return add_line_text(view, line, LINE_DEFAULT) != NULL;
}
+static enum request
+blob_request(struct view *view, enum request request, struct line *line)
+{
+ switch (request) {
+ case REQ_EDIT:
+ open_blob_editor();
+ return REQ_NONE;
+ default:
+ return pager_request(view, request, line);
+ }
+}
+
static const char *blob_argv[SIZEOF_ARG] = {
"git", "cat-file", "blob", "%(blob)", NULL
};
NULL,
blob_read,
pager_draw,
- pager_request,
+ blob_request,
pager_grep,
pager_select,
};
char author[75]; /* Author of the commit. */
struct tm time; /* Date from the author ident. */
char filename[128]; /* Name of file. */
+ bool has_previous; /* Was a "previous" line detected. */
};
struct blame {
if (!commit) {
commit = parse_blame_commit(view, line, &blamed);
string_format(view->ref, "%s %2d%%", view->vid,
- blamed * 100 / view->lines);
+ view->lines ? blamed * 100 / view->lines : 0);
} else if (match_blame_header("author ", &line)) {
string_ncopy(commit->author, line, strlen(line));
} else if (match_blame_header("summary ", &line)) {
string_ncopy(commit->title, line, strlen(line));
+ } else if (match_blame_header("previous ", &line)) {
+ commit->has_previous = TRUE;
+
} else if (match_blame_header("filename ", &line)) {
string_ncopy(commit->filename, line, strlen(line));
commit = NULL;
return TRUE;
}
+static bool
+check_blame_commit(struct blame *blame)
+{
+ if (!blame->commit)
+ report("Commit data not loaded yet");
+ else if (!strcmp(blame->commit->id, NULL_ID))
+ report("No commit exist for the selected line");
+ else
+ return TRUE;
+ return FALSE;
+}
+
static enum request
blame_request(struct view *view, enum request request, struct line *line)
{
switch (request) {
case REQ_VIEW_BLAME:
- if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
- report("Commit ID unknown");
- break;
+ if (check_blame_commit(blame)) {
+ string_copy(opt_ref, blame->commit->id);
+ open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
}
- string_copy(opt_ref, blame->commit->id);
- open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
- return request;
+ break;
+
+ case REQ_PARENT:
+ if (check_blame_commit(blame) &&
+ select_commit_parent(blame->commit->id, opt_ref))
+ open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+ break;
case REQ_ENTER:
if (!blame->commit) {
if (!strcmp(blame->commit->id, NULL_ID)) {
struct view *diff = VIEW(REQ_VIEW_DIFF);
const char *diff_index_argv[] = {
- "git", "diff-index", "--root", "--cached",
- "--patch-with-stat", "-C", "-M",
- "HEAD", "--", view->vid, NULL
+ "git", "diff-index", "--root", "--patch-with-stat",
+ "-C", "-M", "HEAD", "--", view->vid, NULL
};
+ if (!blame->commit->has_previous) {
+ diff_index_argv[1] = "diff";
+ diff_index_argv[2] = "--no-color";
+ diff_index_argv[6] = "--";
+ diff_index_argv[7] = "/dev/null";
+ }
+
if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
report("Failed to allocate diff command");
break;
}
open_view(view, REQ_VIEW_DIFF, flags);
+ if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
+ string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
break;
default:
@@ -4246,14 +4591,9 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
while ((buf = io_get(&io, 0, TRUE))) {
if (!file) {
- if (!realloc_lines(view, view->line_size + 1))
- goto error_out;
-
file = calloc(1, sizeof(*file));
- if (!file)
+ if (!file || !add_line_data(view, file, type))
goto error_out;
-
- add_line_data(view, file, type);
}
/* Parse diff info part. */
@@ -4281,6 +4621,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
unmerged = NULL;
if (collapse) {
free(file);
+ file = NULL;
view->lines--;
continue;
}
"git", "update-index", "-q", "--unmerged", "--refresh", NULL
};
+/* Restore the previous line number to stay in the context or select a
+ * line with something that can be updated. */
+static void
+status_restore(struct view *view)
+{
+ if (view->p_lineno >= view->lines)
+ view->p_lineno = view->lines - 1;
+ while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
+ view->p_lineno++;
+ while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
+ view->p_lineno--;
+
+ /* If the above fails, always skip the "On branch" line. */
+ if (view->p_lineno < view->lines)
+ view->lineno = view->p_lineno;
+ else
+ view->lineno = 1;
+
+ if (view->lineno < view->offset)
+ view->offset = view->lineno;
+ else if (view->offset + view->height <= view->lineno)
+ view->offset = view->lineno - view->height + 1;
+
+ view->p_restore = FALSE;
+}
+
/* 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). */
static bool
status_open(struct view *view)
{
- unsigned long prev_lineno = view->lineno;
-
reset_view(view);
- if (!realloc_lines(view, view->line_size + 7))
- return FALSE;
-
add_line_data(view, NULL, LINE_STAT_HEAD);
if (is_initial_commit())
string_copy(status_onbranch, "Initial commit");
!status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
return FALSE;
- /* If all went well restore the previous line number to stay in
- * the context or select a line with something that can be
- * updated. */
- if (prev_lineno >= view->lines)
- prev_lineno = view->lines - 1;
- while (prev_lineno < view->lines && !view->line[prev_lineno].data)
- prev_lineno++;
- while (prev_lineno > 0 && !view->line[prev_lineno].data)
- prev_lineno--;
-
- /* If the above fails, always skip the "On branch" line. */
- if (prev_lineno < view->lines)
- view->lineno = prev_lineno;
- else
- view->lineno = 1;
-
- if (view->lineno < view->offset)
- view->offset = view->lineno;
- else if (view->offset + view->height <= view->lineno)
- view->offset = view->lineno - view->height + 1;
-
+ /* Restore the exact position or use the specialized restore
+ * mode? */
+ if (!view->p_restore)
+ status_restore(view);
return TRUE;
}
}
split = view_is_displayed(view) ? OPEN_SPLIT : 0;
- open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
+ open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
if (status) {
stage_status = *status;
status_exists(struct status *status, enum line_type type)
{
struct view *view = VIEW(REQ_VIEW_STATUS);
- struct line *line;
+ unsigned long lineno;
- for (line = view->line; line < view->line + view->lines; line++) {
+ for (lineno = 0; lineno < view->lines; lineno++) {
+ struct line *line = &view->line[lineno];
struct status *pos = line->data;
- if (line->type == type && pos &&
- !strcmp(status->new.name, pos->new.name))
+ if (line->type != type)
+ continue;
+ if (!pos && (!status || !status->status) && line[1].data) {
+ select_view_line(view, lineno);
return TRUE;
+ }
+ if (pos && !strcmp(status->new.name, pos->new.name)) {
+ select_view_line(view, lineno);
+ return TRUE;
+ }
}
return FALSE;
return request;
}
+ VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
/* Check whether the staged entry still exists, and close the
* stage view if it doesn't. */
- if (!status_exists(&stage_status, stage_line_type))
+ if (!status_exists(&stage_status, stage_line_type)) {
+ status_restore(VIEW(REQ_VIEW_STATUS));
return REQ_VIEW_CLOSE;
+ }
if (stage_line_type == LINE_STAT_UNTRACKED) {
if (!suffixcmp(stage_status.new.name, -1, "/")) {
}
static void
-update_rev_graph(struct rev_graph *graph)
+update_rev_graph(struct view *view, struct rev_graph *graph)
{
/* If this is the finalizing update ... */
if (graph->commit)
if (!graph->prev->commit)
return;
+ if (view->lines > 2)
+ view->line[view->lines - 3].dirty = 1;
+ if (view->lines > 1)
+ view->line[view->lines - 2].dirty = 1;
draw_rev_graph(graph->prev);
done_rev_graph(graph->prev->prev);
}
die("No revisions match the given arguments.");
if (view->lines > 0) {
commit = view->line[view->lines - 1].data;
+ view->line[view->lines - 1].dirty = 1;
if (!*commit->author) {
view->lines--;
free(commit);
graph->commit = NULL;
}
}
- update_rev_graph(graph);
+ update_rev_graph(view, graph);
for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
clear_rev_graph(&graph_stacks[i]);
break;
case LINE_AUTHOR:
- {
- /* Parse author lines where the name may be empty:
- * author <email@address.tld> 1138474660 +0100
- */
- char *ident = line + STRING_SIZE("author ");
- char *nameend = strchr(ident, '<');
- char *emailend = strchr(ident, '>');
-
- if (!nameend || !emailend)
- break;
-
- update_rev_graph(graph);
+ parse_author_line(line + STRING_SIZE("author "),
+ commit->author, sizeof(commit->author),
+ &commit->time);
+ update_rev_graph(view, graph);
graph = graph->next;
-
- *nameend = *emailend = 0;
- ident = chomp_string(ident);
- if (!*ident) {
- ident = chomp_string(nameend + 1);
- if (!*ident)
- ident = "Unknown";
- }
-
- string_ncopy(commit->author, ident, strlen(ident));
-
- /* Parse epoch and timezone */
- if (emailend[1] == ' ') {
- char *secs = emailend + 2;
- 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;
- }
-
- gmtime_r(&time, &commit->time);
- }
break;
- }
+
default:
/* Fill in the commit title if it has not already been set. */
if (commit->title[0])
* shortened titles, etc. */
string_ncopy(commit->title, line, strlen(line));
+ view->line[view->lines - 1].dirty = 1;
}
return TRUE;
}
}
-static bool
-prompt_yesno(const char *prompt)
+static int
+get_input(bool prompting)
{
- enum { WAIT, STOP, CANCEL } status = WAIT;
- bool answer = FALSE;
-
- while (status == WAIT) {
- struct view *view;
- int i, key;
+ struct view *view;
+ int i, key;
+ if (prompting)
input_mode = TRUE;
+ while (true) {
foreach_view (view, i)
update_view(view);
- input_mode = FALSE;
-
- mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
- wclrtoeol(status_win);
-
/* Refresh, accept single keystroke of input */
key = wgetch(status_win);
- switch (key) {
- case ERR:
- break;
- case 'y':
- case 'Y':
- answer = TRUE;
- status = STOP;
- break;
+ /* wgetch() with nodelay() enabled returns ERR when
+ * there's no input. */
+ if (key == ERR) {
+ doupdate();
- case KEY_ESC:
- case KEY_RETURN:
- case KEY_ENTER:
- case KEY_BACKSPACE:
- case 'n':
- case 'N':
- case '\n':
- default:
- answer = FALSE;
- status = CANCEL;
- }
- }
+ } else if (key == KEY_RESIZE) {
+ int height, width;
- /* Clear the status window */
- status_empty = FALSE;
- report("");
+ getmaxyx(stdscr, height, width);
- return answer;
+ /* Resize the status view and let the view driver take
+ * care of resizing the displayed views. */
+ resize_display();
+ redraw_display(TRUE);
+ wresize(status_win, 1, width);
+ mvwin(status_win, height - 1, 0);
+ wrefresh(status_win);
+
+ } else {
+ input_mode = FALSE;
+ return key;
+ }
+ }
}
static char *
-read_prompt(const char *prompt)
+prompt_input(const char *prompt, input_handler handler, void *data)
{
- enum { READING, STOP, CANCEL } status = READING;
+ enum input_status status = INPUT_OK;
static char buf[SIZEOF_STR];
- int pos = 0;
-
- while (status == READING) {
- struct view *view;
- int i, key;
-
- input_mode = TRUE;
+ size_t pos = 0;
- foreach_view (view, i)
- update_view(view);
+ buf[pos] = 0;
- input_mode = FALSE;
+ while (status == INPUT_OK || status == INPUT_SKIP) {
+ int key;
mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
wclrtoeol(status_win);
- /* Refresh, accept single keystroke of input */
- key = wgetch(status_win);
+ key = get_input(TRUE);
switch (key) {
case KEY_RETURN:
case KEY_ENTER:
case '\n':
- status = pos ? STOP : CANCEL;
+ status = pos ? INPUT_STOP : INPUT_CANCEL;
break;
case KEY_BACKSPACE:
if (pos > 0)
- pos--;
+ buf[--pos] = 0;
else
- status = CANCEL;
+ status = INPUT_CANCEL;
break;
case KEY_ESC:
- status = CANCEL;
- break;
-
- case ERR:
+ status = INPUT_CANCEL;
break;
default:
return NULL;
}
- if (isprint(key))
+ status = handler(data, buf, key);
+ if (status == INPUT_OK)
buf[pos++] = (char) key;
}
}
status_empty = FALSE;
report("");
- if (status == CANCEL)
+ if (status == INPUT_CANCEL)
return NULL;
buf[pos++] = 0;
return buf;
}
+static enum input_status
+prompt_yesno_handler(void *data, char *buf, int c)
+{
+ if (c == 'y' || c == 'Y')
+ return INPUT_STOP;
+ if (c == 'n' || c == 'N')
+ return INPUT_CANCEL;
+ return INPUT_SKIP;
+}
+
+static bool
+prompt_yesno(const char *prompt)
+{
+ char prompt2[SIZEOF_STR];
+
+ if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
+ return FALSE;
+
+ return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
+}
+
+static enum input_status
+read_prompt_handler(void *data, char *buf, int c)
+{
+ return isprint(c) ? INPUT_OK : INPUT_SKIP;
+}
+
+static char *
+read_prompt(const char *prompt)
+{
+ return prompt_input(prompt, read_prompt_handler, NULL);
+}
+
/*
* Repository properties
*/
* the option else either "true" or "false" is printed.
* Default to true for the unknown case. */
opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
- } else {
+
+ } else if (*name == '.') {
string_ncopy(opt_cdup, name, namelen);
+
+ } else {
+ string_ncopy(opt_prefix, name, namelen);
}
return OK;
};
const char *rev_parse_argv[] = {
"git", "rev-parse", "--git-dir", "--is-inside-work-tree",
- "--show-cdup", NULL
+ "--show-cdup", "--show-prefix", NULL
};
if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
}
while (view_driver(display[current_view], request)) {
- int key;
- int i;
+ int key = get_input(FALSE);
- foreach_view (view, i)
- update_view(view);
view = display[current_view];
-
- /* Refresh, accept single keystroke of input */
- key = wgetch(status_win);
-
- /* wgetch() with nodelay() enabled returns ERR when there's no
- * input. */
- if (key == ERR) {
- request = REQ_NONE;
- continue;
- }
-
request = get_keybinding(view->keymap, key);
/* Some low-level request handling. This keeps access to
request = REQ_NONE;
break;
}
- case REQ_SCREEN_RESIZE:
- {
- int height, width;
-
- getmaxyx(stdscr, height, width);
-
- /* Resize the status view and let the view driver take
- * care of resizing the displayed views. */
- wresize(status_win, 1, width);
- mvwin(status_win, height - 1, 0);
- wrefresh(status_win);
- break;
- }
default:
break;
}