Commit 6c4c42e07c3ebc32f3ea82173f6936b9f522733a

Stefan Sperling 2019-06-24T22:28:11

implement search for 'tog blame'

diff --git a/include/got_object.h b/include/got_object.h
index 6baad61..4f5a750 100644
--- a/include/got_object.h
+++ b/include/got_object.h
@@ -202,11 +202,12 @@ const struct got_error *got_object_blob_read_block(size_t *,
 /*
  * Read the entire content of a blob and write it to the specified file.
  * Flush and rewind the file as well. Indicate the amount of bytes
- * written in the size_t output argument, and the number of lines in
- * the file in int argument (NULL can be passed for either output argument).
+ * written in the size_t output argument, and the number of lines in the
+ * file in the int argument, and line offsets in the the off_t argument
+ * (NULL can be passed for any output argument).
  */
 const struct got_error *got_object_blob_dump_to_file(size_t *, int *,
-    FILE *, struct got_blob_object *);
+    off_t **, FILE *, struct got_blob_object *);
 
 /*
  * Attempt to open a tag object in a repository.
diff --git a/lib/blame.c b/lib/blame.c
index b4f8b83..eda1af0 100644
--- a/lib/blame.c
+++ b/lib/blame.c
@@ -40,6 +40,7 @@
 struct got_blame_line {
 	int annotated;
 	struct got_object_id id;
+	off_t offset;
 };
 
 struct got_blame_diff_offsets {
@@ -52,9 +53,11 @@ SLIST_HEAD(got_blame_diff_offsets_list, got_blame_diff_offsets);
 
 struct got_blame {
 	FILE *f;
+	size_t filesize;
 	int nlines;
 	int nannotated;
 	struct got_blame_line *lines; /* one per line */
+	off_t *line_offsets;		/* one per line */
 	int ncommits;
 	struct got_blame_diff_offsets_list diff_offsets_list;
 };
@@ -329,8 +332,8 @@ blame_open(struct got_blame **blamep, const char *path,
 		err = got_error_from_errno("got_opentemp");
 		goto done;
 	}
-	err = got_object_blob_dump_to_file(NULL, &blame->nlines, blame->f,
-	    blob);
+	err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
+	   &blame->line_offsets, blame->f, blob);
 	if (err)
 		goto done;
 
diff --git a/lib/diff.c b/lib/diff.c
index 4da542a..449f4a6 100644
--- a/lib/diff.c
+++ b/lib/diff.c
@@ -71,7 +71,8 @@ diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
 	size1 = 0;
 	if (blob1) {
 		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
-		err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
+		err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
+		    blob1);
 		if (err)
 			goto done;
 	} else
@@ -80,7 +81,8 @@ diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
 	size2 = 0;
 	if (blob2) {
 		idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
-		err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2);
+		err = got_object_blob_dump_to_file(&size2, NULL, NULL, f2,
+		    blob2);
 		if (err)
 			goto done;
 	} else
