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

From:
Mark Jamsek <mark@jamsek.com>
Subject:
Re: [rfc] compound keymaps with prefixed count modifier like vi(1)
To:
gameoftrees@openbsd.org
Date:
Wed, 22 Jun 2022 02:01:07 +1000

Download raw body.

Thread
On 22-06-22 01:08am, Mark Jamsek wrote:
> I figured it'd be easier to explain with an example so the following
> diff demonstrates the intended behaviour. Basically, for movement
> keymaps, they can be prefixed with a count modifier, which determines
> how many times the requested movement is performed.

I just realised I hadn't updated my tree but had naddy's diff applied so
I've got his changes from 48bb96f0 in the previous diff. Please
disregard that one.

diff refs/heads/main refs/heads/nkeymap
blob - a89f36bf163bbb820b19ad56b23223e7e6078bc1
blob + fccd509f1d942aa5ba3cd20be4d5cc69450074f4
--- tog/tog.1
+++ tog/tog.1
@@ -55,6 +55,9 @@ Displays references in the repository.
 .Pp
 .Nm
 provides global and command-specific key bindings and options.
+Some command-specific key bindings may be prefixed with an integer, which is
+denoted by N in the descriptions below, and is used as a modifier to the
+operation as indicated.
 The global key bindings are:
 .Bl -tag -width Ds
 .It Cm Q
@@ -98,30 +101,32 @@ This command is also executed if no explicit command i
 .Pp
 The key bindings for
 .Cm tog log
-are as follows:
+are as follows (N denotes optional prefixed count modifier):
 .Bl -tag -width Ds
 .It Cm Down-arrow, j, >, Full stop, Ctrl-n
-Move the selection cursor down.
+Move the selection cursor down N lines (default: 1).
 .It Cm Up-arrow, k, <, Comma, Ctrl-p
-Move the selection cursor up.
+Move the selection cursor up N lines (default: 1).
 .It Cm Right-arrow, l
-Scroll log message field to the right.
+Scroll log message field to the right N increments (default: 1).
+.br
 Log message moves left on the screen.
 .It Cm Left-arrow, h
-Scroll log message field to the left.
+Scroll log message field to the left N increments (default: 1).
+.br
 Log message moves right on the screen.
 .It Cm $
 Scroll log message field to the rightmost position.
 .It Cm 0
 Scroll log message field to the leftmost position.
 .It Cm Page-down, Space, Ctrl+f, f
-Move the selection cursor down one page.
+Move the selection cursor down N pages (default: 1).
 .It Cm Page-up, Ctrl+b, b
-Move the selection cursor up one page.
+Move the selection cursor up N pages (default: 1).
 .It Cm Ctrl+d, d
-Move the selection cursor down one half page.
+Move the selection cursor down N half pages (default: 1).
 .It Cm Ctrl+u, u
-Move the selection cursor up one half page.
+Move the selection cursor up N half pages (default: 1).
 .It Cm Home, g
 Move the cursor to the newest commit.
 .It Cm End, G
@@ -152,12 +157,15 @@ commit ID SHA1 hash.
 Regular expression syntax is documented in
 .Xr re_format 7 .
 .It Cm n
-Find the next commit which matches the current search pattern.
+Find the Nth next commit which matches the current search pattern (default: 1).
+.br
 Searching continues until either a match is found or the
 .Cm Backspace
 key is pressed.
 .It Cm N
-Find the previous commit which matches the current search pattern.
+Find the Nth previous commit which matches the current search pattern
+(default: 1).
+.br
 Searching continues until either a match is found or the
 .Cm Backspace
 key is pressed.
@@ -220,62 +228,65 @@ automatically, provided the abbreviation is unique.
 .Pp
 The key bindings for
 .Cm tog diff
-are as follows:
+are as follows (N denotes optional prefixed count modifier):
 .Bl -tag -width Ds
 .It Cm a
 Toggle treatment of file contents as ASCII text even if binary data was
 detected.
 .It Cm Down-arrow, j, Ctrl-n
-Scroll down.
+Scroll down N lines (default: 1).
 .It Cm Up-arrow, k, Ctrl-p
-Scroll up.
+Scroll up N lines (default: 1).
 .It Cm Right-arrow, l
-Scroll view to the right.
+Scroll view to the right N increments (default: 1).
+.br
 Diff output moves left on the screen.
 .It Cm Left-arrow, h
-Scroll view to the left.
+Scroll view to the left N increments (default: 1).
+.br
 Diff output moves right on the screen.
 .It Cm $
 Scroll view to the rightmost position.
 .It Cm 0
 Scroll view left to the start of the line.
 .It Cm Page-down, Space, Ctrl+f, f
