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

From:
Mark Jamsek <mark@jamsek.com>
Subject:
Re: tog: keymaps to navigate to prev/next file/hunk in the diff
To:
gameoftrees@openbsd.org
Date:
Mon, 1 Aug 2022 01:17:27 +1000

Download raw body.

Thread
On 22-07-31 03:42pm, Stefan Sperling wrote:
> On Sun, Jul 31, 2022 at 10:19:22PM +1000, Mark Jamsek wrote:
> > The diff also includes some changes to tog where we can now identify
> > lines to colour in the diff view without regex. I included this here for
> > the sake of demonstrating another benefit of having the line type index
> > besides the hunk/file navigation, but I can separate this now for review
> > if you prefer. If ok this would be a couple separate commits, plus the
> > diff code committed to diff.git and then the sync to got.
> 
> Coloring diffs doesn't seem to work correctly yet.
> I see most diff text, including the - and + lines, displayed in the
> terminal's default color when I test your diff.
> 
> I would suggest to leave this part alone for now, i.e. keep matching
> by regex for colorization. We can always change this later once your
> other changes are known to be stable. Those changes by themselves are
> already a significant improvement.

It worked on my terminal--honestly!

I just looked into this and it appears that on some terminals
COLOR_PAIRS can be defined as low as 64. On my term, it was defined as
32767, which is why it worked for me as we set some colours as high as
255 in the previous diff and anything we define must be within the range
1 to COLOR_PAIRS-1. So the problem wasn't actually in the tog code but
in how we defined the DIFF_LINE_* bits.

The revised diff corrects this. But I'll separate the tog colour change
from the diff code changes and the next/prev file/hunk bits. I'll do
this tomorrow and shoot them through (including the suggested
improvements to the diff_{next,prev}_index functions). I was just about
to go to bed when I thought I'd check my email; I saw this and thought
wtf?! So had to figure out what happened :)

