index 74f78df589921a65ae038903944c2f4c0be5029e..88338d0f2832b59f6390d56a8f100efe0bac948a 100644 (file)
--- a/tig.c
+++ b/tig.c
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 {
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)
{
@@ -692,7 +709,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"), \
{ '@', REQ_STAGE_NEXT },
{ ',', REQ_TREE_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" };
+ const char *obsolete[] = { "cherry-pick", "screen-resize" };
size_t namelen = strlen(argv[2]);
int i;
/* 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 */
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);
+ redrawwin(view->win);
+ 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;
@@ -2587,6 +2603,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)
{
end_update(view, FALSE);
}
+ if (restore_view_position(view))
+ redraw = TRUE;
+
if (!view_is_displayed(view))
return TRUE;
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);
report("tig-%s (built %s)", TIG_VERSION, __DATE__);
return TRUE;
- case REQ_SCREEN_RESIZE:
- resize_display();
- /* Fall-through */
case REQ_SCREEN_REDRAW:
redraw_display(TRUE);
break;
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 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 {
} 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;
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[5] = "--";
+ diff_index_argv[6] = "/dev/null";
+ }
+
if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
report("Failed to allocate diff command");
break;
"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);
add_line_data(view, NULL, LINE_STAT_HEAD);
!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)
continue;
- if (!pos && (!status || !status->status))
+ if (!pos && (!status || !status->status) && line[1].data) {
+ select_view_line(view, lineno);
return TRUE;
- if (pos && !strcmp(status->new.name, pos->new.name))
+ }
+ 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 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;
+ /* Refresh, accept single keystroke of input */
+ key = wgetch(status_win);
+
+ /* wgetch() with nodelay() enabled returns ERR when
+ * there's no input. */
+ if (key == ERR) {
+ doupdate();
+
+ } else if (key == KEY_RESIZE) {
+ int height, width;
+
+ getmaxyx(stdscr, height, width);
+
+ /* 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 bool
+prompt_yesno(const char *prompt)
+{
+ enum { WAIT, STOP, CANCEL } status = WAIT;
+ bool answer = FALSE;
+
+ while (status == WAIT) {
+ int key;
mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
wclrtoeol(status_win);
- /* Refresh, accept single keystroke of input */
- key = wgetch(status_win);
+ key = get_input(TRUE);
switch (key) {
- case ERR:
- break;
-
case 'y':
case 'Y':
answer = TRUE;
int pos = 0;
while (status == READING) {
- struct view *view;
- int i, key;
-
- input_mode = TRUE;
-
- foreach_view (view, i)
- update_view(view);
-
- input_mode = FALSE;
+ 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:
status = CANCEL;
break;
- case ERR:
- break;
-
default:
if (pos >= sizeof(buf)) {
report("Input string too long");
}
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;
}