"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:
Thu, 21 Jul 2022 17:13:20 +1000

Download raw body.

Thread
On 22-07-20 10:36pm, Christian Weisgerber wrote:
> Mark Jamsek:
> 
> > We might have found a reasonable solution: keep centering line n in the
> > nG case, but highlight line n till the next key press. This way, as stsp
> > suggested, we keep the leading and following context, and provide
> > a visual, albeit ephemeral, clue of the desired line.
> 
> Makes sense.
> I'm terribly old-fashioned and prefer if terminal highlighting
> limits itself to A_STANDOUT, although I readily admit that the
> hardware terminals where this mattered have been dead for decades.

We use A_STANDOUT (or A_REVERSE in the previous diff as I've had issues
with A_STANDOUT in tmux in the past, otherwise they both appear to do
the same thing?), but because the text is coloured, we get the coloured
highlight. Like we currently do with highlighted search terms in diff
views. To have plain white/black, we would have to not set the colour
attribute on gline.

I'm partial to the coloured highlight when TOG_COLORS is set, and have
left it like this; but if you feel strongly--or if anyone else also
prefers--that we make it monochromatic in both cases, I can change this.
Alternatively, it could be configurable and/or toggleable but that may
be overkill.

> nG works in all views. Yay!
> There is the cosmetic issue in the blame view that the line number
> in the header is only updated with a noticeable delay.

Fixed!

> The implementation seems weird in that it largely bypasses the
> existing g/G code instead of handling all cases in the same code.
> This...
> 
> > @@ -6712,6 +6894,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;
> > +	}
> > +
> 
> ... is particularly weird.  Couldn't tree_goto_line() be used for
> all cases?

At first I thought we'd either have to duplicate a bit of code or move
the existing G/g bits if we wanted to handle the base cases in
tree_goto_line() so I left them for the input handler. But it looks like
we don't have to.

Revised and rebased diff below reads a lot better imo. Thanks, naddy!

> I would have thought we'd end up with something like
> 
> 	n = view->count;
> 	...
> 	case 'g':
> 		if (n == 0)
> 			n = 1;
> 		/* FALLTHROUGH */
> 	case 'G':
> 		if (n == 0)
> 			n = ...;
> 		... do stuff (n) ...;
> 		break;
> 
> Okay, I'll stop with the backseat driving.

Please don't.

It makes me think more and ultimately write better code so I really do
appreciate and welcome all the backseat driving :)

diff refs/heads/main refs/heads/dev/gline
commit - c3821befbed0b67d1b48a5cfa3aaa2e022c58430
commit + 6aba0b5044ca3682899309aea1d763a451120360
blob - f73c13e4ee7be77816a5e17af55d718bc8289519
blob + cbfc070b6bf815b53aaa16c9410c12e0c54c511d
--- 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 - 4d998d35a4df6ddb4a64381049b0173d4e9f0344
blob + ee2f82e2ccc98d8c6ae5992cd32eb946d3b48ca2
--- tog/tog.c
+++ tog/tog.c
@@ -517,6 +517,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, hiline; /* navigate to and highlight this nG line */
 	int ch, count; /* current keymap and count prefix */
 	int resized; /* set when in a resize event */
 	int focussed; /* Only set on one parent or child view at a time. */
@@ -1235,6 +1236,7 @@ get_compound_key(struct tog_view *view, int c)

 	view->count = 0;
 	cbreak();  /* block for input */
+	nodelay(view->window, FALSE);
 	wmove(v->window, v->nlines - 1, 0);
 	wclrtoeol(v->window);
 	waddch(v->window, ':');
@@ -1256,6 +1258,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 = view->hiline = n;
+		n = 0;
+		c = 0;
+	}
+
 	/* Massage excessive or inapplicable values at the input handler. */
 	view->count = n;

@@ -1293,7 +1301,6 @@ view_input(struct tog_view **new, int *done, struct to
 		return NULL;
 	}

-	nodelay(view->window, FALSE);
 	/* Allow threads to make progress while we are waiting for input. */
 	errcode = pthread_mutex_unlock(&tog_mutex);
 	if (errcode)
@@ -1312,10 +1319,12 @@ view_input(struct tog_view **new, int *done, struct to
 		if (ch >= '1' && ch  <= '9')
 			view->ch = ch = get_compound_key(view, ch);
 	}
+	if (view->hiline && ch != ERR && ch != 0)
+		view->hiline = 0;  /* key pressed, clear line highlight */
+	nodelay(view->window, TRUE);
 	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();
