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

From:
Mark Jamsek <mark@jamsek.com>
Subject:
Re: tog: nG key map like vi(1) and less(1)
To:
Christian Weisgerber <naddy@mips.inka.de>
Cc:
gameoftrees@openbsd.org
Date:
Sun, 17 Jul 2022 00:54:07 +1000

Download raw body.

Thread
On 22-07-14 03:24pm, Christian Weisgerber wrote:
> Mark Jamsek:
> 
> > I didn't think it made sense to add this to the log, tree, and ref
> > views; but if you think we should make it global, I'm happy to expand
> > it.
> 
> As of yesterday, the FreeBSD src repository had 3802 references.
> I think nG would be helpful for navigating those in the ref view.
> 
> The directory regress/lib/libcrypto/x509/bettertls/certificates/
> in the OpenBSD src repository holds about 11,000 files.  Again, I
> think nG could be helpful for navigating those in the tree view.
> Or, you know, just the 718 in share/man/man4.  (Currently, the tree
> view doesn't show the number of the selected item.  That should be
> added separately.)
> 
> A few weeks ago I was digging through old BSD history via the Git
> conversion of the Berkeley CSRG's repository, and although people
> will suggest the use of / search, I think nG could be helpful in
> the log view, too, when dealing with repositories with 100,000+
> commits.  For instance, it lends itself to a binary search to locate
> commits around an approximate date.
> 
> So, yes, I'd love for this feature to be available in all views.

ok, colour me convinced! And having just now tested it in the different
views, I have to admit it makes a lot more sense to have this available
in all views than my initial take.

> > naddy suggested a while back that this makes the g key map somewhat
> > redundant as 1G satisfies this use case, so we could be more like
> > vi(1) and remove g; however, less(1) provides g so there's precedent for
> > both.
> 
> I'm fine with keeping g, however, in that case I suggest that g
> should also take an optional line number, as it does in less(1).
> So g and G would be the same, except for the default action if no
> numeric prefix is provided.

Below diff adds n{G,g} to all views, falling back to default G/g when no
count prefix is entered. Tree index is also added in this diff but, as
naddy suggests, will be committed separately if ok. It makes testing the
implementation easier.

The thing I'm not too sure about, we center line n in the middle of the
page for the blame and diff views. As Stefan suggested, this makes the
most sense as it provides more context. However, the line index in the
diff header is in the form "i/N" such that i = first_displayed_line and
N = nlines in the file. Therefore, when using nG, the view is centered
on n but the index has no reference to n. Rather, i is
n - 1/2(view->nlines) [excluding header+border] except where
n < 1/2(view->nlines) when i is 1.  For example, in an 80x24 terminal,
99G will display an index of 89/N.  Whether this is a problem or not,
I'm not sure.

diff refs/heads/main refs/heads/dev/gline
commit - 86fbc0d4baa2c117d3bf5c65262d1857de548062
commit + 7480f8f00379f00fce40f8a7efbbd1c74e2e44ca
blob - 4ec26b8962b911a5c478faf7aebc3d0686931d6e
blob + 9f74828a77760dfa550b3531766dc6bc08694121
--- tog/tog.1
+++ tog/tog.1
@@ -93,6 +93,10 @@ N increments (default: 1).
 .It Cm +
 When in a split-screen view, increase the size of the focussed split
 N increments (default: 1).
+.It Cm G
+Go to line N in the view (default: last line).
+.It Cm g
+Go to line N in the view (default: first line).
 .El
 .Pp
 Global options must precede the command name, and are as follows:
