Download raw body.
tog: keymaps to navigate to prev/next file/hunk in the diff
I got the idea from stsp the other day when he mentioned that he doesn't always navigate diffs linearly, and I started paying close attention to how I read diffs. He's right--sometimes I jump around a fair bit too, and sometimes I read them top to bottom. In both cases, I find these key maps help navigating diffs. Two new sets of key maps are proposed: P - jump to the previous file N - jump to the next file { - jump to the previous hunk } - jump to the next hunk Next and previous are relative to the current line, and they work with a count prefix too. NP in particular are helpful in large diffs of multiple files; often I want to read the man page first (if the diff involves user-facing changes) to get an idea of what the implementation should do, then after reading the code, I may want to jump back and forth from the docs if, for example, I'm scrutinising something. But {} are also handy, especially if, like me, you want to start reading a new section from the top of the page. Or if the diff contains blocks of moved code (e.g., op's recent 'got path -R' change in 38d61ead4), it's nice to be able to quickly jump to the next hunk. diff refs/heads/main refs/heads/dev/ezdiffnav commit - 10a16316a03005a07c45b2bbf1b5644b64e846fb commit + a9ec599a292f341e598c9030e9271dbd2a9e4624 blob - c09dbe11bb6e0224ed0138a35da047947c82f41d blob + cfe0e187356ff9cb6a95e48ac0be0e82559a946e --- tog/tog.1 +++ tog/tog.1 @@ -290,6 +290,14 @@ Scroll up N half pages (default: 1). Scroll to the top of the view. .It Cm End, G Scroll to the bottom of the view. +.It Cm P +Navigate to the Nth previous file in the diff (default: 1). +.It Cm N +Navigate to the Nth next file in the diff (default: 1). +.It Cm \&{ +Navigate to the Nth previous hunk in the diff (default: 1). +.It Cm \&} +Navigate to the Nth next hunk in the diff (default: 1). .It Cm \&[ Reduce diff context by N lines (default: 1). .It Cm \&] blob - aaca1ae9bd59788b771d3797213ce44b033f50a8 blob + aa20cfdbdcd6dd0083294b8d5c22a281f0ca3755 --- tog/tog.c +++ tog/tog.c @@ -313,12 +313,19 @@ get_color_value(const char *envvar) return default_color_value(envvar); } +struct tog_index { + int *lineno; + int nalloc; + int idx; + int n; +}; struct tog_diff_view_state { struct got_object_id *id1, *id2; const char *label1, *label2; FILE *f, *f1, *f2; int fd1, fd2; + int lineno; int first_displayed_line; int last_displayed_line; int eof; @@ -327,6 +334,7 @@ struct tog_diff_view_state { int force_text_diff; struct got_repository *repo; struct tog_colors colors; + struct tog_index file_idx, hunk_idx; size_t nlines; off_t *line_offsets; int matched_line; @@ -4214,12 +4222,85 @@ done: } static const struct got_error * +tog_alloc_index(struct tog_index *idx) +{ + if (idx->n == idx->nalloc) { + const size_t n = idx->nalloc ? idx->nalloc * 2 : 8; + int *p; + + p = reallocarray(idx->lineno, n, sizeof(int)); + if (p == NULL) { + free(idx->lineno); + idx->lineno = NULL; + return got_error_from_errno("reallocarray"); + } + idx->lineno = p; + idx->nalloc = n; + } + + return NULL; +} + +/* Save line indexes of each file and hunk in the diff. */ +static const struct got_error * +diff_index_file(struct tog_diff_view_state *s) +{ + const struct got_error *err = NULL; + char *line = NULL; + int len, lineno = 0; + size_t sz; + + if (fseeko(s->f, 0L, SEEK_SET) == -1) + return got_error_from_errno("fseeko"); + + while (getline(&line, &sz, s->f) != -1) { + /* match: blob - {/dev/null,SHA1 (mode nnn)} */ + len = strlen(line); + if (!strncmp(line, "blob - ", 7) && + (len == SHA1_DIGEST_STRING_LENGTH + 7 || + len == SHA1_DIGEST_STRING_LENGTH + 18 || len == 17)) { + err = tog_alloc_index(&s->file_idx); + if (err) { + free(line); + return err; + } + s->file_idx.lineno[s->file_idx.n++] = lineno; + } else if (!strncmp(line, "@@ -", 4)) { + err = tog_alloc_index(&s->hunk_idx); + if (err) { + free(line); + return err; + } + s->hunk_idx.lineno[s->hunk_idx.n++] = lineno; + } + ++lineno; + } + free(line); + if (!feof(s->f)) + return got_ferror(s->f, GOT_ERR_IO); + + return NULL; +} + +static void +free_index(struct tog_index *index) +{ + index->n = 0; + index->idx = 0; + index->nalloc = 0; + free(index->lineno); + index->lineno = NULL; +} + +static const struct got_error * create_diff(struct tog_diff_view_state *s) { const struct got_error *err = NULL; FILE *f = NULL; int obj_type; + free_index(&s->file_idx); + free_index(&s->hunk_idx); free(s->line_offsets); s->line_offsets = malloc(sizeof(off_t)); if (s->line_offsets == NULL) @@ -4298,11 +4379,11 @@ create_diff(struct tog_diff_view_state *s) err = got_error(GOT_ERR_OBJ_TYPE); break; } - if (err) - goto done; done: if (s->f && fflush(s->f) != 0 && err == NULL) err = got_error_from_errno("fflush"); + if (err == NULL) + err = diff_index_file(s); return err; } @@ -4421,6 +4502,8 @@ close_diff_view(struct tog_view *view) if (s->fd2 != -1 && close(s->fd2) == -1 && err == NULL) err = got_error_from_errno("close"); s->fd2 = -1; + free_index(&s->file_idx); + free_index(&s->hunk_idx); free_colors(&s->colors); free(s->line_offsets); s->line_offsets = NULL; @@ -4639,6 +4722,45 @@ reset_diff_view(struct tog_view *view) return create_diff(s); } +static int +diff_prev_index(struct tog_diff_view_state *s, struct tog_index *i) +{ + if (!i->lineno) + return s->first_displayed_line; + + if (s->lineno - 1 <= i->lineno[i->idx]) { + if (i->idx == 0 || s->lineno < i->lineno[0]) + i->idx = i->n - 1; + else + while (i->idx > 0 && s->lineno - 1 <= i->lineno[i->idx]) + --i->idx; + } else + while (i->idx < i->n - 1 && s->lineno > i->lineno[i->idx + 1]) + ++i->idx; + + return i->lineno[i->idx] + 1; +} + +static int +diff_next_index(struct tog_diff_view_state *s, struct tog_index *i) +{ + if (!i->lineno) + return s->first_displayed_line; + + if (s->lineno - 1 >= i->lineno[i->idx]) { + if (i->idx == i->n - 1 || s->lineno > i->lineno[i->n - 1]) + i->idx = 0; + else + while (i->idx < i->n - 1 && + s->lineno > i->lineno[i->idx]) + ++i->idx; + } else + while (i->idx > 0 && s->lineno - 1 < i->lineno[i->idx - 1]) + --i->idx; + + return i->lineno[i->idx] + 1; +} + static struct got_object_id *get_selected_commit_id(struct tog_blame_line *, int, int, int); static struct got_object_id *get_annotation_for_line(struct tog_blame_line *, @@ -4656,6 +4778,8 @@ input_diff_view(struct tog_view **new_view, struct tog ssize_t linelen; int i, nscroll = view->nlines - 1, up = 0; + s->lineno = s->first_displayed_line - 1 + s->selected_line; + switch (ch) { case '0': view->x = 0; @@ -4756,6 +4880,22 @@ input_diff_view(struct tog_view **new_view, struct tog } free(line); break; + case 'P': + s->first_displayed_line = diff_prev_index(s, &s->file_idx); + s->selected_line = 1; + break; + case 'N': + s->first_displayed_line = diff_next_index(s, &s->file_idx); + s->selected_line = 1; + break; + case '{': + s->first_displayed_line = diff_prev_index(s, &s->hunk_idx); + s->selected_line = 1; + break; + case '}': + s->first_displayed_line = diff_next_index(s, &s->hunk_idx); + s->selected_line = 1; + break; case '[': if (s->diff_context > 0) { s->diff_context--; -- Mark Jamsek <fnc.bsdbox.org> GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68
tog: keymaps to navigate to prev/next file/hunk in the diff