"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Mark Jamsek <mark@jamsek.com>
Subject:
tog: keymaps to navigate to prev/next file/hunk in the diff
To:
gameoftrees@openbsd.org
Date:
Mon, 25 Jul 2022 02:54:29 +1000

Download raw body.

Thread
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