diff refs/heads/main 27336574fb6654651641a191dda5922f518daa25
commit - 86b4b772a2c289053be29f6df2ad411dc853e05a
commit + 27336574fb6654651641a191dda5922f518daa25
blob - 52a212802c5d2d319bd43f2fa914eb2f48d10b90
blob + 15d9ab77ce628dc6b1939507cdf0e236943d6b82
--- got/got.c
+++ got/got.c
@@ -3701,7 +3701,7 @@ diff_trees(struct got_object_id *tree_id1, struct got_
 	arg.force_text_diff = force_text_diff;
 	arg.diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
 	arg.outfile = outfile;
-	arg.line_offsets = NULL;
+	arg.lines = NULL;
 	arg.nlines = 0;
 	while (path[0] == '/')
 		path++;
blob - 4243be1c5479449f6d3623e760d1979bd58682b8
blob + 047879a0f0642a3f5933281b8ca444b50a33e01f
--- include/got_diff.h
+++ include/got_diff.h
@@ -20,6 +20,31 @@ enum got_diff_algorithm {
 };

 /*
+ * List of all line types in a diff (including '{got,tog} log' lines).
+ * XXX GOT_DIFF_LINE_HUNK to GOT_DIFF_LINE_NONE inclusive must map to the
+ * DIFF_LINE_* macro counterparts defined in lib/diff_output.h (60-64).
+ */
+enum got_diff_line_type {
+	GOT_DIFF_LINE_LOGMSG,
+	GOT_DIFF_LINE_AUTHOR,
+	GOT_DIFF_LINE_DATE,
+	GOT_DIFF_LINE_CHANGES,
+	GOT_DIFF_LINE_META,
+	GOT_DIFF_LINE_BLOB_MIN,
+	GOT_DIFF_LINE_BLOB_PLUS,
+	GOT_DIFF_LINE_HUNK = 60,
+	GOT_DIFF_LINE_MINUS,
+	GOT_DIFF_LINE_PLUS,
+	GOT_DIFF_LINE_CONTEXT,
+	GOT_DIFF_LINE_NONE
+};
+
+struct got_diff_line {
+	off_t	offset;
+	uint8_t	type;
+};
+
+/*
  * Compute the differences between two blobs and write unified diff text
  * to the provided output file. Two open temporary files must be provided
  * for internal use; these files can be obtained from got_opentemp() and
@@ -34,7 +59,7 @@ enum got_diff_algorithm {
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_blob(off_t **, size_t *,
+const struct got_error *got_diff_blob(struct got_diff_line **, size_t *,
     struct got_blob_object *, struct got_blob_object *, FILE *, FILE *,
     const char *, const char *, enum got_diff_algorithm, int, int, int,
     FILE *);
@@ -84,18 +109,18 @@ struct got_diff_blob_output_unidiff_arg {

 	/*
 	 * The number of lines contained in produced unidiff text output,
-	 * and an array of byte offsets to each line. May be initialized to
-	 * zero and NULL to ignore line offsets. If not NULL, then the line
-	 * offsets array will be populated. Optionally, the array can be
-	 * pre-populated with line offsets, with nlines > 0 indicating
-	 * the length of the pre-populated array. This is useful if the
-	 * output file already contains some lines of text.
-	 * The array will be grown as needed to accomodate additional line
-	 * offsets, and the last offset found in a pre-populated array will
-	 * be added to all subsequent offsets.
+	 * and an array of got_diff_lines with byte offset and line type to
+	 * each line. May be initialized to zero and NULL to ignore line
+	 * metadata. If not NULL, then the array of line offsets and types will
+	 * be populated. Optionally, the array can be pre-populated with line
+	 * offsets and types, with nlines > 0 indicating the length of the
+	 * pre-populated array.  This is useful if the output file already
+	 * contains some lines of text.  The array will be grown as needed to
+	 * accomodate additional offsets and types, and the last offset found
+	 * in a pre-populated array will be added to all subsequent offsets.
 	 */
 	size_t nlines;
-	off_t *line_offsets;	/* Dispose of with free(3) when done. */
+	struct got_diff_line *lines; /* Dispose of with free(3) when done. */
 };
 const struct got_error *got_diff_blob_output_unidiff(void *,
     struct got_blob_object *, struct got_blob_object *, FILE *, FILE *,
@@ -155,10 +180,10 @@ const struct got_error *got_diff_tree_collect_changed_
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_blobs(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    const char *, const char *, enum got_diff_algorithm, int, int, int,
-    struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_blobs(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, const char *, const char *, enum got_diff_algorithm,
+    int, int, int, struct got_repository *, FILE *);

 struct got_pathlist_head;

@@ -177,10 +202,11 @@ struct got_pathlist_head;
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_trees(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    struct got_pathlist_head *, const char *, const char *,
-    enum got_diff_algorithm, int, int, int, struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_trees(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, struct got_pathlist_head *, const char *,
+    const char *, enum got_diff_algorithm, int, int, int,
+    struct got_repository *, FILE *);

 /*
  * Diff two objects, assuming both objects are commits.
@@ -194,9 +220,9 @@ const struct got_error *got_diff_objects_as_trees(off_
  * If not NULL, the two initial output arguments will be populated with an
  * array of line offsets for, and the number of lines in, the unidiff text.
  */
-const struct got_error *got_diff_objects_as_commits(off_t **, size_t *,
-    FILE *, FILE *, int, int, struct got_object_id *, struct got_object_id *,
-    struct got_pathlist_head *, enum got_diff_algorithm, int, int, int,
-    struct got_repository *, FILE *);
+const struct got_error *got_diff_objects_as_commits(struct got_diff_line **,
+    size_t *, FILE *, FILE *, int, int, struct got_object_id *,
+    struct got_object_id *, struct got_pathlist_head *, enum got_diff_algorithm,
+    int, int, int, struct got_repository *, FILE *);

 #define GOT_DIFF_MAX_CONTEXT	64
blob - 7268a714345f26eb33869d80941662fdd7688b13
blob + f671a4f105400e52465bc32e860ae0b0b1e71afd
--- lib/diff.c
+++ lib/diff.c
@@ -39,21 +39,24 @@
 #include "got_lib_object.h"

 static const struct got_error *
-add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
+add_line_metadata(struct got_diff_line **lines, size_t *nlines,
+    off_t off, uint8_t type)
 {
-	off_t *p;
+	struct got_diff_line *p;

-	p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
+	p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
 	if (p == NULL)
 		return got_error_from_errno("reallocarray");
-	*line_offsets = p;
-	(*line_offsets)[*nlines] = off;
+	*lines = p;
+	(*lines)[*nlines].offset = off;
+	(*lines)[*nlines].type = type;
 	(*nlines)++;
+
 	return NULL;
 }

 static const struct got_error *
-diff_blobs(off_t **line_offsets, size_t *nlines,
+diff_blobs(struct got_diff_line **lines, size_t *nlines,
     struct got_diffreg_result **resultp, struct got_blob_object *blob1,
     struct got_blob_object *blob2, FILE *f1, FILE *f2,
     const char *label1, const char *label2, mode_t mode1, mode_t mode2,
@@ -69,10 +72,10 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 	off_t outoff = 0;
 	int n;

-	if (line_offsets && *line_offsets && *nlines > 0)
-		outoff = (*line_offsets)[*nlines - 1];
-	else if (line_offsets) {
-		err = add_line_offset(line_offsets, nlines, 0);
+	if (lines && *lines && nlines > 0)
+		outoff = (*lines)[*nlines - 1].offset;
+	else if (lines) {
+		err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
 		if (err)
 			goto done;
 	}
@@ -141,8 +144,9 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		if (n < 0)
 			goto done;
 		outoff += n;
-		if (line_offsets) {
-			err = add_line_offset(line_offsets, nlines, outoff);
+		if (lines) {
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_BLOB_MIN);
 			if (err)
 				goto done;
 		}
@@ -152,8 +156,9 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		if (n < 0)
 			goto done;
 		outoff += n;
-		if (line_offsets) {
-			err = add_line_offset(line_offsets, nlines, outoff);
+		if (lines) {
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_BLOB_PLUS);
 			if (err)
 				goto done;
 		}
@@ -167,7 +172,7 @@ diff_blobs(off_t **line_offsets, size_t *nlines,
 		goto done;

 	if (outfile) {
-		err = got_diffreg_output(line_offsets, nlines, result,
+		err = got_diffreg_output(lines, nlines, result,
 		    blob1 != NULL, blob2 != NULL,
 		    label1 ? label1 : idstr1,
 		    label2 ? label2 : idstr2,
@@ -196,19 +201,19 @@ got_diff_blob_output_unidiff(void *arg, struct got_blo
 {
 	struct got_diff_blob_output_unidiff_arg *a = arg;

-	return diff_blobs(&a->line_offsets, &a->nlines, NULL,
+	return diff_blobs(&a->lines, &a->nlines, NULL,
 	    blob1, blob2, f1, f2, label1, label2, mode1, mode2, a->diff_context,
 	    a->ignore_whitespace, a->force_text_diff, a->outfile, a->diff_algo);
 }

 const struct got_error *
-got_diff_blob(off_t **line_offsets, size_t *nlines,
+got_diff_blob(struct got_diff_line **lines, size_t*nlines,
     struct got_blob_object *blob1, struct got_blob_object *blob2,
     FILE *f1, FILE *f2, const char *label1, const char *label2,
     enum got_diff_algorithm diff_algo, int diff_context,
     int ignore_whitespace, int force_text_diff, FILE *outfile)
 {
-	return diff_blobs(line_offsets, nlines, NULL, blob1, blob2, f1, f2,
+	return diff_blobs(lines, nlines, NULL, blob1, blob2, f1, f2,
 	    label1, label2, 0, 0, diff_context, ignore_whitespace,
 	    force_text_diff, outfile, diff_algo);
 }
@@ -726,7 +731,7 @@ got_diff_tree(struct got_tree_object *tree1, struct go
 }

 const struct got_error *
-got_diff_objects_as_blobs(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_blobs(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     const char *label1, const char *label2,
@@ -750,9 +755,9 @@ got_diff_objects_as_blobs(off_t **line_offsets, size_t
 		if (err)
 			goto done;
 	}
-	err = got_diff_blob(line_offsets, nlines, blob1, blob2, f1, f2,
-	    label1, label2, diff_algo, diff_context, ignore_whitespace,
-	    force_text_diff, outfile);
+	err = got_diff_blob(lines, nlines, blob1, blob2, f1, f2, label1, label2,
+	    diff_algo, diff_context, ignore_whitespace, force_text_diff,
+	    outfile);
 done:
 	if (blob1)
 		got_object_blob_close(blob1);
@@ -890,24 +895,26 @@ done:
 }

 static const struct got_error *
-show_object_id(off_t **line_offsets, size_t *nlines, const char *obj_typestr,
-    int ch, const char *id_str, FILE *outfile)
+show_object_id(struct got_diff_line **lines, size_t *nlines,
+    const char *obj_typestr, int ch, const char *id_str, FILE *outfile)
 {
 	const struct got_error *err;
 	int n;
 	off_t outoff = 0;

 	n = fprintf(outfile, "%s %c %s\n", obj_typestr, ch, id_str);
-	if (line_offsets != NULL && *line_offsets != NULL) {
+	if (lines != NULL && *lines != NULL) {
 		if (*nlines == 0) {
-			err = add_line_offset(line_offsets, nlines, 0);
+			err = add_line_metadata(lines, nlines, 0,
+			    GOT_DIFF_LINE_META);
 			if (err)
 				return err;
 		} else
-			outoff = (*line_offsets)[*nlines - 1];
+			outoff = (*lines)[*nlines - 1].offset;

 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_META);
 		if (err)
 			return err;
 	}
@@ -916,7 +923,7 @@ show_object_id(off_t **line_offsets, size_t *nlines, c
 }

 static const struct got_error *
-diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, const char *label1, const char *label2,
@@ -927,7 +934,7 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 	const struct got_error *err;
 	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
 	struct got_diff_blob_output_unidiff_arg arg;
-	int want_lineoffsets = (line_offsets != NULL && *line_offsets != NULL);
+	int want_linemeta = (lines != NULL && *lines != NULL);

 	if (id1 == NULL && id2 == NULL)
 		return got_error(GOT_ERR_NO_OBJ);
@@ -948,11 +955,11 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 	arg.ignore_whitespace = ignore_whitespace;
 	arg.force_text_diff = force_text_diff;
 	arg.outfile = outfile;
-	if (want_lineoffsets) {
-		arg.line_offsets = *line_offsets;
+	if (want_linemeta) {
+		arg.lines = *lines;
 		arg.nlines = *nlines;
 	} else {
-		arg.line_offsets = NULL;
+		arg.lines = NULL;
 		arg.nlines = 0;
 	}
 	if (paths == NULL || TAILQ_EMPTY(paths)) {
@@ -963,8 +970,8 @@ diff_objects_as_trees(off_t **line_offsets, size_t *nl
 		err = diff_paths(tree1, tree2, f1, f2, fd1, fd2, paths, repo,
 		    got_diff_blob_output_unidiff, &arg);
 	}
-	if (want_lineoffsets) {
-		*line_offsets = arg.line_offsets; /* was likely re-allocated */
+	if (want_linemeta) {
+		*lines = arg.lines; /* was likely re-allocated */
 		*nlines = arg.nlines;
 	}
 done:
@@ -976,7 +983,7 @@ done:
 }

 const struct got_error *
-got_diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, const char *label1, const char *label2,
@@ -993,15 +1000,14 @@ got_diff_objects_as_trees(off_t **line_offsets, size_t
 		err = got_object_id_str(&idstr, id1);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "tree", '-',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "tree", '-', idstr, outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "tree", '-',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "tree", '-', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}
@@ -1010,21 +1016,20 @@ got_diff_objects_as_trees(off_t **line_offsets, size_t
 		err = got_object_id_str(&idstr, id2);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "tree", '+',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "tree", '+', idstr, outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "tree", '+',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "tree", '+', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}

-	err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
-	    id1, id2, paths, label1, label2, diff_context, ignore_whitespace,
+	err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2, id1, id2,
+	    paths, label1, label2, diff_context, ignore_whitespace,
 	    force_text_diff, repo, outfile, diff_algo);
 done:
 	free(idstr);
@@ -1032,7 +1037,7 @@ done:
 }

 const struct got_error *
-got_diff_objects_as_commits(off_t **line_offsets, size_t *nlines,
+got_diff_objects_as_commits(struct got_diff_line **lines, size_t *nlines,
     FILE *f1, FILE *f2, int fd1, int fd2,
     struct got_object_id *id1, struct got_object_id *id2,
     struct got_pathlist_head *paths, enum got_diff_algorithm diff_algo,
@@ -1053,15 +1058,15 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 		err = got_object_id_str(&idstr, id1);
 		if (err)
 			goto done;
-		err = show_object_id(line_offsets, nlines, "commit", '-',
-		    idstr, outfile);
+		err = show_object_id(lines, nlines, "commit", '-', idstr,
+		    outfile);
 		if (err)
 			goto done;
 		free(idstr);
 		idstr = NULL;
 	} else {
-		err = show_object_id(line_offsets, nlines, "commit", '-',
-		    "/dev/null", outfile);
+		err = show_object_id(lines, nlines, "commit", '-', "/dev/null",
+		    outfile);
 		if (err)
 			goto done;
 	}
@@ -1073,12 +1078,11 @@ got_diff_objects_as_commits(off_t **line_offsets, size
 	err = got_object_id_str(&idstr, id2);
 	if (err)
 		goto done;
-	err = show_object_id(line_offsets, nlines, "commit", '+',
-	    idstr, outfile);
+	err = show_object_id(lines, nlines, "commit", '+', idstr, outfile);
 	if (err)
 		goto done;

-	err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
+	err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2,
 	    commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
 	    got_object_commit_get_tree_id(commit2), paths, "", "",
 	    diff_context, ignore_whitespace, force_text_diff, repo, outfile,
blob - e44ac0a4e3f7fe9caf5b350a89c7e5c5beba00be
blob + f9f748fd298e5716a0fbfa2a0ff0277abc7a331e
--- lib/diff_output.c
+++ lib/diff_output.c
@@ -64,6 +64,7 @@ diff_output_lines(struct diff_output_info *outinfo, FI
 {
 	struct diff_atom *atom;
 	off_t outoff = 0, *offp;
+	uint8_t *typep;
 	int rc;

 	if (outinfo && outinfo->line_offsets.len > 0) {
@@ -122,6 +123,12 @@ diff_output_lines(struct diff_output_info *outinfo, FI
 				return ENOMEM;
 			outoff += outlen;
 			*offp = outoff;
+			ARRAYLIST_ADD(typep, outinfo->line_types);
+			if (typep == NULL)
+				return ENOMEM;
+			*typep = *prefix == ' ' ? DIFF_LINE_CONTEXT :
+			    *prefix == '-' ? DIFF_LINE_MINUS :
+			    *prefix == '+' ? DIFF_LINE_PLUS : DIFF_LINE_NONE;
 		}
 	}

@@ -207,7 +214,9 @@ diff_output_trailing_newline_msg(struct diff_output_in
 	unsigned int atom_count;
 	int rc, ch;
 	off_t outoff = 0, *offp;
+	uint8_t *typep;

+
 	if (chunk_type == CHUNK_MINUS || chunk_type == CHUNK_SAME) {
 		start_atom = c->left_start;
 		atom_count = c->left_count;
@@ -240,6 +249,10 @@ diff_output_trailing_newline_msg(struct diff_output_in
 				return ENOMEM;
 			outoff += rc;
 			*offp = outoff;
+			ARRAYLIST_ADD(typep, outinfo->line_types);
+			if (typep == NULL)
+				return ENOMEM;
+			*typep = DIFF_LINE_NONE;
 		}
 	}

@@ -316,6 +329,7 @@ diff_output_info_alloc(void)
 {
 	struct diff_output_info *output_info;
 	off_t *offp;
+	uint8_t *typep;

 	output_info = malloc(sizeof(*output_info));
 	if (output_info != NULL) {
@@ -326,6 +340,13 @@ diff_output_info_alloc(void)
 			return NULL;
 		}
 		*offp = 0;
+		ARRAYLIST_INIT(output_info->line_types, 128);
+		ARRAYLIST_ADD(typep, output_info->line_types);
+		if (typep == NULL) {
+			diff_output_info_free(output_info);
+			return NULL;
+		}
+		*typep = DIFF_LINE_NONE;
 	}
 	return output_info;
 }
@@ -334,6 +355,7 @@ void
 diff_output_info_free(struct diff_output_info *output_info)
 {
 	ARRAYLIST_FREE(output_info->line_offsets);
+	ARRAYLIST_FREE(output_info->line_types);
 	free(output_info);
 }

blob - 6cd959e4159e40e5fcc467e4f69c7318fd54d3d5
blob + 304efb917050acadd79aae06eec9473ae5fdc797
--- lib/diff_output.h
+++ lib/diff_output.h
@@ -32,6 +32,16 @@ struct diff_output_info {
 	 * The last offset in this array corresponds to end-of-file.
 	 */
 	ARRAYLIST(off_t) line_offsets;
+	/*
+	 * Type (i.e., context, minus, plus) of each line generated by the diff.
+	 * nb. 0x00 to 0x3b reserved for client-defined line types.
+	 */
+	ARRAYLIST(uint8_t) line_types;
+#define DIFF_LINE_HUNK		0x3c
+#define DIFF_LINE_MINUS		0x3d
+#define DIFF_LINE_PLUS		0x3e
+#define DIFF_LINE_CONTEXT	0x3f
+#define DIFF_LINE_NONE		0x40  /* binary or no EOF newline msg, etc. */
 };

 void diff_output_info_free(struct diff_output_info *output_info);
blob - f3f4ad74c807d77b28b7b0af68f0d0ef2435d5c1
blob + 18bff74063081228c1c525cce2b35ad26a527bf8
--- lib/diff_output_unidiff.c
+++ lib/diff_output_unidiff.c
@@ -229,6 +229,7 @@ output_unidiff_chunk(struct diff_output_info *outinfo,
 {
 	int rc, left_start, left_len, right_start, right_len;
 	off_t outoff = 0, *offp;
+	uint8_t *typep;

 	if (diff_range_empty(&cc->left) && diff_range_empty(&cc->right))
 		return DIFF_RC_OK;
@@ -249,7 +250,10 @@ output_unidiff_chunk(struct diff_output_info *outinfo,
 				return ENOMEM;
 			outoff += rc;
 			*offp = outoff;
-
+			ARRAYLIST_ADD(typep, outinfo->line_types);
+			if (typep == NULL)
+				return ENOMEM;
+			*typep = DIFF_LINE_MINUS;
 		}
 		rc = fprintf(dest, "+++ %s\n",
 		    diff_output_get_label_right(info));
@@ -261,7 +265,10 @@ output_unidiff_chunk(struct diff_output_info *outinfo,
 				return ENOMEM;
 			outoff += rc;
 			*offp = outoff;
-
+			ARRAYLIST_ADD(typep, outinfo->line_types);
+			if (typep == NULL)
+				return ENOMEM;
+			*typep = DIFF_LINE_PLUS;
 		}
 		state->header_printed = true;
 	}
@@ -319,7 +326,10 @@ output_unidiff_chunk(struct diff_output_info *outinfo,
 			return ENOMEM;
 		outoff += rc;
 		*offp = outoff;
-
+		ARRAYLIST_ADD(typep, outinfo->line_types);
+		if (typep == NULL)
+			return ENOMEM;
+		*typep = DIFF_LINE_HUNK;
 	}

 	/* Got the absolute line numbers where to start printing, and the index
@@ -426,6 +436,7 @@ diff_output_unidiff(struct diff_output_info **output_i
 	bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA);
 	bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA);
 	off_t outoff = 0, *offp;
+	uint8_t *typep;
 	int rc, i;

 	if (!result)
@@ -463,7 +474,10 @@ diff_output_unidiff(struct diff_output_info **output_i
 					return ENOMEM;
 				outoff += rc;
 				*offp = outoff;
-
+				ARRAYLIST_ADD(typep, outinfo->line_types);
+				if (typep == NULL)
+					return ENOMEM;
+				*typep = DIFF_LINE_NONE;
 			}
 			break;
 		}
blob - 30c7388ad74a1c5e2b958cf79a9f5c1790de245d
blob + ab75c9fa52c19c7f3190c186c3002f46ee627c7e
--- lib/diffreg.c
+++ lib/diffreg.c
@@ -251,7 +251,7 @@ done:
 }

 const struct got_error *
-got_diffreg_output(off_t **line_offsets, size_t *nlines,
+got_diffreg_output(struct got_diff_line **lines, size_t *nlines,
     struct got_diffreg_result *diff_result, int f1_exists, int f2_exists,
     const char *path1, const char *path2,
     enum got_diff_output_format output_format, int context_lines, FILE *outfile)
@@ -272,13 +272,13 @@ got_diffreg_output(off_t **line_offsets, size_t *nline
 	switch (output_format) {
 	case GOT_DIFF_OUTPUT_UNIDIFF:
 		rc = diff_output_unidiff(
-		    line_offsets ? &output_info : NULL, outfile, &info,
+		    lines ? &output_info : NULL, outfile, &info,
 		    diff_result->result, context_lines);
 		if (rc != DIFF_RC_OK)
 			return got_error_set_errno(rc, "diff_output_unidiff");
 		break;
 	case GOT_DIFF_OUTPUT_EDSCRIPT:
-		rc = diff_output_edscript(line_offsets ? &output_info : NULL,
+		rc = diff_output_edscript(lines ? &output_info : NULL,
 		    outfile, &info, diff_result->result);
 		if (rc != DIFF_RC_OK)
 			return got_error_set_errno(rc, "diff_output_edscript");
@@ -286,29 +286,34 @@ got_diffreg_output(off_t **line_offsets, size_t *nline

 	}

-	if (line_offsets && *line_offsets) {
+	if (lines && *lines) {
 		if (output_info->line_offsets.len > 0) {
-			off_t prev_offset = 0, *p, *o;
+			struct got_diff_line *p;
+			off_t prev_offset = 0, *o;
+			uint8_t *o2;
 			int i, len;
 			if (*nlines > 0) {
-				prev_offset = (*line_offsets)[*nlines - 1];
+				prev_offset = (*lines)[*nlines - 1].offset;
 				/*
 				 * First line offset is always zero. Skip it
 				 * when appending to a pre-populated array.
 				 */
 				o = &output_info->line_offsets.head[1];
+				o2 = &output_info->line_types.head[1];
 				len = output_info->line_offsets.len - 1;
 			} else {
 				o = &output_info->line_offsets.head[0];
+				o2 = &output_info->line_types.head[0];
 				len = output_info->line_offsets.len;
 			}
-			p = reallocarray(*line_offsets, *nlines + len,
-			    sizeof(off_t));
+			p = reallocarray(*lines, *nlines + len, sizeof(**lines));
 			if (p == NULL)
 				return got_error_from_errno("calloc");
-			for (i = 0; i < len; i++)
-				p[*nlines + i] = o[i] + prev_offset;
-			*line_offsets = p;
+			for (i = 0; i < len; i++) {
+				p[*nlines + i].offset = o[i] + prev_offset;
+				p[*nlines + i].type = o2[i];
+			}
+			*lines = p;
 			*nlines += len;
 		}
 		diff_output_info_free(output_info);
blob - 3ff8d96891722f9a89a5245a2fb9b5e5c8424e83
blob + 43c7096d0a3fe2bb611660d822341235f3223a17
--- lib/got_lib_diff.h
+++ lib/got_lib_diff.h
@@ -44,7 +44,7 @@ const struct got_error *got_diff_prepare_file(FILE *,
     struct diff_data *, const struct diff_config *, int, int);
 const struct got_error *got_diffreg(struct got_diffreg_result **, FILE *,
     FILE *, enum got_diff_algorithm, int, int);
-const struct got_error *got_diffreg_output(off_t **, size_t *,
+const struct got_error *got_diffreg_output(struct got_diff_line **, size_t *,
     struct got_diffreg_result *, int, int, const char *, const char *,
     enum got_diff_output_format, int, FILE *);
 const struct got_error *got_diffreg_result_free(struct got_diffreg_result *);
blob - c09dbe11bb6e0224ed0138a35da047947c82f41d
blob + 21f349fab0529b88773681976e3bb44121f1de09
--- 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 \&(
+Navigate to the Nth previous file in the diff (default: 1).
+.It Cm \&)
+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 - 58990f76b31337bb2ba1c56ebcce5b3b13a3cb78
blob + af073ba6a6657ef99e64a176574997926ff63e2e
--- tog/tog.c
+++ tog/tog.c
@@ -313,12 +313,12 @@ get_color_value(const char *envvar)
 	return default_color_value(envvar);
 }

-
 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;
@@ -326,9 +326,8 @@ struct tog_diff_view_state {
 	int ignore_whitespace;
 	int force_text_diff;
 	struct got_repository *repo;
-	struct tog_colors colors;
+	struct got_diff_line *lines;
 	size_t nlines;
-	off_t *line_offsets;
 	int matched_line;
 	int selected_line;

@@ -3849,14 +3848,15 @@ draw_file(struct tog_view *view, const char *header)
 	char *line;
 	size_t linesize = 0;
 	ssize_t linelen;
-	struct tog_color *tc;
 	wchar_t *wline;
 	int width;
 	int max_lines = view->nlines;
 	int nlines = s->nlines;
 	off_t line_offset;
+	enum got_diff_line_type linetype;

-	line_offset = s->line_offsets[s->first_displayed_line - 1];
+	s->lineno = s->first_displayed_line - 1;
+	line_offset = s->lines[s->first_displayed_line - 1].offset;
 	if (fseeko(s->f, line_offset, SEEK_SET) == -1)
 		return got_error_from_errno("fseek");

@@ -3892,6 +3892,8 @@ draw_file(struct tog_view *view, const char *header)
 	view->maxx = 0;
 	line = NULL;
 	while (max_lines > 0 && nprinted < max_lines) {
+		attr_t attr = 0;
+
 		linelen = getline(&line, &linesize, s->f);
 		if (linelen == -1) {
 			if (feof(s->f)) {
@@ -3902,6 +3904,8 @@ draw_file(struct tog_view *view, const char *header)
 			return got_ferror(s->f, GOT_ERR_IO);
 		}

+		if (++s->lineno < s->first_displayed_line)
+			continue;
 		/* Set view->maxx based on full line length. */
 		err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
 		    view->x ? 1 : 0);
@@ -3913,10 +3917,12 @@ draw_file(struct tog_view *view, const char *header)
 		free(wline);
 		wline = NULL;

-		tc = match_color(&s->colors, line);
-		if (tc)
-			wattr_on(view->window,
-			    COLOR_PAIR(tc->colorpair), NULL);
+		linetype = s->lines[s->lineno].type;
+		if (linetype > GOT_DIFF_LINE_LOGMSG &&
+		    linetype < GOT_DIFF_LINE_CONTEXT)
+			attr = COLOR_PAIR(linetype);
+		if (attr)
+			wattron(view->window, attr);
 		if (s->first_displayed_line + nprinted == s->matched_line &&
 		    regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
 			err = add_matched_line(&width, line, view->ncols, 0,
@@ -3937,9 +3943,8 @@ draw_file(struct tog_view *view, const char *header)
 			free(wline);
 			wline = NULL;
 		}
-		if (tc)
-			wattr_off(view->window,
-			    COLOR_PAIR(tc->colorpair), NULL);
+		if (attr)
+			wattroff(view->window, attr);
 		if (width <= view->ncols - 1)
 			waddch(view->window, '\n');
 		nprinted++;
@@ -4043,21 +4048,24 @@ done:
 }

 static const struct got_error *
-add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
+add_line_metadata(struct got_diff_line **lines, size_t *nlines,
+    off_t off, uint8_t type)
 {
-	off_t *p;
+	struct got_diff_line *p;

-	p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
+	p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
 	if (p == NULL)
 		return got_error_from_errno("reallocarray");
-	*line_offsets = p;
-	(*line_offsets)[*nlines] = off;
+	*lines = p;
+	(*lines)[*nlines].offset = off;
+	(*lines)[*nlines].type = type;
 	(*nlines)++;
+
 	return NULL;
 }

 static const struct got_error *
-write_commit_info(off_t **line_offsets, size_t *nlines,
+write_commit_info(struct got_diff_line **lines, size_t *nlines,
     struct got_object_id *commit_id, struct got_reflist_head *refs,
     struct got_repository *repo, FILE *outfile)
 {
@@ -4091,7 +4099,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}

-	err = add_line_offset(line_offsets, nlines, 0);
+	err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
 	if (err)
 		goto done;

@@ -4102,7 +4110,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}
 	outoff += n;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_META);
 	if (err)
 		goto done;

@@ -4113,7 +4121,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 		goto done;
 	}
 	outoff += n;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_AUTHOR);
 	if (err)
 		goto done;

@@ -4126,7 +4134,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_DATE);
 		if (err)
 			goto done;
 	}
@@ -4139,7 +4148,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_AUTHOR);
 		if (err)
 			goto done;
 	}
@@ -4158,7 +4168,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 				goto done;
 			}
 			outoff += n;
-			err = add_line_offset(line_offsets, nlines, outoff);
+			err = add_line_metadata(lines, nlines, outoff,
+			    GOT_DIFF_LINE_META);
 			if (err)
 				goto done;
 			free(id_str);
@@ -4177,7 +4188,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_LOGMSG);
 		if (err)
 			goto done;
 	}
@@ -4193,7 +4205,8 @@ write_commit_info(off_t **line_offsets, size_t *nlines
 			goto done;
 		}
 		outoff += n;
-		err = add_line_offset(line_offsets, nlines, outoff);
+		err = add_line_metadata(lines, nlines, outoff,
+		    GOT_DIFF_LINE_CHANGES);
 		if (err)
 			goto done;
 		free((char *)pe->path);
@@ -4202,7 +4215,7 @@ write_commit_info(off_t **line_offsets, size_t *nlines

 	fputc('\n', outfile);
 	outoff++;
-	err = add_line_offset(line_offsets, nlines, outoff);
+	err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_NONE);
 done:
 	got_pathlist_free(&changed_paths);
 	free(id_str);
@@ -4210,8 +4223,8 @@ done:
 	free(refs_str);
 	got_object_commit_close(commit);
 	if (err) {
-		free(*line_offsets);
-		*line_offsets = NULL;
+		free(*lines);
+		*lines = NULL;
 		*nlines = 0;
 	}
 	return err;
@@ -4224,9 +4237,9 @@ create_diff(struct tog_diff_view_state *s)
 	FILE *f = NULL;
 	int obj_type;

-	free(s->line_offsets);
-	s->line_offsets = malloc(sizeof(off_t));
-	if (s->line_offsets == NULL)
+	free(s->lines);
+	s->lines = malloc(sizeof(*s->lines));
+	if (s->lines == NULL)
 		return got_error_from_errno("malloc");
 	s->nlines = 0;

@@ -4250,13 +4263,13 @@ create_diff(struct tog_diff_view_state *s)

 	switch (obj_type) {
 	case GOT_OBJ_TYPE_BLOB:
-		err = got_diff_objects_as_blobs(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_blobs(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2,
 		    s->label1, s->label2, tog_diff_algo, s->diff_context,
 		    s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
 		break;
 	case GOT_OBJ_TYPE_TREE:
-		err = got_diff_objects_as_trees(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_trees(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL, "", "",
 		    tog_diff_algo, s->diff_context, s->ignore_whitespace,
 		    s->force_text_diff, s->repo, s->f);
@@ -4273,17 +4286,17 @@ create_diff(struct tog_diff_view_state *s)
 		refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
 		/* Show commit info if we're diffing to a parent/root commit. */
 		if (s->id1 == NULL) {
-			err = write_commit_info(&s->line_offsets, &s->nlines,
-			    s->id2, refs, s->repo, s->f);
+			err = write_commit_info(&s->lines, &s->nlines, s->id2,
+			    refs, s->repo, s->f);
 			if (err)
 				goto done;
 		} else {
 			parent_ids = got_object_commit_get_parent_ids(commit2);
 			STAILQ_FOREACH(pid, parent_ids, entry) {
 				if (got_object_id_cmp(s->id1, &pid->id) == 0) {
-					err = write_commit_info(
-					    &s->line_offsets, &s->nlines,
-					    s->id2, refs, s->repo, s->f);
+					err = write_commit_info(&s->lines,
+					    &s->nlines, s->id2, refs, s->repo,
+					    s->f);
 					if (err)
 						goto done;
 					break;
@@ -4292,7 +4305,7 @@ create_diff(struct tog_diff_view_state *s)
 		}
 		got_object_commit_close(commit2);

-		err = got_diff_objects_as_commits(&s->line_offsets, &s->nlines,
+		err = got_diff_objects_as_commits(&s->lines, &s->nlines,
 		    s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL,
 		    tog_diff_algo, s->diff_context, s->ignore_whitespace,
 		    s->force_text_diff, s->repo, s->f);
@@ -4302,8 +4315,6 @@ 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");
@@ -4365,7 +4376,7 @@ search_next_diff_view(struct tog_view *view)
 				lineno = s->nlines;
 		}

-		offset = s->line_offsets[lineno - 1];
+		offset = s->lines[lineno - 1].offset;
 		if (fseeko(s->f, offset, SEEK_SET) != 0) {
 			free(line);
 			return got_error_from_errno("fseeko");
@@ -4425,9 +4436,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_colors(&s->colors);
-	free(s->line_offsets);
-	s->line_offsets = NULL;
+	free(s->lines);
+	s->lines = NULL;
 	s->nlines = 0;
 	return err;
 }
@@ -4511,43 +4521,25 @@ open_diff_view(struct tog_view *view, struct got_objec
 	s->parent_view = parent_view;
 	s->repo = repo;

-	STAILQ_INIT(&s->colors);
 	if (has_colors() && getenv("TOG_COLORS") != NULL) {
-		err = add_color(&s->colors,
-		    "^-", TOG_COLOR_DIFF_MINUS,
-		    get_color_value("TOG_COLOR_DIFF_MINUS"));
-		if (err)
-			goto done;
-		err = add_color(&s->colors, "^\\+",
-		    TOG_COLOR_DIFF_PLUS,
-		    get_color_value("TOG_COLOR_DIFF_PLUS"));
-		if (err)
-			goto done;
-		err = add_color(&s->colors,
-		    "^@@", TOG_COLOR_DIFF_CHUNK_HEADER,
-		    get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"));
-		if (err)
-			goto done;
-
-		err = add_color(&s->colors,
-		    "^(commit [0-9a-f]|parent [0-9]|"
-		    "(blob|file|tree|commit) [-+] |"
-		    "[MDmA]  [^ ])", TOG_COLOR_DIFF_META,
-		    get_color_value("TOG_COLOR_DIFF_META"));
-		if (err)
-			goto done;
-
-		err = add_color(&s->colors,
-		    "^(from|via): ", TOG_COLOR_AUTHOR,
-		    get_color_value("TOG_COLOR_AUTHOR"));
-		if (err)
-			goto done;
-
-		err = add_color(&s->colors,
-		    "^date: ", TOG_COLOR_DATE,
-		    get_color_value("TOG_COLOR_DATE"));
-		if (err)
-			goto done;
+		init_pair(GOT_DIFF_LINE_MINUS,
+		    get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
+		init_pair(GOT_DIFF_LINE_PLUS,
+		    get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
+		init_pair(GOT_DIFF_LINE_HUNK,
+		    get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
+		init_pair(GOT_DIFF_LINE_META,
+		    get_color_value("TOG_COLOR_DIFF_META"), -1);
+		init_pair(GOT_DIFF_LINE_CHANGES,
+		    get_color_value("TOG_COLOR_DIFF_META"), -1);
+		init_pair(GOT_DIFF_LINE_BLOB_MIN,
+		    get_color_value("TOG_COLOR_DIFF_META"), -1);
+		init_pair(GOT_DIFF_LINE_BLOB_PLUS,
+		    get_color_value("TOG_COLOR_DIFF_META"), -1);
+		init_pair(GOT_DIFF_LINE_AUTHOR,
+		    get_color_value("TOG_COLOR_AUTHOR"), -1);
+		init_pair(GOT_DIFF_LINE_DATE,
+		    get_color_value("TOG_COLOR_DATE"), -1);
 	}

 	if (parent_view && parent_view->type == TOG_VIEW_LOG &&
@@ -4643,6 +4635,36 @@ reset_diff_view(struct tog_view *view)
 	return create_diff(s);
 }

+static void
+diff_prev_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
+{
+	int i = s->first_displayed_line - 1;
+
+	while (s->lines[i].type != type) {
+		if (i == 0)
+			i = s->nlines - 1;
+		--i;
+	}
+
+	s->selected_line = 1;
+	s->first_displayed_line = i;
+}
+
+static void
+diff_next_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
+{
+	int i = s->first_displayed_line + 1;
+
+	while (s->lines[i].type != type) {
+		if (i == s->nlines - 1)
+			i = 0;
+		++i;
+	}
+
+	s->selected_line = 1;
+	s->first_displayed_line = i;
+}
+
 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 *,
@@ -4660,6 +4682,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;
@@ -4760,6 +4784,18 @@ input_diff_view(struct tog_view **new_view, struct tog
 		}
 		free(line);
 		break;
+	case '(':
+		diff_prev_index(s, GOT_DIFF_LINE_BLOB_MIN);
+		break;
+	case ')':
+		diff_next_index(s, GOT_DIFF_LINE_BLOB_MIN);
+		break;
+	case '{':
+		diff_prev_index(s, GOT_DIFF_LINE_HUNK);
+		break;
+	case '}':
+		diff_next_index(s, GOT_DIFF_LINE_HUNK);
+		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