"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:
gameoftrees@openbsd.org
Date:
Thu, 14 Jul 2022 22:50:11 +1000

Download raw body.

Thread
On 22-07-14 10:49am, Stefan Sperling wrote:
> On Thu, Jul 14, 2022 at 01:37:07AM +1000, Mark Jamsek wrote:
> > This implements the nG key map--functionally the same as vi(1) and
> > less(1)--to navigate to line n in the diff and blame views.
> > 
> > As the diff view is a pager, we make nG behave like less(1) such that
> > line n will be the first line on the page.
> > 
> > And because the blame view is more like an editor with navigation, we
> > make nG behave like vi(1) such that line n is centered in the middle of
> > the page--except for when n < view->nlines / 2. In this case, line
> > n will be the nth line on the page (excluding the header).
> > 
> > The alternative is to make them both either place line n at the top
> > [like less(1)] or the middle [like vi(1)] of the page. If we go that
> > route, I'm partial to centering the line.
> 
> I would say centering makes sense for both diff and blame views.
> 
> Even though the diff view acts a bit like a pager, I don't see how you
> could tell in advance which line of a diff to move to in order to find
> something. There won't be any external index that would give you a
> specific line number to move to. People will instead use search to
> match a diff hunk header or the name of a function or struct.
> 
> However, since the diff view displays the total number of lines at the
> top, nG could be used for operations that jump to 30% or 50% of the diff.
> In which case centering on the target line makes more sense since that
> will show context before and after the target line. Unlike prose text,
> diffs are not always read linearly from top to bottom. At least I often
> find myself jumping around in diffs to make sense of things.

All good points, and I'm in agreement.

Just trying this now, and centering in the diff view feels a lot better.
The only thing I'm not too sure about--both in terms of whether it's
a problem and how to go about solving it if we decide it's a problem--is
the line index in the header is in the form "i/N" such that
i = first_displayed_line and N = nlines. Therefore, when using nG, the
view is centered on n but the index has no reference to n. Rather, i is
n - 1/2N except where n < 1/2N when i is 1. For example, in an 80x24
terminal, 99G will display an index of 89/N

> > 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.
> 
> nG might be interesting for log: "jump the to Nth commit". But it's
> not very important.

This is true, and shouldn't be a problem to add.

> > 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. And given this only adds nG to two views, we probably want to keep
> > g unless expanding nG to all views.
> 
> I'm also in favour of keeping 'g' since I use it :)

Me too :)

Although I always double-tap 'g' out of habit from vim!

Updated diff centers line n in both diff and blame views.

diff refs/heads/main refs/heads/dev/gline
commit - 84bc347c349f09dc692e8d760ba9af3d21b0f5af
commit + f6b15382e2a2d2332ba1bc3a788ec570d4ca308e
blob - 4ec26b8962b911a5c478faf7aebc3d0686931d6e
blob + 3dee27c9da2e151c418d27f26dbfec60d1f27f47
--- tog/tog.1
+++ tog/tog.1
@@ -286,8 +286,10 @@ Scroll down N half pages (default: 1).
 Scroll up N half pages (default: 1).
 .It Cm Home, g
 Scroll to the top of the view.
-.It Cm End, G
+.It Cm End
 Scroll to the bottom of the view.
+.It Cm G
+Go to line N in the diff (default: last line).
 .It Cm \&[
 Reduce diff context by N lines (default: 1).
 .It Cm \&]
@@ -384,8 +386,10 @@ Move the selection cursor down N half pages (default:
 Move the selection cursor up N half pages (default: 1).
 .It Cm Home, g
 Move the selection cursor to the first line of the file.
-.It Cm End, G
+.It Cm End
 Move the selection cursor to the last line of the file.
+.It Cm G
+Go to line N in the file (default: last line).
 .It Cm Enter
 Open a
 .Cm diff
blob - 41a38da73b3c99dd62ddda5ae9e19708dea2b862
blob + 23b4d84494e81d74a91568332eb2a1468d41b91d
--- 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,13 @@ 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 ((view->type == TOG_VIEW_BLAME || view->type == TOG_VIEW_DIFF) &&
+	    c == 'G') {
+		view->gline = n;  /* nG key map */
+		n = 0;
+		c = 0;
+	}
+
 	/* Massage excessive or inapplicable values at the input handler. */
 	view->count = n;

@@ -1283,15 +1291,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 +1312,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();
@@ -3859,13 +3865,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 +3924,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 +3974,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 +4018,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 +5134,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 +5169,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);

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