Download raw body.
tog: nG key map like vi(1) and less(1)
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 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. 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. diff refs/heads/main refs/heads/dev/gline commit - 2525dccb913c5e7f293cfcad103af7a761d32b34 commit + 3539279418de788b5fbc216b5fc6ba14f67cfe91 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 + b04bb37cdd7c24649cca3ddc8764f73970374122 --- 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,6 @@ get_compound_key(struct tog_view *view, int c) v = view->parent; view->count = 0; - cbreak(); /* block for input */ wmove(v->window, v->nlines - 1, 0); wclrtoeol(v->window); waddch(v->window, ':'); @@ -1283,15 +1283,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; @@ -1299,13 +1298,19 @@ view_input(struct tog_view **new, int *done, struct to ch = view->ch; } else { ch = wgetch(view->window); - if (ch >= '1' && ch <= '9') + if (ch >= '1' && ch <= '9') { + nodelay(view->window, FALSE); view->ch = ch = get_compound_key(view, ch); + if (ch == 'G') { + view->gline = view->count; + view->ch = ch = 0; + view->count = 0; + } + } } 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 +3864,70 @@ done: return err; } +static int +gotoline(struct tog_view *view, int *lineno, int *nprinted) +{ + FILE *f = NULL; + int *eof, *first, *selected; + int restart = 0; + + if (view->type == TOG_VIEW_DIFF) { + struct tog_diff_view_state *s = &view->state.diff; + + first = &s->first_displayed_line; + selected = &s->selected_line; + eof = &s->eof; + f = s->f; + + /* Make gline the first line on the page like less(1). */ + if (*first != 1 && *lineno > view->gline) + restart = 1; /* after gline */ + else if (*lineno < view->gline) /* before gline */ + return 0; + else + *selected = 1; /* at gline */ + } 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; + + /* Center gline in the middle of the page like vi(1). */ + if (*first != 1 && + (*lineno > view->gline - (view->nlines - 3) / 2)) + restart = 1; + else if (*lineno < view->gline - (view->nlines - 3) / 2) + return 0; + else { + *selected = view->gline <= (view->nlines - 3) / 2 ? + view->gline : (view->nlines - 3) / 2 + 1; + } + } else + return 0; + + if (restart) { + rewind(f); + *eof = 0; + *first = 1; + *lineno = 0; + *nprinted = 0; + return 0; + } + + 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 +3938,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 +3988,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 +4032,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 +5148,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 +5183,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
tog: nG key map like vi(1) and less(1)