-Scroll down one page.
+Scroll down N pages (default: 1).
 .It Cm Page-up, Ctrl+b, b
-Scroll up one page.
+Scroll up N pages (default: 1).
 .It Cm Ctrl+d, d
-Scroll down one half page.
+Scroll down N half pages (default: 1).
 .It Cm Ctrl+u, u
-Scroll up one half page.
+Scroll up N half pages (default: 1).
 .It Cm Home, g
 Scroll to the top of the view.
 .It Cm End, G
 Scroll to the bottom of the view.
 .It Cm \&[
-Reduce the amount of diff context lines.
+Reduce diff context by N lines (default: 1).
 .It Cm \&]
-Increase the amount of diff context lines.
+Increase diff context by N lines (default: 1).
 .It Cm <, Comma
 If the
 .Cm diff
 view was opened via the
 .Cm log
-view, move to the previous (younger) commit.
+view, move to the Nth previous (younger) commit (default: 1).
 .It Cm >, Full stop
 If the
 .Cm diff
 view was opened via the
 .Cm log
-view, move to the next (older) commit.
+view, move to the Nth next (older) commit (default: 1).
 .It Cm /
 Prompt for a search pattern and start searching for matching lines.
 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.
+Find the Nth next line which matches the current search pattern (default: 1).
 .It Cm N
-Find the previous line which matches the current search pattern.
+Find the Nth previous line which matches the current search pattern
+(default: 1).
 .It Cm w
 Toggle display of whitespace-only changes.
 .El
@@ -304,30 +315,32 @@ Display line-by-line history of a file at the specifie
 .Pp
 The key bindings for
 .Cm tog blame
-are as follows:
+are as follows (N denotes optional prefixed count modifier):
 .Bl -tag -width Ds
 .It Cm Down-arrow, j, Ctrl-n
-Move the selection cursor down.
+Move the selection cursor down N pages (default: 1).
 .It Cm Up-arrow, k, Ctrl-p
-Move the selection cursor up.
+Move the selection cursor up N pages (default: 1).
 .It Cm Right-arrow, l
-Scroll view to the right.
+Scroll view to the right N increments (default: 1).
+.br
 File output moves left on the screen.
 .It Cm Left-arrow, h
-Scroll view to the left.
+Scroll view to the left N increments (default: 1).
+.br
 File output moves right on the screen.
 .It Cm $
 Scroll view to the rightmost position.
 .It Cm 0
 Scroll view left to the start of the line.
 .It Cm Page-down, Space, Ctrl+f, f
-Move the selection cursor down one page.
+Move the selection cursor down N pages (default: 1).
 .It Cm Page-up, Ctrl+b, b
-Move the selection cursor up one page.
+Move the selection cursor up N pages (default: 1).
 .It Cm Ctrl+d, d
-Move the selection cursor down one half page.
+Move the selection cursor down N half pages (default: 1).
 .It Cm Ctrl+u, u
-Move the selection cursor up one half page.
+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
@@ -356,9 +369,10 @@ 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.
+Find the Nth next line which matches the current search pattern (default: 1).
 .It Cm N
-Find the previous line which matches the current search pattern.
+Find the Nth previous line which matches the current search pattern
+(default: 1).
 .El
 .Pp
 The options for
@@ -397,20 +411,20 @@ Symbolic link entries are also annotated with the targ
 .Pp
 The key bindings for
 .Cm tog tree
-are as follows:
+are as follows (N denotes optional prefixed count modifier):
 .Bl -tag -width Ds
 .It Cm Down-arrow, j, Ctrl-n
-Move the selection cursor down.
+Move the selection cursor down N lines (default: 1).
 .It Cm Up-arrow, k, Ctrl-p
-Move the selection cursor up.
+Move the selection cursor up N lines (default: 1).
 .It Cm Page-down, Space, Ctrl+f, f
-Move the selection cursor down one page.
+Move the selection cursor down N pages (default: 1).
 .It Cm Page-up, Ctrl+b, b
-Move the selection cursor up one page.
+Move the selection cursor up N pages (default: 1).
 .It Cm Ctrl+d, d
-Move the selection cursor down one half page.
+Move the selection cursor down N half pages (default: 1).
 .It Cm Ctrl+u, u
-Move the selection cursor up one half page.
+Move the selection cursor up N half pages (default: 1).
 .It Cm Home, g
 Move the selection cursor to the first entry.
 .It Cm End, G
