From: Mark Jamsek 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 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 GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68