@@ -157,7 +159,8 @@ got_diff_blob_file(struct got_blob_object *blob1, FILE *f2, size_t size2,
 		if (f1 == NULL)
 			return got_error_from_errno("got_opentemp");
 		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
-		err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
+		err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
+		    blob1);
 		if (err)
 			goto done;
 	} else {
diff --git a/lib/object.c b/lib/object.c
index 1c9eb57..1a425fa 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -1136,13 +1136,17 @@ got_object_blob_read_block(size_t *outlenp, struct got_blob_object *blob)
 
 const struct got_error *
 got_object_blob_dump_to_file(size_t *total_len, int *nlines,
-    FILE *outfile, struct got_blob_object *blob)
+    off_t **line_offsets, FILE *outfile, struct got_blob_object *blob)
 {
 	const struct got_error *err = NULL;
 	size_t n, len, hdrlen;
 	const uint8_t *buf;
 	int i;
+	size_t noffsets = 0;
+	off_t off = 0;
 
+	if (line_offsets)
+		*line_offsets = NULL;
 	if (total_len)
 		*total_len = 0;
 	if (nlines)
@@ -1155,15 +1159,31 @@ got_object_blob_dump_to_file(size_t *total_len, int *nlines,
 			return err;
 		if (len == 0)
 			break;
-		if (total_len)
-			*total_len += len;
 		buf = got_object_blob_get_read_buf(blob);
-		if (nlines) {
-			for (i = 0; i < len; i++) {
-				if (buf[i] == '\n')
-					(*nlines)++;
+		for (i = 0; i < len; i++) {
+			if (buf[i] != '\n')
+				continue;
+			if (nlines)
+				(*nlines)++;
+			if (line_offsets && nlines && noffsets < *nlines) {
+				off_t *o = recallocarray(*line_offsets,
+				    noffsets, *nlines, sizeof(**line_offsets));
+				if (o == NULL) {
+					free(*line_offsets);
+					*line_offsets = NULL;
+					return got_error_from_errno(
+					    "recallocarray");
+				}
+				*line_offsets = o;
+				noffsets = *nlines;
+			}
+			if (line_offsets && nlines && total_len) {
+				(*line_offsets)[*nlines - 1] = off;
+				off = *total_len + i + 1;
 			}
 		}
+		if (total_len)
+			*total_len += len;
 		/* Skip blob object header first time around. */
 		n = fwrite(buf + hdrlen, 1, len - hdrlen, outfile);
 		if (n != len - hdrlen)
diff --git a/lib/worktree.c b/lib/worktree.c
index b0e00b1..4fbd3aa 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -761,7 +761,8 @@ merge_blob(int *local_changes_subsumed, struct got_worktree *worktree,
 	err = got_opentemp_named(&blob_deriv_path, &f_deriv, base_path);
 	if (err)
 		goto done;
-	err = got_object_blob_dump_to_file(NULL, NULL, f_deriv, blob_deriv);
+	err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_deriv,
+	    blob_deriv);
 	if (err)
 		goto done;
 
@@ -776,7 +777,7 @@ merge_blob(int *local_changes_subsumed, struct got_worktree *worktree,
 	if (err)
 		goto done;
 	if (blob_orig) {
-		err = got_object_blob_dump_to_file(NULL, NULL, f_orig,
+		err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_orig,
 		    blob_orig);
 		if (err)
 			goto done;
diff --git a/tog/tog.1 b/tog/tog.1
index 5f0b98d..55b3318 100644
--- a/tog/tog.1
+++ b/tog/tog.1
@@ -193,6 +193,15 @@ currently selected line's commit.
 Reload the
 .Cm blame
 view with the previously blamed commit.
+.It Cm /
+Prompt for a search pattern and start searching for matching line.
+The search pattern is an extended regular expression.
+Regular expression syntax is documented in
+.Xr re_format 7 .
+.It Cm n
+Find the next line which matches the current search pattern.
+.It Cm N
+Find the previous line which matches the current search pattern.
 .El
 .Pp
 The options for
diff --git a/tog/tog.c b/tog/tog.c
index 5aed76e..1795147 100644
--- a/tog/tog.c
+++ b/tog/tog.c
@@ -178,6 +178,7 @@ struct tog_blame {
 	size_t filesize;
 	struct tog_blame_line *lines;
 	int nlines;
+	off_t *line_offsets;
 	pthread_t thread;
 	struct tog_blame_thread_args thread_args;
 	struct tog_blame_cb_args cb_args;
@@ -198,6 +199,7 @@ struct tog_blame_view_state {
 	struct got_reflist_head *refs;
 	struct got_object_id *commit_id;
 	struct tog_blame blame;
+	int matched_line;
 };
 
 struct tog_parent_tree {
@@ -303,6 +305,8 @@ static const struct got_error *show_blame_view(struct tog_view *);
 static const struct got_error *input_blame_view(struct tog_view **,
     struct tog_view **, struct tog_view **, struct tog_view *, int);
 static const struct got_error *close_blame_view(struct tog_view *);
+static const struct got_error *search_start_blame_view(struct tog_view *);
+static const struct got_error *search_next_blame_view(struct tog_view *);
 
 static const struct got_error *open_tree_view(struct tog_view *,
     struct got_tree_object *, struct got_object_id *,
@@ -3122,7 +3126,7 @@ run_blame(struct tog_blame *blame, struct tog_view *view, int *blame_complete,
 		goto done;
 	}
 	err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
-	    blame->f, blob);
+	    &blame->line_offsets, blame->f, blob);
 	if (err)
 		goto done;
 
@@ -3191,6 +3195,8 @@ open_blame_view(struct tog_view *view, char *path,
 	view->show = show_blame_view;
 	view->input = input_blame_view;
 	view->close = close_blame_view;
+	view->search_start = search_start_blame_view;
+	view->search_next = search_next_blame_view;
 
 	return run_blame(&s->blame, view, &s->blame_complete,
 	    &s->first_displayed_line, &s->last_displayed_line,
@@ -3220,6 +3226,109 @@ close_blame_view(struct tog_view *view)
 }
 
 static const struct got_error *
+search_start_blame_view(struct tog_view *view)
+{
+	struct tog_blame_view_state *s = &view->state.blame;
+
+	s->matched_line = 0;
+	return NULL;
+}
+
+static int
+match_line(const char *line, regex_t *regex)
+{
+	regmatch_t regmatch;
+
+	return regexec(regex, line, 1, &regmatch, 0) == 0;
+}
+
+
+static const struct got_error *
+search_next_blame_view(struct tog_view *view)
+{
+	const struct got_error *err = NULL;
+	struct tog_blame_view_state *s = &view->state.blame;
+	int lineno;
+
+	if (!view->searching) {
+		view->search_next_done = 1;
+		return NULL;
+	}
+
+	if (s->matched_line) {
+		if (view->searching == TOG_SEARCH_FORWARD)
+			lineno = s->first_displayed_line - 1 + s->selected_line + 1;
+		else
+			lineno = s->first_displayed_line - 1 + s->selected_line - 1;
+	} else {
+		if (view->searching == TOG_SEARCH_FORWARD)
+			lineno = 1;
+		else
+			lineno = s->blame.nlines;
+	}
+
+	while (1) {
+		char *line = NULL;
+		off_t offset;
+		size_t len;
+
+		if (lineno <= 0 || lineno > s->blame.nlines) {
+			if (s->matched_line == 0) {
+				view->search_next_done = 1;
+				free(line);
+				break;
+			}
+			if (view->searching == TOG_SEARCH_FORWARD)
+				lineno = 1;
+			else
+				lineno = s->blame.nlines;
+		}
+
+		offset = s->blame.line_offsets[lineno - 1];
+		if (fseeko(s->blame.f, offset, SEEK_SET) != 0) {
+			free(line);
+			return got_error_from_errno("fseeko");
+		}
+		free(line);
+		line = parse_next_line(s->blame.f, &len);
+		if (line == NULL)
+			break;
+		if (match_line(line, &view->regex)) {
+			view->search_next_done = 1;
+			s->matched_line = lineno;
+			free(line);
+			break;
+		}
+		free(line);
+		line = NULL;
+		if (view->searching == TOG_SEARCH_FORWARD)
+			lineno++;
+		else
+			lineno--;
+	}
+
+	if (s->matched_line) {
+		int cur = s->first_displayed_line - 1 + s->selected_line;
+		while (cur < s->matched_line) {
+			err = input_blame_view(NULL, NULL, NULL, view, KEY_DOWN);
+			if (err)
+				return err;
+			cur++;
+		}
+		while (cur > s->matched_line) {
+			err = input_blame_view(NULL, NULL, NULL, view, KEY_UP);
+			if (err)
+				return err;
+			cur--;
+		}
+		s->first_displayed_line = s->matched_line;
+		s->selected_line = 1;
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
 show_blame_view(struct tog_view *view)
 {
 	const struct got_error *err = NULL;