blob - 97ac0690a9232815bb10bc4a237246e2ec53e069
blob + b8db0e3a0b50e5293deae307bc2f5f91bd7d620e
--- tog/tog.c
+++ tog/tog.c
@@ -516,6 +516,7 @@ struct tog_view {
 	int maxx, x; /* max column and current start column */
 	int lines, cols; /* copies of LINES and COLS */
 	int nscrolled, offset; /* lines scrolled and hsplit line offset */
+	int gline; /* navigate to this nG line */
 	int ch, count; /* current keymap and count prefix */
 	int resize; /* set when in a resize event */
 	int focussed; /* Only set on one parent or child view at a time. */
@@ -1224,7 +1225,7 @@ get_compound_key(struct tog_view *view, int c)
 		v = view->parent;

 	view->count = 0;
-	cbreak();  /* block for input */
+	nodelay(view->window, FALSE);  /* block for input */
 	wmove(v->window, v->nlines - 1, 0);
 	wclrtoeol(v->window);
 	waddch(v->window, ':');
@@ -1246,6 +1247,12 @@ get_compound_key(struct tog_view *view, int c)
 			n = n * 10 + (c - '0');
 	} while (((c = wgetch(view->window))) >= '0' && c <= '9' && c != ERR);

+	if (c == 'G' || c == 'g') {	/* nG key map */
+		view->gline = n;
+		n = 0;
+		c = 0;
+	}
+
 	/* Massage excessive or inapplicable values at the input handler. */
 	view->count = n;

@@ -1283,15 +1290,14 @@ view_input(struct tog_view **new, int *done, struct to
 		return NULL;
 	}