@@ -431,7 +445,7 @@ This can then be used to open a new
 .Cm tree
 view for arbitrary branches and tags.
 .It Cm Backspace
-Move back to the parent directory.
+Move back to the Nth parent directory (default: 1).
 .It Cm i
 Show object IDs for all objects displayed in the
 .Cm tree
@@ -443,9 +457,11 @@ against the tree entry's name.
 Regular expression syntax is documented in
 .Xr re_format 7 .
 .It Cm n
-Find the next tree entry which matches the current search pattern.
+Find the Nth next tree entry which matches the current search pattern
+(default: 1).
 .It Cm N
-Find the previous tree entry which matches the current search pattern.
+Find the Nth previous tree entry which matches the current search pattern
+(default: 1).
 .El
 .Pp
 The options for
@@ -471,20 +487,20 @@ Display references in the repository.
 .Pp
 The key bindings for
 .Cm tog ref
-are as follows:
+are as follows (N denotes optional prefixed count modifier):
 .Bl -tag -width Ds
 .It Cm Down-arrow, j, Ctrl-n
-Move the selection cursor down.
+Move the selection cursor down N lines (default: 1).
 .It Cm Up-arrow, k, Ctrl-p
-Move the selection cursor up.
+Move the selection cursor up N lines (default: 1).
 .It Cm Page-down, Space, Ctrl+f, f
-Move the selection cursor down one page.
+Move the selection cursor down N pages (default: 1).
 .It Cm Page-up, Ctrl+b, b
-Move the selection cursor up one page.
+Move the selection cursor up N pages (default: 1).
 .It Cm Ctrl+d, d
-Move the selection cursor down one half page.
+Move the selection cursor down N half pages (default: 1).
 .It Cm Ctrl+u, u
-Move the selection cursor up one half page.
+Move the selection cursor up N half pages (default: 1).
 .It Cm Home, g
 Move the selection cursor to the first reference.
 .It Cm End, G
@@ -513,9 +529,11 @@ against absolute reference names.
 Regular expression syntax is documented in
 .Xr re_format 7 .
 .It Cm n
-Find the next reference which matches the current search pattern.
+Find the Nth next reference which matches the current search pattern
+(default: 1).
 .It Cm N
-Find the previous reference which matches the current search pattern.
+Find the Nth previous reference which matches the current search pattern
+(default: 1).
 .It Cm Ctrl+l
 Reload the list of references displayed by the
 .Cm ref
