Download raw body.
tog: keymaps to navigate to prev/next file/hunk in the diff
I got the idea from stsp the other day when he mentioned that he doesn't
always navigate diffs linearly, and I started paying close attention to
how I read diffs. He's right--sometimes I jump around a fair bit too,
and sometimes I read them top to bottom. In both cases, I find these key
maps help navigating diffs.
Two new sets of key maps are proposed:
P - jump to the previous file
N - jump to the next file
{ - jump to the previous hunk
} - jump to the next hunk
Next and previous are relative to the current line, and they work with
a count prefix too.
NP in particular are helpful in large diffs of multiple files; often
I want to read the man page first (if the diff involves user-facing
changes) to get an idea of what the implementation should do, then after
reading the code, I may want to jump back and forth from the docs if,
for example, I'm scrutinising something. But {} are also handy,
especially if, like me, you want to start reading a new section from the
top of the page. Or if the diff contains blocks of moved code (e.g.,
op's recent 'got path -R' change in 38d61ead4), it's nice to be able to
quickly jump to the next hunk.
diff refs/heads/main refs/heads/dev/ezdiffnav
commit - 10a16316a03005a07c45b2bbf1b5644b64e846fb
commit + a9ec599a292f341e598c9030e9271dbd2a9e4624
blob - c09dbe11bb6e0224ed0138a35da047947c82f41d
blob + cfe0e187356ff9cb6a95e48ac0be0e82559a946e
--- tog/tog.1
+++ tog/tog.1
@@ -290,6 +290,14 @@ Scroll up N half pages (default: 1).
Scroll to the top of the view.
.It Cm End, G
Scroll to the bottom of the view.
+.It Cm P
+Navigate to the Nth previous file in the diff (default: 1).
+.It Cm N
+Navigate to the Nth next file in the diff (default: 1).
+.It Cm \&{
+Navigate to the Nth previous hunk in the diff (default: 1).
+.It Cm \&}
+Navigate to the Nth next hunk in the diff (default: 1).
.It Cm \&[
Reduce diff context by N lines (default: 1).
.It Cm \&]
blob - aaca1ae9bd59788b771d3797213ce44b033f50a8
blob + aa20cfdbdcd6dd0083294b8d5c22a281f0ca3755
--- tog/tog.c
+++ tog/tog.c
@@ -313,12 +313,19 @@ get_color_value(const char *envvar)
return default_color_value(envvar);
}
+struct tog_index {
+ int *lineno;
+ int nalloc;
+ int idx;
+ int n;
+};
struct tog_diff_view_state {
struct got_object_id *id1, *id2;
const char *label1, *label2;
FILE *f, *f1, *f2;
int fd1, fd2;
+ int lineno;
int first_displayed_line;
int last_displayed_line;
int eof;
@@ -327,6 +334,7 @@ struct tog_diff_view_state {
int force_text_diff;
struct got_repository *repo;
struct tog_colors colors;
+ struct tog_index file_idx, hunk_idx;
size_t nlines;
off_t *line_offsets;
int matched_line;
@@ -4214,12 +4222,85 @@ done:
}
static const struct got_error *
+tog_alloc_index(struct tog_index *idx)
+{
+ if (idx->n == idx->nalloc) {
+ const size_t n = idx->nalloc ? idx->nalloc * 2 : 8;
+ int *p;
+
+ p = reallocarray(idx->lineno, n, sizeof(int));
+ if (p == NULL) {
+ free(idx->lineno);
+ idx->lineno = NULL;
+ return got_error_from_errno("reallocarray");
+ }
+ idx->lineno = p;
+ idx->nalloc = n;
+ }
+
+ return NULL;
+}
+
+/* Save line indexes of each file and hunk in the diff. */
+static const struct got_error *
+diff_index_file(struct tog_diff_view_state *s)
+{
+ const struct got_error *err = NULL;
+ char *line = NULL;
+ int len, lineno = 0;
+ size_t sz;
+
+ if (fseeko(s->f, 0L, SEEK_SET) == -1)
+ return got_error_from_errno("fseeko");
+
+ while (getline(&line, &sz, s->f) != -1) {
+ /* match: blob - {/dev/null,SHA1 (mode nnn)} */
+ len = strlen(line);
+ if (!strncmp(line, "blob - ", 7) &&
+ (len == SHA1_DIGEST_STRING_LENGTH + 7 ||
+ len == SHA1_DIGEST_STRING_LENGTH + 18 || len == 17)) {
+ err = tog_alloc_index(&s->file_idx);
+ if (err) {
+ free(line);
+ return err;
+ }
+ s->file_idx.lineno[s->file_idx.n++] = lineno;
+ } else if (!strncmp(line, "@@ -", 4)) {
+ err = tog_alloc_index(&s->hunk_idx);
+ if (err) {
+ free(line);
+ return err;
+ }
+ s->hunk_idx.lineno[s->hunk_idx.n++] = lineno;
+ }
+ ++lineno;
+ }
+ free(line);
+ if (!feof(s->f))
+ return got_ferror(s->f, GOT_ERR_IO);
+
+ return NULL;
+}
+
+static void
+free_index(struct tog_index *index)
+{
+ index->n = 0;
+ index->idx = 0;
+ index->nalloc = 0;
+ free(index->lineno);
+ index->lineno = NULL;
+}
+
+static const struct got_error *
create_diff(struct tog_diff_view_state *s)
{
const struct got_error *err = NULL;
FILE *f = NULL;
int obj_type;
+ free_index(&s->file_idx);
+ free_index(&s->hunk_idx);
free(s->line_offsets);
s->line_offsets = malloc(sizeof(off_t));
if (s->line_offsets == NULL)
@@ -4298,11 +4379,11 @@ create_diff(struct tog_diff_view_state *s)
err = got_error(GOT_ERR_OBJ_TYPE);
break;
}
- if (err)
- goto done;
done:
if (s->f && fflush(s->f) != 0 && err == NULL)
err = got_error_from_errno("fflush");
+ if (err == NULL)
+ err = diff_index_file(s);
return err;
}
@@ -4421,6 +4502,8 @@ close_diff_view(struct tog_view *view)
if (s->fd2 != -1 && close(s->fd2) == -1 && err == NULL)
err = got_error_from_errno("close");
s->fd2 = -1;
+ free_index(&s->file_idx);
+ free_index(&s->hunk_idx);
free_colors(&s->colors);
free(s->line_offsets);
s->line_offsets = NULL;
@@ -4639,6 +4722,45 @@ reset_diff_view(struct tog_view *view)
return create_diff(s);
}
+static int
+diff_prev_index(struct tog_diff_view_state *s, struct tog_index *i)
+{
+ if (!i->lineno)
+ return s->first_displayed_line;
+
+ if (s->lineno - 1 <= i->lineno[i->idx]) {
+ if (i->idx == 0 || s->lineno < i->lineno[0])
+ i->idx = i->n - 1;
+ else
+ while (i->idx > 0 && s->lineno - 1 <= i->lineno[i->idx])
+ --i->idx;
+ } else
+ while (i->idx < i->n - 1 && s->lineno > i->lineno[i->idx + 1])
+ ++i->idx;
+
+ return i->lineno[i->idx] + 1;
+}
+
+static int
+diff_next_index(struct tog_diff_view_state *s, struct tog_index *i)
+{
+ if (!i->lineno)
+ return s->first_displayed_line;
+
+ if (s->lineno - 1 >= i->lineno[i->idx]) {
+ if (i->idx == i->n - 1 || s->lineno > i->lineno[i->n - 1])
+ i->idx = 0;
+ else
+ while (i->idx < i->n - 1 &&
+ s->lineno > i->lineno[i->idx])
+ ++i->idx;
+ } else
+ while (i->idx > 0 && s->lineno - 1 < i->lineno[i->idx - 1])
+ --i->idx;
+
+ return i->lineno[i->idx] + 1;
+}
+
static struct got_object_id *get_selected_commit_id(struct tog_blame_line *,
int, int, int);
static struct got_object_id *get_annotation_for_line(struct tog_blame_line *,
@@ -4656,6 +4778,8 @@ input_diff_view(struct tog_view **new_view, struct tog
ssize_t linelen;
int i, nscroll = view->nlines - 1, up = 0;
+ s->lineno = s->first_displayed_line - 1 + s->selected_line;
+
switch (ch) {
case '0':
view->x = 0;
@@ -4756,6 +4880,22 @@ input_diff_view(struct tog_view **new_view, struct tog
}
free(line);
break;
+ case 'P':
+ s->first_displayed_line = diff_prev_index(s, &s->file_idx);
+ s->selected_line = 1;
+ break;
+ case 'N':
+ s->first_displayed_line = diff_next_index(s, &s->file_idx);
+ s->selected_line = 1;
+ break;
+ case '{':
+ s->first_displayed_line = diff_prev_index(s, &s->hunk_idx);
+ s->selected_line = 1;
+ break;
+ case '}':
+ s->first_displayed_line = diff_next_index(s, &s->hunk_idx);
+ s->selected_line = 1;
+ break;
case '[':
if (s->diff_context > 0) {
s->diff_context--;
--
Mark Jamsek <fnc.bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68
tog: keymaps to navigate to prev/next file/hunk in the diff