@@ -2450,7 +2459,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;
@@ -3203,6 +3213,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;
@@ -3226,6 +3272,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;
@@ -3882,13 +3931,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;
@@ -3898,17 +3989,24 @@ draw_file(struct tog_view *view, const char *header)
 	int max_lines = view->nlines;
 	int nlines = s->nlines;
 	off_t line_offset;
+	attr_t attr;

+	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,
-		    header) == -1)
+		int ln = view->gline ? view->gline <= (view->nlines - 3) / 2 ?
+		    1 : view->gline - (view->nlines - 3) / 2 :
+		    lineno + s->selected_line;
+
+		if (asprintf(&line, "[%d/%d] %s", ln, nlines, header) == -1)
 			return got_error_from_errno("asprintf");
 		err = format_line(&wline, &width, NULL, line, 0, view->ncols,
 		    0, 0);
@@ -3945,6 +4043,14 @@ draw_file(struct tog_view *view, const char *header)
 			return got_ferror(s->f, GOT_ERR_IO);
 		}

+		attr = 0;
+		if (++lineno < s->first_displayed_line)
+			continue;
+		if (view->gline && !gotoline(view, &lineno, &nprinted))
+			continue;
+		if (lineno == view->hiline)
+			attr = A_BOLD | A_STANDOUT;
+
 		/* Set view->maxx based on full line length. */
 		err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
 		    view->x ? 1 : 0);
@@ -3958,8 +4064,9 @@ draw_file(struct tog_view *view, const char *header)

 		tc = match_color(&s->colors, line);
 		if (tc)
-			wattr_on(view->window,
-			    COLOR_PAIR(tc->colorpair), NULL);
+			attr |= COLOR_PAIR(tc->colorpair);
+		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,
@@ -3980,12 +4087,18 @@ 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 (width <= view->ncols - 1)
-			waddch(view->window, '\n');
-		nprinted++;
+		if (lineno == view->hiline) {
+			/* highlight full gline length */
+			while (width++ < view->ncols)
+				waddch(view->window, ' ');
+		} else {
+			if (width <= view->ncols - 1)
+				waddch(view->window, '\n');
+		}
+		if (attr)
+			wattroff(view->window, attr);
+		if (++nprinted == 1)
+			s->first_displayed_line = lineno;
 	}
 	free(line);
 	if (nprinted >= 1)
@@ -5102,7 +5215,10 @@ draw_blame(struct tog_view *view)
 	if (width < view->ncols - 1)
 		waddch(view->window, '\n');

-	if (asprintf(&line, "[%d/%d] %s%s",
+	if (view->gline > blame->nlines)
+		view->gline = blame->nlines;
+
+	if (asprintf(&line, "[%d/%d] %s%s", view->gline ? view->gline :
 	    s->first_displayed_line - 1 + s->selected_line, blame->nlines,
 	    s->blame_complete ? "" : "annotating... ", s->path) == -1) {
 		free(id_str);
@@ -5134,6 +5250,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);
@@ -6250,7 +6368,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;
@@ -6280,6 +6398,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)
@@ -6318,7 +6445,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 = "";
@@ -6438,8 +6564,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);
@@ -6780,6 +6908,67 @@ 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;
+	int				  root = s->tree == s->root;
+	int				  off = root ? 1 : 2;
+
+	g = view->gline;
+	view->gline = 0;
+
+	if (g == 0)
+		g = 1;
+	else if (g > got_object_tree_get_nentries(s->tree))
+		g = got_object_tree_get_nentries(s->tree) + (root ? 0 : 1);
+
+	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 += off;  /* account for ".." */
+	}
+	last = got_tree_entry_get_index(*lte);
+	last += off;
+
+	if (g >= first && g <= last && g - first < nlines) {
+		s->selected = g - first;
+		return NULL;	/* gline is on the current page */
+	}
+
+	if (*ste != NULL) {
+		i = got_tree_entry_get_index(*ste);
+		i += off;
+	}
+
+	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 += off;
+			s->selected = g - first;
+		}
+	} else if (i > g)
+		tree_scroll_up(s, i - g);
+
+	if (g < nlines &&
+	    (*fte == NULL || (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;
@@ -6788,6 +6977,9 @@ 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)
+		return tree_goto_line(view, nscroll);
+
 	switch (ch) {
 	case 'i':
 		s->show_ids = !s->show_ids;
@@ -7438,8 +7630,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);
@@ -7720,7 +7914,48 @@ 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 == 0)
+		g = 1;
+	else if (g > s->nrefs)
+		g = s->nrefs;
+
+	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;
@@ -7729,6 +7964,9 @@ 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)
+		return ref_goto_line(view, nscroll);
+
 	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