blob - 17b45ee2d85a1a2a4e160e82a484d346d9874d7d
blob + 78e47e8a187dbacb55661c676692831b735039c4
--- tog/tog.c
+++ tog/tog.c
@@ -502,6 +502,7 @@ struct tog_view {
 	int nlines, ncols, begin_y, begin_x;
 	int maxx, x; /* max column and current start column */
 	int lines, cols; /* copies of LINES and COLS */
+	int ch, count; /* current keymap and count prefix */
 	int focussed; /* Only set on one parent or child view at a time. */
 	int dying;
 	struct tog_view *parent;
@@ -667,6 +668,8 @@ view_open(int nlines, int ncols, int begin_y, int begi
 	if (view == NULL)
 		return NULL;
 
+	view->ch = 0;
+	view->count = 0;
 	view->type = type;
 	view->lines = LINES;
 	view->cols = COLS;
@@ -884,6 +887,42 @@ view_search_start(struct tog_view *view)
 	return NULL;
 }
 
+/*
+ * Compute view->count from numeric user input.  User has five-tenths of a
+ * second to follow each numeric keypress with another number to form count.
+ * Return first non-numeric input or ERR and assign total to view->count.
+ * XXX Should we add support for user-defined timeout?
+ */
+static int
+get_compound_key(struct tog_view *view, int c)
+{
+	int n = 0;
+
+	view->count = 0;
+	halfdelay(5);  /* block for half a second */
+
+	do {
+		/*
+		 * Don't overflow. Max valid request should be the greatest
+		 * between the longest and total lines; cap at 10 million.
+		 */
+		if (n >= 9999999UL)
+			n = 9999999UL;
+		else
+			n = n * 10 + (c - '0');
+	} while (((c = wgetch(view->window))) >= '0' && c <= '9' && c != ERR);
+
+	/*
+	 * Massage value at the input handler if count is invalid.
+	 * XXX Should we validate with a helper function here
+	 * despite maintaining list of key maps in two places?
+	 */
+	view->count = n;
+
+	cbreak();  /* return to blocking */
+	return c;
+}
+
 static const struct got_error *
 view_input(struct tog_view **new, int *done, struct tog_view *view,
     struct tog_view_list_head *views)
@@ -896,8 +935,10 @@ view_input(struct tog_view **new, int *done, struct to
 
 	/* Clear "no matches" indicator. */
 	if (view->search_next_done == TOG_SEARCH_NO_MORE ||
-	    view->search_next_done == TOG_SEARCH_HAVE_NONE)
+	    view->search_next_done == TOG_SEARCH_HAVE_NONE) {
 		view->search_next_done = TOG_SEARCH_HAVE_MORE;
+		view->count = 0;
+	}
 
 	if (view->searching && !view->search_next_done) {
 		errcode = pthread_mutex_unlock(&tog_mutex);
@@ -918,7 +959,13 @@ view_input(struct tog_view **new, int *done, struct to
 	errcode = pthread_mutex_unlock(&tog_mutex);
 	if (errcode)
 		return got_error_set_errno(errcode, "pthread_mutex_unlock");
-	ch = wgetch(view->window);
+	/* If we have an unfinished count, don't get a new key map. */
+	ch = view->ch;
+	if ((view->count && --view->count == 0) || !view->count) {
+		ch = wgetch(view->window);
+		if (ch >= '1' && ch  <= '9')
+			view->ch = ch = get_compound_key(view, ch);
+	}
 	errcode = pthread_mutex_lock(&tog_mutex);
 	if (errcode)
 		return got_error_set_errno(errcode, "pthread_mutex_lock");
@@ -949,6 +996,7 @@ view_input(struct tog_view **new, int *done, struct to
 
 	switch (ch) {
 	case '\t':
+		view->count = 0;
 		if (view->child) {
 			view->focussed = 0;
 			view->child->focussed = 1;
@@ -969,6 +1017,7 @@ view_input(struct tog_view **new, int *done, struct to
 		*done = 1;
 		break;
 	case 'F':
+		view->count = 0;
 		if (view_is_parent_view(view)) {
 			if (view->child == NULL)
 				break;
@@ -1000,6 +1049,7 @@ view_input(struct tog_view **new, int *done, struct to
 	case KEY_RESIZE:
 		break;
 	case '/':
+		view->count = 0;
 		if (view->search_start)
 			view_search_start(view);
 		else
@@ -2595,21 +2645,28 @@ input_log_view(struct tog_view **new_view, struct tog_
 		break;
 	case '$':
 		view->x = MAX(view->maxx - view->ncols / 2, 0);
+		view->count = 0;
 		break;
 	case KEY_RIGHT:
 	case 'l':
 		if (view->x + view->ncols / 2 < view->maxx)
 			view->x += 2;  /* move two columns right */
+		else
+			view->count = 0;
 		break;
 	case KEY_LEFT:
 	case 'h':
 		view->x -= MIN(view->x, 2);  /* move two columns back */
+		if (view->x <= 0)
+			view->count = 0;
 		break;
 	case 'k':
 	case KEY_UP:
 	case '<':
 	case ',':
 	case CTRL('p'):
+		if (s->selected_entry->idx == 0)
+			view->count = 0;
 		if (s->first_displayed_entry == NULL)
 			break;
 		if (s->selected > 0)
@@ -2623,6 +2680,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 		s->selected = 0;
 		s->first_displayed_entry = TAILQ_FIRST(&s->commits.head);
 		select_commit(s);
+		view->count = 0;
 		break;
 	case CTRL('u'):
 	case 'u':
@@ -2638,6 +2696,8 @@ input_log_view(struct tog_view **new_view, struct tog_
 		else
 			log_scroll_up(s, nscroll);
 		select_commit(s);
+		if (s->selected_entry->idx == 0)
+			view->count = 0;
 		break;
 	case 'j':
 	case KEY_DOWN:
@@ -2655,11 +2715,15 @@ input_log_view(struct tog_view **new_view, struct tog_
 				break;
 		}
 		select_commit(s);
+		if (s->thread_args.log_complete &&
+		    s->selected_entry->idx == s->commits.ncommits - 1)
+			view->count = 0;
 		break;
 	case 'G':
 	case KEY_END: {
 		/* We don't know yet how many commits, so we're forced to
 		 * traverse them all. */
+		view->count = 0;
 		if (!s->thread_args.log_complete) {
 			s->thread_args.load_all = 1;
 			return trigger_log_thread(view, 0);
@@ -2688,8 +2752,10 @@ input_log_view(struct tog_view **new_view, struct tog_
 	case ' ': {
 		struct commit_queue_entry *first;
 		first = s->first_displayed_entry;
-		if (first == NULL)
+		if (first == NULL) {
+			view->count = 0;
 			break;
+		}
 		err = log_scroll_down(view, nscroll);
 		if (err)
 			break;
@@ -2701,6 +2767,9 @@ input_log_view(struct tog_view **new_view, struct tog_
 			    s->selected_entry->idx, nscroll + 1);
 		}
 		select_commit(s);
+		if (s->thread_args.log_complete &&
+		    s->selected_entry->idx == s->commits.ncommits - 1)
+			view->count = 0;
 		break;
 	}
 	case KEY_RESIZE:
@@ -2718,6 +2787,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 		break;
 	case KEY_ENTER:
 	case '\r':
+		view->count = 0;
 		if (s->selected_entry == NULL)
 			break;
 		if (view_is_parent_view(view))
@@ -2741,6 +2811,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 			*new_view = diff_view;
 		break;
 	case 't':
+		view->count = 0;
 		if (s->selected_entry == NULL)
 			break;
 		if (view_is_parent_view(view))
@@ -2766,6 +2837,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 	case KEY_BACKSPACE:
 	case CTRL('l'):
 	case 'B':
+		view->count = 0;
 		if (ch == KEY_BACKSPACE &&
 		    got_path_is_root_dir(s->in_repo_path))
 			break;
@@ -2827,6 +2899,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 		s->search_entry = NULL;
 		break;
 	case 'r':
+		view->count = 0;
 		if (view_is_parent_view(view))
 			begin_x = view_split_begin_x(view->begin_x);
 		ref_view = view_open(view->nlines, view->ncols,
@@ -2852,6 +2925,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 			*new_view = ref_view;
 		break;
 	default:
+		view->count = 0;
 		break;
 	}
 
@@ -4017,15 +4091,20 @@ input_diff_view(struct tog_view **new_view, struct tog
 		break;
 	case '$':
 		view->x = MAX(view->maxx - view->ncols / 3, 0);
+		view->count = 0;
 		break;
 	case KEY_RIGHT:
 	case 'l':
 		if (view->x + view->ncols / 3 < view->maxx)
 			view->x += 2;  /* move two columns right */
+		else
+			view->count = 0;
 		break;
 	case KEY_LEFT:
 	case 'h':
 		view->x -= MIN(view->x, 2);  /* move two columns back */
+		if (view->x <= 0)
+			view->count = 0;
 		break;
 	case 'a':
 	case 'w':
@@ -4039,13 +4118,16 @@ input_diff_view(struct tog_view **new_view, struct tog
 		s->matched_line = 0;
 		diff_view_indicate_progress(view);
 		err = create_diff(s);
+		view->count = 0;
 		break;
 	case 'g':
 	case KEY_HOME:
 		s->first_displayed_line = 1;
+		view->count = 0;
 		break;
 	case 'G':
 	case KEY_END:
+		view->count = 0;
 		if (s->eof)
 			break;
 
@@ -4057,6 +4139,8 @@ input_diff_view(struct tog_view **new_view, struct tog
 	case CTRL('p'):
 		if (s->first_displayed_line > 1)
 			s->first_displayed_line--;
+		else
+			view->count = 0;
 		break;
 	case CTRL('u'):
 	case 'u':
@@ -4065,8 +4149,10 @@ input_diff_view(struct tog_view **new_view, struct tog
 	case KEY_PPAGE:
 	case CTRL('b'):
 	case 'b':
-		if (s->first_displayed_line == 1)
+		if (s->first_displayed_line == 1) {
+			view->count = 0;
 			break;
+		}
 		i = 0;
 		while (i++ < nscroll && s->first_displayed_line > 1)
 			s->first_displayed_line--;
@@ -4076,6 +4162,8 @@ input_diff_view(struct tog_view **new_view, struct tog
 	case CTRL('n'):
 		if (!s->eof)
 			s->first_displayed_line++;
+		else
+			view->count = 0;
 		break;
 	case CTRL('d'):
 	case 'd':
@@ -4085,8 +4173,10 @@ input_diff_view(struct tog_view **new_view, struct tog
 	case CTRL('f'):
 	case 'f':
 	case ' ':
-		if (s->eof)
+		if (s->eof) {
+			view->count = 0;
 			break;
+		}
 		i = 0;
 		while (!s->eof && i++ < nscroll) {
 			linelen = getline(&line, &linesize, s->f);
@@ -4112,7 +4202,8 @@ input_diff_view(struct tog_view **new_view, struct tog
 				s->first_displayed_line = 1;
 				s->last_displayed_line = view->nlines;
 			}
-		}
+		} else
+			view->count = 0;
 		break;
 	case ']':
 		if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
@@ -4120,15 +4211,19 @@ input_diff_view(struct tog_view **new_view, struct tog
 			s->matched_line = 0;
 			diff_view_indicate_progress(view);
 			err = create_diff(s);
-		}
+		} else
+			view->count = 0;
 		break;
 	case '<':
 	case ',':
-		if (s->log_view == NULL)
+		if (s->log_view == NULL) {
+			view->count = 0;
 			break;
+		}
 		ls = &s->log_view->state.log;
 		old_selected_entry = ls->selected_entry;
 
+		/* view->count handled in input_log_view() */
 		err = input_log_view(NULL, s->log_view, KEY_UP);
 		if (err)
 			break;
@@ -4150,11 +4245,14 @@ input_diff_view(struct tog_view **new_view, struct tog
 		break;
 	case '>':
 	case '.':
-		if (s->log_view == NULL)
+		if (s->log_view == NULL) {
+			view->count = 0;
 			break;
+		}
 		ls = &s->log_view->state.log;
 		old_selected_entry = ls->selected_entry;
 
+		/* view->count handled in input_log_view() */
 		err = input_log_view(NULL, s->log_view, KEY_DOWN);
 		if (err)
 			break;
@@ -4175,6 +4273,7 @@ input_diff_view(struct tog_view **new_view, struct tog
 		err = create_diff(s);
 		break;
 	default:
+		view->count = 0;
 		break;
 	}
 
@@ -4964,15 +5063,20 @@ input_blame_view(struct tog_view **new_view, struct to
 		break;
 	case '$':
 		view->x = MAX(view->maxx - view->ncols / 3, 0);
+		view->count = 0;
 		break;
 	case KEY_RIGHT:
 	case 'l':
 		if (view->x + view->ncols / 3 < view->maxx)
 			view->x += 2;  /* move two columns right */
+		else
+			view->count = 0;
 		break;
 	case KEY_LEFT:
 	case 'h':
 		view->x -= MIN(view->x, 2);  /* move two columns back */
+		if (view->x <= 0)
+			view->count = 0;
 		break;
 	case 'q':
 		s->done = 1;
@@ -4981,6 +5085,7 @@ input_blame_view(struct tog_view **new_view, struct to
 	case KEY_HOME:
 		s->selected_line = 1;
 		s->first_displayed_line = 1;
+		view->count = 0;
 		break;
 	case 'G':
 	case KEY_END:
@@ -4992,6 +5097,7 @@ input_blame_view(struct tog_view **new_view, struct to
 			s->first_displayed_line = s->blame.nlines -
 			    (view->nlines - 3);
 		}
+		view->count = 0;
 		break;
 	case 'k':
 	case KEY_UP:
@@ -5001,6 +5107,8 @@ input_blame_view(struct tog_view **new_view, struct to
 		else if (s->selected_line == 1 &&
 		    s->first_displayed_line > 1)
 			s->first_displayed_line--;
+		else
+			view->count = 0;
 		break;
 	case CTRL('u'):
 	case 'u':
@@ -5011,6 +5119,7 @@ input_blame_view(struct tog_view **new_view, struct to
 	case 'b':
 		if (s->first_displayed_line == 1) {
 			s->selected_line = MAX(1, s->selected_line - nscroll);
+			view->count = 0;
 			break;
 		}
 		if (s->first_displayed_line > nscroll)
@@ -5028,10 +5137,14 @@ input_blame_view(struct tog_view **new_view, struct to
 		else if (s->last_displayed_line <
 		    s->blame.nlines)
 			s->first_displayed_line++;
+		else
+			view->count = 0;
 		break;
 	case 'c':
 	case 'p': {
 		struct got_object_id *id = NULL;
+
+		view->count = 0;
 		id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
 		    s->first_displayed_line, s->selected_line);
 		if (id == NULL)
@@ -5099,6 +5212,8 @@ input_blame_view(struct tog_view **new_view, struct to
 	}
 	case 'C': {
 		struct got_object_qid *first;
+
+		view->count = 0;
 		first = STAILQ_FIRST(&s->blamed_commits);
 		if (!got_object_id_cmp(&first->id, s->commit_id))
 			break;
@@ -5121,6 +5236,8 @@ input_blame_view(struct tog_view **new_view, struct to
 		struct got_object_id *id = NULL;
 		struct got_object_qid *pid;
 		struct got_commit_object *commit = NULL;
+
+		view->count = 0;
 		id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
 		    s->first_displayed_line, s->selected_line);
 		if (id == NULL)
@@ -5172,6 +5289,7 @@ input_blame_view(struct tog_view **new_view, struct to
 		if (s->last_displayed_line >= s->blame.nlines &&
 		    s->selected_line >= MIN(s->blame.nlines,
 		    view->nlines - 2)) {
+			view->count = 0;
 			break;
 		}
 		if (s->last_displayed_line >= s->blame.nlines &&
@@ -5193,6 +5311,7 @@ input_blame_view(struct tog_view **new_view, struct to
 		}
 		break;
 	default:
+		view->count = 0;
 		break;
 	}
 	return thread_err ? thread_err : err;
@@ -5885,8 +6004,10 @@ input_tree_view(struct tog_view **new_view, struct tog
 	switch (ch) {
 	case 'i':
 		s->show_ids = !s->show_ids;
+		view->count = 0;
 		break;
 	case 'l':
+		view->count = 0;
 		if (!s->selected_entry)
 			break;
 		if (view_is_parent_view(view))
@@ -5906,6 +6027,7 @@ input_tree_view(struct tog_view **new_view, struct tog
 			*new_view = log_view;
 		break;
 	case 'r':
+		view->count = 0;
 		if (view_is_parent_view(view))
 			begin_x = view_split_begin_x(view->begin_x);
 		ref_view = view_open(view->nlines, view->ncols,
@@ -5933,6 +6055,7 @@ input_tree_view(struct tog_view **new_view, struct tog
 	case 'g':
 	case KEY_HOME:
 		s->selected = 0;
+		view->count = 0;
 		if (s->tree == s->root)
 			s->first_displayed_entry =
 			    got_object_tree_get_first_entry(s->tree);
@@ -5942,6 +6065,7 @@ input_tree_view(struct tog_view **new_view, struct tog
 	case 'G':
 	case KEY_END:
 		s->selected = 0;
+		view->count = 0;
 		te = got_object_tree_get_last_entry(s->tree);
 		for (n = 0; n < view->nlines - 3; n++) {
 			if (te == NULL) {
@@ -5965,6 +6089,10 @@ input_tree_view(struct tog_view **new_view, struct tog
 			break;
 		}
 		tree_scroll_up(s, 1);
+		if (s->selected_entry == NULL ||
+		    (s->tree == s->root && s->selected_entry ==
+		     got_object_tree_get_first_entry(s->tree)))
+			view->count = 0;
 		break;
 	case CTRL('u'):
 	case 'u':
@@ -5982,6 +6110,10 @@ input_tree_view(struct tog_view **new_view, struct tog
 				s->selected -= MIN(s->selected, nscroll);
 		}
 		tree_scroll_up(s, MAX(0, nscroll));
+		if (s->selected_entry == NULL ||
+		    (s->tree == s->root && s->selected_entry ==
+		     got_object_tree_get_first_entry(s->tree)))
+			view->count = 0;
 		break;
 	case 'j':
 	case KEY_DOWN:
@@ -5991,9 +6123,11 @@ input_tree_view(struct tog_view **new_view, struct tog
 			break;
 		}
 		if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
-		    == NULL)
+		    == NULL) {
 			/* can't scroll any further */
+			view->count = 0;
 			break;
+		}
 		tree_scroll_down(s, 1);
 		break;
 	case CTRL('d'):
@@ -6010,6 +6144,8 @@ input_tree_view(struct tog_view **new_view, struct tog
 			if (s->selected < s->ndisplayed - 1)
 				s->selected += MIN(nscroll,
 				    s->ndisplayed - s->selected - 1);
+			else
+				view->count = 0;
 			break;
 		}
 		tree_scroll_down(s, nscroll);
@@ -6020,8 +6156,10 @@ input_tree_view(struct tog_view **new_view, struct tog
 		if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
 			struct tog_parent_tree *parent;
 			/* user selected '..' */
-			if (s->tree == s->root)
+			if (s->tree == s->root) {
+				view->count = 0;
 				break;
+			}
 			parent = TAILQ_FIRST(&s->parents);
 			TAILQ_REMOVE(&s->parents, parent,
 			    entry);
@@ -6036,6 +6174,7 @@ input_tree_view(struct tog_view **new_view, struct tog
 		} else if (S_ISDIR(got_tree_entry_get_mode(
 		    s->selected_entry))) {
 			struct got_tree_object *subtree;
+			view->count = 0;
 			err = got_object_open_as_tree(&subtree, s->repo,
 			    got_tree_entry_get_id(s->selected_entry));
 			if (err)
@@ -6056,6 +6195,7 @@ input_tree_view(struct tog_view **new_view, struct tog
 			    s->commit_id, s->repo);
 			if (err)
 				break;
+			view->count = 0;
 			view->focussed = 0;
 			blame_view->focussed = 1;
 			if (view_is_parent_view(view)) {
@@ -6073,8 +6213,10 @@ input_tree_view(struct tog_view **new_view, struct tog
 	case KEY_RESIZE:
 		if (view->nlines >= 4 && s->selected >= view->nlines - 3)
 			s->selected = view->nlines - 4;
+		view->count = 0;
 		break;
 	default:
+		view->count = 0;
 		break;
 	}
 
@@ -6757,12 +6899,15 @@ input_ref_view(struct tog_view **new_view, struct tog_
 	switch (ch) {
 	case 'i':
 		s->show_ids = !s->show_ids;
+		view->count = 0;
 		break;
 	case 'm':
 		s->show_date = !s->show_date;
+		view->count = 0;
 		break;
 	case 'o':
 		s->sort_by_date = !s->sort_by_date;
+		view->count = 0;
 		err = got_reflist_sort(&tog_refs, s->sort_by_date ?
 		    got_ref_cmp_by_commit_timestamp_descending :
 		    tog_ref_cmp_by_name, s->repo);
@@ -6778,6 +6923,7 @@ input_ref_view(struct tog_view **new_view, struct tog_
 		break;
 	case KEY_ENTER:
 	case '\r':
+		view->count = 0;
 		if (!s->selected_entry)
 			break;
 		if (view_is_parent_view(view))
@@ -6798,6 +6944,7 @@ input_ref_view(struct tog_view **new_view, struct tog_
 			*new_view = log_view;
 		break;
 	case 't':
+		view->count = 0;
 		if (!s->selected_entry)
 			break;
 		if (view_is_parent_view(view))
@@ -6822,11 +6969,13 @@ input_ref_view(struct tog_view **new_view, struct tog_
 	case 'g':
 	case KEY_HOME:
 		s->selected = 0;
+		view->count = 0;
 		s->first_displayed_entry = TAILQ_FIRST(&s->refs);
 		break;
 	case 'G':
 	case KEY_END:
 		s->selected = 0;
+		view->count = 0;
 		re = TAILQ_LAST(&s->refs, tog_reflist_head);
 		for (n = 0; n < view->nlines - 1; n++) {
 			if (re == NULL)
@@ -6845,6 +6994,8 @@ input_ref_view(struct tog_view **new_view, struct tog_
 			break;
 		}
 		ref_scroll_up(s, 1);
+		if (s->selected_entry == TAILQ_FIRST(&s->refs))
+			view->count = 0;
 		break;
 	case CTRL('u'):
 	case 'u':
@@ -6856,6 +7007,8 @@ input_ref_view(struct tog_view **new_view, struct tog_
 		if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
 			s->selected -= MIN(nscroll, s->selected);
 		ref_scroll_up(s, MAX(0, nscroll));
+		if (s->selected_entry == TAILQ_FIRST(&s->refs))
+			view->count = 0;
 		break;
 	case 'j':
 	case KEY_DOWN:
@@ -6864,9 +7017,11 @@ input_ref_view(struct tog_view **new_view, struct tog_
 			s->selected++;
 			break;
 		}
-		if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL)
+		if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
 			/* can't scroll any further */
+			view->count = 0;
 			break;
+		}
 		ref_scroll_down(s, 1);
 		break;
 	case CTRL('d'):
@@ -6882,11 +7037,15 @@ input_ref_view(struct tog_view **new_view, struct tog_
 			if (s->selected < s->ndisplayed - 1)
 				s->selected += MIN(nscroll,
 				    s->ndisplayed - s->selected - 1);
+			if (view->count > 1 && s->selected < s->ndisplayed - 1)
+				s->selected += s->ndisplayed - s->selected - 1;
+			view->count = 0;
 			break;
 		}
 		ref_scroll_down(s, nscroll);
 		break;
 	case CTRL('l'):
+		view->count = 0;
 		tog_free_refs();
 		err = tog_load_refs(s->repo, s->sort_by_date);
 		if (err)
@@ -6899,6 +7058,7 @@ input_ref_view(struct tog_view **new_view, struct tog_
 			s->selected = view->nlines - 2;
 		break;
 	default:
+		view->count = 0;
 		break;
 	}
 

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