-	nodelay(view->window, FALSE);
+	cbreak();
+	nodelay(view->window, TRUE);
 	/* Allow threads to make progress while we are waiting for input. */
 	errcode = pthread_mutex_unlock(&tog_mutex);
 	if (errcode)
 		return got_error_set_errno(errcode, "pthread_mutex_unlock");
 	/* If we have an unfinished count, let C-g or backspace abort. */
 	if (view->count && --view->count) {
-		cbreak();
-		nodelay(view->window, TRUE);
 		ch = wgetch(view->window);
 		if (ch == CTRL('g') || ch == KEY_BACKSPACE)
 			view->count = 0;
@@ -1305,7 +1311,6 @@ view_input(struct tog_view **new, int *done, struct to
 	errcode = pthread_mutex_lock(&tog_mutex);
 	if (errcode)
 		return got_error_set_errno(errcode, "pthread_mutex_lock");
-	nodelay(view->window, TRUE);

 	if (tog_sigwinch_received || tog_sigcont_received) {
 		tog_resizeterm();
@@ -2431,7 +2436,8 @@ log_scroll_down(struct tog_view *view, int maxscroll)
 		/*
 		 * Ask the log thread for required amount of commits.
 		 */
-		s->thread_args.commits_needed += maxscroll;
+		s->thread_args.commits_needed +=
+		    ncommits_needed - s->commits.ncommits;
 		err = trigger_log_thread(view, 1);
 		if (err)
 			return err;
@@ -3183,6 +3189,42 @@ view_init_hsplit(struct tog_view *view, int y)
 }

 static const struct got_error *
+log_goto_line(struct tog_view *view, int nlines)
+{
+	const struct got_error		*err = NULL;
+	struct tog_log_view_state	*s = &view->state.log;
+	int				 g, idx = s->selected_entry->idx;
+
+	g = view->gline;
+	view->gline = 0;
+
+	if (g >= s->first_displayed_entry->idx + 1 &&
+	    g <= s->last_displayed_entry->idx + 1 &&
+	    g - s->first_displayed_entry->idx - 1 < nlines) {
+		s->selected = g - s->first_displayed_entry->idx - 1;
+		select_commit(s);
+		return NULL;
+	}
+
+	if (idx + 1 < g) {
+		err = log_move_cursor_down(view, g - idx - 1);
+		if (!err && g > s->selected_entry->idx + 1)
+			err = log_move_cursor_down(view,
+			    g - s->first_displayed_entry->idx - 1);
+		if (err)
+			return err;
+	} else if (idx + 1 > g)
+		log_move_cursor_up(view, idx - g + 1, 0);
+
+	if (g < nlines && s->first_displayed_entry->idx == 0)
+		s->selected = g - 1;
+
+	select_commit(s);
+	return NULL;
+
+}
+
+static const struct got_error *
 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
 {
 	const struct got_error *err = NULL;
@@ -3206,6 +3248,9 @@ input_log_view(struct tog_view **new_view, struct tog_
 	if (view_is_hsplit_top(view))
 		--eos;  /* border */

+	if (view->gline)
+		return log_goto_line(view, eos);
+
 	switch (ch) {
 	case 'q':
 		s->quit = 1;
@@ -3859,13 +3904,55 @@ done:
 	return err;
 }

+static int
+gotoline(struct tog_view *view, int *lineno, int *nprinted)
+{
+	FILE	*f = NULL;
+	int	*eof, *first, *selected;
+
+	if (view->type == TOG_VIEW_DIFF) {
+		struct tog_diff_view_state *s = &view->state.diff;
+
+		first = &s->first_displayed_line;
+		selected = first;
+		eof = &s->eof;
+		f = s->f;
+	} else if (view->type == TOG_VIEW_BLAME) {
+		struct tog_blame_view_state *s = &view->state.blame;
+
+		first = &s->first_displayed_line;
+		selected = &s->selected_line;
+		eof = &s->eof;
+		f = s->blame.f;
+	} else
+		return 0;
+
+	/* Center gline in the middle of the page like vi(1). */
+	if (*lineno < view->gline - (view->nlines - 3) / 2)
+		return 0;
+	if (*first != 1 && (*lineno > view->gline - (view->nlines - 3) / 2)) {
+		rewind(f);
+		*eof = 0;
+		*first = 1;
+		*lineno = 0;
+		*nprinted = 0;
+		return 0;
+	}
+
+	*selected = view->gline <= (view->nlines - 3) / 2 ?
+		view->gline : (view->nlines - 3) / 2 + 1;
+	view->gline = 0;
+
+	return 1;
+}
+
 static const struct got_error *
 draw_file(struct tog_view *view, const char *header)
 {
 	struct tog_diff_view_state *s = &view->state.diff;
 	regmatch_t *regmatch = &view->regmatch;
 	const struct got_error *err;
-	int nprinted = 0;
+	int lineno, nprinted = 0;
 	char *line;
 	size_t linesize = 0;
 	ssize_t linelen;
@@ -3876,12 +3963,16 @@ draw_file(struct tog_view *view, const char *header)
 	int nlines = s->nlines;
 	off_t line_offset;

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

 	werase(view->window);

+	if (view->gline > s->nlines - 1)
+		view->gline = s->nlines - 1;
+
 	if (header) {
 		if (asprintf(&line, "[%d/%d] %s",
 		    s->first_displayed_line - 1 + s->selected_line, nlines,
@@ -3922,6 +4013,10 @@ draw_file(struct tog_view *view, const char *header)
 			return got_ferror(s->f, GOT_ERR_IO);
 		}

+		if (++lineno < s->first_displayed_line)
+			continue;
+		if (view->gline && !gotoline(view, &lineno, &nprinted))
+			continue;
 		/* Set view->maxx based on full line length. */
 		err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
 		    view->x ? 1 : 0);
@@ -3962,7 +4057,8 @@ draw_file(struct tog_view *view, const char *header)
 			    COLOR_PAIR(tc->colorpair), NULL);
 		if (width <= view->ncols - 1)
 			waddch(view->window, '\n');
-		nprinted++;
+		if (++nprinted == 1)
+			s->first_displayed_line = lineno;
 	}
 	free(line);
 	if (nprinted >= 1)
@@ -5077,6 +5173,9 @@ draw_blame(struct tog_view *view)
 	if (width < view->ncols - 1)
 		waddch(view->window, '\n');

+	if (view->gline > blame->nlines)
+		view->gline = blame->nlines;
+
 	if (asprintf(&line, "[%d/%d] %s%s",
 	    s->first_displayed_line - 1 + s->selected_line, blame->nlines,
 	    s->blame_complete ? "" : "annotating... ", s->path) == -1) {
@@ -5109,6 +5208,8 @@ draw_blame(struct tog_view *view)
 		}
 		if (++lineno < s->first_displayed_line)
 			continue;
+		if (view->gline && !gotoline(view, &lineno, &nprinted))
+			continue;

 		/* Set view->maxx based on full line length. */
 		err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 9, 1);
@@ -6164,7 +6265,7 @@ draw_tree_entries(struct tog_view *view, const char *p
 	struct got_tree_entry *te;
 	wchar_t *wline;
 	struct tog_color *tc;
-	int width, n, i, nentries;
+	int width, n, nentries, i = 1;
 	int limit = view->nlines;

 	s->ndisplayed = 0;
@@ -6194,6 +6295,15 @@ draw_tree_entries(struct tog_view *view, const char *p
 		wstandend(view->window);
 	free(wline);
 	wline = NULL;
+
+	if (s->selected_entry) {
+		i = got_tree_entry_get_index(s->selected_entry);
+		i += s->tree == s->root ? 1 : 2;  /* account for ".." entry */
+	}
+	nentries = got_object_tree_get_nentries(s->tree);
+	wprintw(view->window, " [%d/%d]", i,
+	    nentries + (s->tree == s->root ? 0 : 1));  /* ".." in !root tree */
+
 	if (width < view->ncols - 1)
 		waddch(view->window, '\n');
 	if (--limit <= 0)
@@ -6232,7 +6342,6 @@ draw_tree_entries(struct tog_view *view, const char *p
 		te = s->first_displayed_entry;
 	}

-	nentries = got_object_tree_get_nentries(s->tree);
 	for (i = got_tree_entry_get_index(te); i < nentries; i++) {
 		char *line = NULL, *id_str = NULL, *link_target = NULL;
 		const char *modestr = "";
@@ -6352,8 +6461,10 @@ tree_scroll_down(struct tog_view *view, int maxscroll)

 	last = s->last_displayed_entry;
 	while (next && n++ < maxscroll) {
-		if (last)
+		if (last) {
+			s->last_displayed_entry = last;
 			last = got_tree_entry_get_next(s->tree, last);
+		}
 		if (last || (view->mode == TOG_VIEW_SPLIT_HRZN && next)) {
 			s->first_displayed_entry = next;
 			next = got_tree_entry_get_next(s->tree, next);
@@ -6694,6 +6805,60 @@ show_tree_view(struct tog_view *view)
 }

 static const struct got_error *
+tree_goto_line(struct tog_view *view, int nlines)
+{
+	const struct got_error		 *err = NULL;
+	struct tog_tree_view_state	 *s = &view->state.tree;
+	struct got_tree_entry		**fte, **lte, **ste;
+	int				  g, last, first = 1, i = 1;
+
+	g = view->gline;
+	view->gline = 0;
+
+	fte = &s->first_displayed_entry;
+	lte = &s->last_displayed_entry;
+	ste = &s->selected_entry;
+
+	if (*fte != NULL) {
+		first = got_tree_entry_get_index(*fte);
+		first += (s->tree == s->root ? 1 : 2);  /* account for ".." */
+	}
+	last = got_tree_entry_get_index(*lte);
+	last += (s->tree == s->root ? 1 : 2);
+
+	/* gline is on the current page */
+	if (g >= first && g <= last && g - first < nlines) {
+		s->selected = g - first;
+		return NULL;
+	}
+
+	if (*ste != NULL) {
+		i = got_tree_entry_get_index(*ste);
+		i += (s->tree == s->root ? 1 : 2);
+	}
+
+	if (i < g) {
+		err = tree_scroll_down(view, g - i);
+		if (err)
+			return err;
+		if (got_tree_entry_get_index(*lte) >=
+		    got_object_tree_get_nentries(s->tree) - 1 && first +
+		    s->selected < g && s->selected < s->ndisplayed - 1) {
+			first = got_tree_entry_get_index(*fte);
+			first += (s->tree == s->root ? 1 : 2);
+			s->selected = g - first;
+		}
+	} else if (i > g)
+		tree_scroll_up(s, i - g);
+
+	if (g < nlines && (*fte == NULL ||
+	    (s->tree == s->root && !got_tree_entry_get_index(*fte))))
+		s->selected = g - 1;
+
+	return NULL;
+}
+
+static const struct got_error *
 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
 {
 	const struct got_error *err = NULL;
@@ -6702,6 +6867,16 @@ input_tree_view(struct tog_view **new_view, struct tog
 	struct got_tree_entry *te;
 	int begin_y = 0, begin_x = 0, n, nscroll = view->nlines - 3;

+	if (view->gline) {
+		if (view->gline == 1)
+			ch = 'g';
+		else if (view->gline > got_object_tree_get_nentries(s->tree))
+			ch = 'G';
+		else
+			return tree_goto_line(view, nscroll);
+		view->gline = 0;
+	}
+
 	switch (ch) {
 	case 'i':
 		s->show_ids = !s->show_ids;
@@ -7352,8 +7527,10 @@ ref_scroll_down(struct tog_view *view, int maxscroll)

 	last = s->last_displayed_entry;
 	while (next && n++ < maxscroll) {
-		if (last)
+		if (last) {
+			s->last_displayed_entry = last;
 			last = TAILQ_NEXT(last, entry);
+		}
 		if (last || (view->mode == TOG_VIEW_SPLIT_HRZN)) {
 			s->first_displayed_entry = next;
 			next = TAILQ_NEXT(next, entry);
@@ -7634,7 +7811,43 @@ done:
 	free(commit_id);
 	return err;
 }
+
 static const struct got_error *
+ref_goto_line(struct tog_view *view, int nlines)
+{
+	const struct got_error		*err = NULL;
+	struct tog_ref_view_state	*s = &view->state.ref;
+	int				 g, idx = s->selected_entry->idx;
+
+	g = view->gline;
+	view->gline = 0;
+
+	if (g >= s->first_displayed_entry->idx + 1 &&
+	    g <= s->last_displayed_entry->idx + 1 &&
+	    g - s->first_displayed_entry->idx - 1 < nlines) {
+		s->selected = g - s->first_displayed_entry->idx - 1;
+		return NULL;
+	}
+
+	if (idx + 1 < g) {
+		err = ref_scroll_down(view, g - idx - 1);
+		if (err)
+			return err;
+		if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL &&
+		    s->first_displayed_entry->idx + s->selected < g &&
+		    s->selected < s->ndisplayed - 1)
+			s->selected = g - s->first_displayed_entry->idx - 1;
+	} else if (idx + 1 > g)
+		ref_scroll_up(s, idx - g + 1);
+
+	if (g < nlines && s->first_displayed_entry->idx == 0)
+		s->selected = g - 1;
+
+	return NULL;
+
+}
+
+static const struct got_error *
 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
 {
 	const struct got_error *err = NULL;
@@ -7643,6 +7856,16 @@ input_ref_view(struct tog_view **new_view, struct tog_
 	struct tog_reflist_entry *re;
 	int begin_y = 0, begin_x = 0, n, nscroll = view->nlines - 1;

+	if (view->gline) {
+		if (view->gline >= s->nrefs)
+			ch = 'G';
+		else if (view->gline == 1)
+			ch = 'g';
+		else
+			return ref_goto_line(view, nscroll);
+		view->gline = 0;
+	}
+
 	switch (ch) {
 	case 'i':
 		s->show_ids = !s->show_ids;

-- 
Mark Jamsek <fnc.bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80  E6E2 2930 DC66 86EE CF68