Download raw body.
[rfc] tog horizontal scroll (diff & blame view)
On 22-06-15 05:23pm, Stefan Sperling wrote: > On Thu, Jun 16, 2022 at 01:04:58AM +1000, Mark Jamsek wrote: > > > Does this make sense? > > > Or is there something wrong in my assumptions? > > > > > > > That's intended; I figured we should still let it scroll assuming the > > user wants to focus on the end of the line. For instance, I tend to like > > keeping my focus on the first third of the screen width. So I've made it > > to scroll so that the end of the line will remain roughly in that area. > > But if you think we should not scroll at all if the line fits on the > > screen, I'm happy to do that too. > > There is no wrong or right way to do this. > I am fine with the way it is behaving now. Thinking some more about it, > it seems nice that horizontal movement is always possible even if the > lines already fit on the screen. This makes it much easier for users > to discover the existence of this feature just by pressing buttons. > Okay, great! That's a good point too. Some users may intuitively try sideways scroll with vim key maps or arrow keys so it's another avenue for learning while using. > My expectations about the behaviour of $ were based on the documentation: > "scrolls to the end of the longest line". > Maybe we should rephrase this? Something like "scrolls the view to the > right-most position", without an explicit hint as to where this position > might be anchored in the scrolling logic? Effectively, $ is just a > short-cut for pressing right-arrow a lot of times, right? > Good idea. I've reworded the docs. Let me know if this is okay. And, yes, $ is a shortcut for l/right-arrow to eol. > > > > +static uint16_t > > > > +expanded_strsz(const char *str, unsigned short n) > > > > +{ > > > > + uint16_t i = 0; > > > > + > > > > + while (str && (str[i] != '\n' && str[i] != '\0')) { > > > > + if (str[i] == '\t') > > > > + n += 8 - (i % 8); /* expand tabs */ > > > > + if ((str[i] & 0xc0) == 0x80) > > > > + --n; /* utf8 continutation byte */ > > > > + ++i; > > > > + } > > > > + > > > > + return n; > > > > > > This code should work fine on OpenBSD. However, Got -portable might > > > be running on systems which use non-UTF8 locales. For this reason, > > > it might be better to use wchar_t and wcwidth() to determine the > > > display length of the expanded version of the string. It would then > > > work on OpenBSD and everywhere else, and less work would be required > > > for the -portable version. > > > > > > > Okay. It's late here and I was just about to hit the sack but I should > > be able to change this tomorrow evening. Thanks, Stefan! > > Sure, no rush. Thank you for working on this. It is a feature I've > always wanted. I even took a stab at this myself once, but did not > get to working code very fast and ran out of motivation. > No worries. I thought of refactoring the view driver to use pads but that would've been a lot of churn, plus it would've been difficult to reimplement the search on top of pads. It's much easier like this, imo. The updated diff is below. Rather than refactor expanded_strsz(), I decided to call format_line() earlier to get the width. I think this is better for a couple reasons: less code; and expanded_strsz() would duplicate a lot of that routine--in most cases, we'll be drawing the line so I think it's more efficient like this. But if you think we should go the other way, I'm happy to do that too, just let me know :) Also, I've added horizontal scroll to the log view now too. Rather than scroll the whole line, we just scroll the log message. I think this is pretty cool, but let me know if you don't like it and we can change it. > I would also appreciate a rebased version of your last-modififed date > patch for the ref view. > I sent the rebased patch for that in the other thread. horizontal scroll for diff, blame, and log views: diff 1b1b91abdb4cd380995e3542580daa8700d93f6f /home/mark/src/git/got blob - 56d92245cc73778b494f7d494ab0ad7560d3fab6 file + tog/tog.1 --- tog/tog.1 +++ tog/tog.1 @@ -104,6 +104,14 @@ are as follows: Move the selection cursor down. .It Cm Up-arrow, k, <, Comma, Ctrl-p Move the selection cursor up. +.It Cm Right-arrow, l +Scroll log message field to the right. Log message moves left on the screen. +.It Cm Left-arrow, h +Scroll log message field to the left. 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, Ctrl+f Move the selection cursor down one page. .It Cm Page-up, Ctrl+b @@ -219,6 +227,14 @@ detected. Scroll down. .It Cm Up-arrow, k, Ctrl-p Scroll up. +.It Cm Right-arrow, l +Scroll view to the right. Diff output moves left on the screen. +.It Cm Left-arrow, h +Scroll view to the left. 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 Scroll down one page. .It Cm Page-up, Ctrl+b @@ -290,6 +306,14 @@ are as follows: Move the selection cursor down. .It Cm Up-arrow, k, Ctrl-p Move the selection cursor up. +.It Cm Right-arrow, l +Scroll view to the right. File output moves left on the screen. +.It Cm Left-arrow, h +Scroll view to the left. 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 Move the selection cursor down one page. .It Cm Page-up, Ctrl+b blob - 48c3db4951d9e65f17f32a087f88342694e36c1f file + tog/tog.c --- tog/tog.c +++ tog/tog.c @@ -500,6 +500,7 @@ struct tog_view { WINDOW *window; PANEL *panel; 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 focussed; /* Only set on one parent or child view at a time. */ int dying; @@ -1223,21 +1224,62 @@ done: return err; } +static const struct got_error * +expand_tab(char **ptr, const char *src) +{ + char *dst; + size_t len, n, idx = 0, sz = 0; + + *ptr = NULL; + n = len = strlen(src); + dst = malloc((n + 1) * sizeof(char)); + if (dst == NULL) + return got_error_from_errno("malloc"); + + while (idx < len && src[idx]) { + const char c = src[idx]; + + if (c == '\t') { + size_t nb = TABSIZE - sz % TABSIZE; + n += nb; + dst = reallocarray(dst, n, sizeof(char)); + if (dst == NULL) + return got_error_from_errno("reallocarray"); + memcpy(dst + sz, " ", nb); + sz += nb; + } else + dst[sz++] = src[idx]; + ++idx; + } + + dst[sz] = '\0'; + *ptr = dst; + return NULL; +} + /* Format a line for display, ensuring that it won't overflow a width limit. */ static const struct got_error * format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit, - int col_tab_align) + int col_tab_align, int expand) { const struct got_error *err = NULL; int cols = 0; wchar_t *wline = NULL; + char *exstr = NULL; size_t wlen; int i; *wlinep = NULL; *widthp = 0; - err = mbs2ws(&wline, &wlen, line); + if (expand) { + err = expand_tab(&exstr, line); + if (err) + return err; + } + + err = mbs2ws(&wline, &wlen, expand ? exstr : line); + free(exstr); if (err) return err; @@ -1370,7 +1412,8 @@ format_author(wchar_t **wauthor, int *author_width, ch if (smallerthan && smallerthan[1] != '\0') author = smallerthan + 1; author[strcspn(author, "@>")] = '\0'; - return format_line(wauthor, author_width, author, limit, col_tab_align); + return format_line(wauthor, author_width, author, limit, col_tab_align, + 0); } static const struct got_error * @@ -1467,12 +1510,15 @@ draw_commit(struct tog_view *view, struct got_commit_o newline = strchr(logmsg, '\n'); if (newline) *newline = '\0'; - limit = avail - col; - err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col); + limit = view->x + avail - col; + err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col, 1); if (err) goto done; - waddwstr(view->window, wlogmsg); - col += logmsg_width; + if (view->x < logmsg_width - 1) + waddwstr(view->window, wlogmsg + view->x); + else + logmsg_width = 0; + col += MAX(logmsg_width - view->x, 0); while (col < avail) { waddch(view->window, ' '); col++; @@ -1709,7 +1755,7 @@ draw_commits(struct tog_view *view) header = NULL; goto done; } - err = format_line(&wline, &width, header, view->ncols, 0); + err = format_line(&wline, &width, header, view->ncols, 0, 0); if (err) goto done; @@ -1738,8 +1784,9 @@ draw_commits(struct tog_view *view) /* Grow author column size if necessary. */ entry = s->first_displayed_entry; ncommits = 0; + view->maxx = 0; while (entry) { - char *author; + char *author, *eol, *msg, *msg0; wchar_t *wauthor; int width; if (ncommits >= limit - 1) @@ -1755,6 +1802,17 @@ draw_commits(struct tog_view *view) author_cols = width; free(wauthor); free(author); + err = got_object_commit_get_logmsg(&msg0, entry->commit); + if (err) + goto done; + msg = msg0; + while (*msg == '\n') + ++msg; + if ((eol = strchr(msg, '\n'))) + view->maxx = MAX(view->maxx, eol - msg); + else + view->maxx = MAX(view->maxx, strlen(msg)); + free(msg0); ncommits++; entry = TAILQ_NEXT(entry, entry); } @@ -2476,6 +2534,21 @@ input_log_view(struct tog_view **new_view, struct tog_ case 'q': s->quit = 1; break; + case '0': + view->x = 0; + break; + case '$': + view->x = MAX(view->maxx - view->ncols / 2, 0); + break; + case KEY_RIGHT: + case 'l': + if (view->x + view->ncols / 2 < view->maxx) + view->x += 2; /* move two columns right */ + break; + case KEY_LEFT: + case 'h': + view->x -= MIN(view->x, 2); /* move two columns back */ + break; case 'k': case KEY_UP: case '<': @@ -2979,62 +3052,55 @@ match_color(struct tog_colors *colors, const char *lin static const struct got_error * add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align, - WINDOW *window, regmatch_t *regmatch) + WINDOW *window, int skip, regmatch_t *regmatch) { const struct got_error *err = NULL; wchar_t *wline; - int width; - char *s; + int rme, rms, n, width; *wtotal = 0; + rms = regmatch->rm_so; + rme = regmatch->rm_eo; - s = strndup(line, regmatch->rm_so); - if (s == NULL) - return got_error_from_errno("strndup"); - - err = format_line(&wline, &width, s, wlimit, col_tab_align); - if (err) { - free(s); + err = format_line(&wline, &width, line, wlimit + skip, + col_tab_align, 1); + if (err) return err; + + /* draw up to matched token if we haven't scrolled past it */ + n = MAX(rms - skip, 0); + if (n) { + waddnwstr(window, wline + skip, n); + wlimit -= n; + *wtotal += n; } - waddwstr(window, wline); - free(wline); - free(s); - wlimit -= width; - *wtotal += width; if (wlimit > 0) { - s = strndup(line + regmatch->rm_so, - regmatch->rm_eo - regmatch->rm_so); - if (s == NULL) { - err = got_error_from_errno("strndup"); - free(s); - return err; + int len = rme - rms; + n = 0; + if (skip > rms) { + n = skip - rms; + len = MAX(len - n, 0); } - err = format_line(&wline, &width, s, wlimit, col_tab_align); - if (err) { - free(s); - return err; + /* draw (visible part of) matched token (if scrolled into it) */ + if (len) { + wattron(window, A_STANDOUT); + waddnwstr(window, wline + rms + n, len); + wattroff(window, A_STANDOUT); + wlimit -= len; + *wtotal += len; } - wattr_on(window, A_STANDOUT, NULL); - waddwstr(window, wline); - wattr_off(window, A_STANDOUT, NULL); - free(wline); - free(s); - wlimit -= width; - *wtotal += width; } - if (wlimit > 0 && strlen(line) > regmatch->rm_eo) { - err = format_line(&wline, &width, - line + regmatch->rm_eo, wlimit, col_tab_align); - if (err) - return err; - waddwstr(window, wline); - free(wline); - *wtotal += width; + if (wlimit > 0 && skip < width) { /* draw rest of line */ + n = 0; + if (skip > rme) + n = MIN(skip - rme, width - rme); + waddnwstr(window, wline + rme + n, wlimit); } + *wtotal = width; + free(wline); return NULL; } @@ -3066,7 +3132,7 @@ draw_file(struct tog_view *view, const char *header) s->first_displayed_line - 1 + s->selected_line, nlines, header) == -1) return got_error_from_errno("asprintf"); - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); free(line); if (err) return err; @@ -3087,6 +3153,7 @@ draw_file(struct tog_view *view, const char *header) } s->eof = 0; + view->maxx = 0; line = NULL; while (max_lines > 0 && nprinted < max_lines) { linelen = getline(&line, &linesize, s->f); @@ -3099,6 +3166,8 @@ draw_file(struct tog_view *view, const char *header) return got_ferror(s->f, GOT_ERR_IO); } + view->maxx = MAX(view->maxx, linelen); + tc = match_color(&s->colors, line); if (tc) wattr_on(view->window, @@ -3106,25 +3175,27 @@ draw_file(struct tog_view *view, const char *header) 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, - view->window, regmatch); + view->window, view->x, regmatch); if (err) { free(line); return err; } } else { - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, + view->x + view->ncols, 0, view->x ? 1 : 0); if (err) { free(line); return err; } - waddwstr(view->window, wline); + if (view->x < width - 1) + waddwstr(view->window, wline + view->x); free(wline); wline = NULL; } if (tc) wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL); - if (width <= view->ncols - 1) + if (width - view->x <= view->ncols - 1) waddch(view->window, '\n'); nprinted++; } @@ -3143,7 +3214,8 @@ draw_file(struct tog_view *view, const char *header) nprinted++; } - err = format_line(&wline, &width, TOG_EOF_STRING, view->ncols, 0); + err = format_line(&wline, &width, TOG_EOF_STRING, view->ncols, + 0, 0); if (err) { return err; } @@ -3512,8 +3584,9 @@ static const struct got_error * search_next_diff_view(struct tog_view *view) { struct tog_diff_view_state *s = &view->state.diff; + const struct got_error *err = NULL; int lineno; - char *line = NULL; + char *exstr = NULL, *line = NULL; size_t linesize = 0; ssize_t linelen; @@ -3551,25 +3624,31 @@ search_next_diff_view(struct tog_view *view) return got_error_from_errno("fseeko"); } linelen = getline(&line, &linesize, s->f); + err = expand_tab(&exstr, line); + if (err) + break; if (linelen != -1 && - match_line(line, &view->regex, 1, &view->regmatch)) { + match_line(exstr, &view->regex, 1, &view->regmatch)) { view->search_next_done = TOG_SEARCH_HAVE_MORE; s->matched_line = lineno; break; } + free(exstr); + exstr = NULL; if (view->searching == TOG_SEARCH_FORWARD) lineno++; else lineno--; } free(line); + free(exstr); if (s->matched_line) { s->first_displayed_line = s->matched_line; s->selected_line = 1; } - return NULL; + return err; } static const struct got_error * @@ -3790,6 +3869,21 @@ input_diff_view(struct tog_view **new_view, struct tog int i, nscroll = view->nlines - 1; switch (ch) { + case '0': + view->x = 0; + break; + case '$': + view->x = MAX(view->maxx - view->ncols / 3, 0); + break; + case KEY_RIGHT: + case 'l': + if (view->x + view->ncols / 3 < view->maxx) + view->x += 2; /* move two columns right */ + break; + case KEY_LEFT: + case 'h': + view->x -= MIN(view->x, 2); /* move two columns back */ + break; case 'a': case 'w': if (ch == 'a') @@ -3904,6 +3998,7 @@ input_diff_view(struct tog_view **new_view, struct tog s->first_displayed_line = 1; s->last_displayed_line = view->nlines; s->matched_line = 0; + view->x = 0; diff_view_indicate_progress(view); err = create_diff(s); @@ -3929,6 +4024,7 @@ input_diff_view(struct tog_view **new_view, struct tog s->first_displayed_line = 1; s->last_displayed_line = view->nlines; s->matched_line = 0; + view->x = 0; diff_view_indicate_progress(view); err = create_diff(s); @@ -4118,7 +4214,7 @@ draw_blame(struct tog_view *view) return err; } - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); free(line); line = NULL; if (err) @@ -4147,7 +4243,7 @@ draw_blame(struct tog_view *view) return got_error_from_errno("asprintf"); } free(id_str); - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); free(line); line = NULL; if (err) @@ -4159,6 +4255,7 @@ draw_blame(struct tog_view *view) waddch(view->window, '\n'); s->eof = 0; + view->maxx = 0; while (nprinted < view->nlines - 2) { linelen = getline(&line, &linesize, blame->f); if (linelen == -1) { @@ -4172,6 +4269,8 @@ draw_blame(struct tog_view *view) if (++lineno < s->first_displayed_line) continue; + view->maxx = MAX(view->maxx, linelen); + if (view->focussed && nprinted == s->selected_line - 1) wstandout(view->window); @@ -4224,7 +4323,7 @@ draw_blame(struct tog_view *view) s->matched_line && regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) { err = add_matched_line(&width, line, view->ncols - 9, 9, - view->window, regmatch); + view->window, view->x, regmatch); if (err) { free(line); return err; @@ -4232,11 +4331,15 @@ draw_blame(struct tog_view *view) width += 9; } else { err = format_line(&wline, &width, line, - view->ncols - 9, 9); - waddwstr(view->window, wline); + view->x + view->ncols - 9, 9, 1); + if (!err && view->x < width - 1) { + waddwstr(view->window, wline + view->x); + width += 9; + } free(wline); wline = NULL; - width += 9; + if (err) + return err; } if (width <= view->ncols - 1) @@ -4603,8 +4706,9 @@ static const struct got_error * search_next_blame_view(struct tog_view *view) { struct tog_blame_view_state *s = &view->state.blame; + const struct got_error *err = NULL; int lineno; - char *line = NULL; + char *exstr = NULL, *line = NULL; size_t linesize = 0; ssize_t linelen; @@ -4642,25 +4746,31 @@ search_next_blame_view(struct tog_view *view) return got_error_from_errno("fseeko"); } linelen = getline(&line, &linesize, s->blame.f); + err = expand_tab(&exstr, line); + if (err) + break; if (linelen != -1 && - match_line(line, &view->regex, 1, &view->regmatch)) { + match_line(exstr, &view->regex, 1, &view->regmatch)) { view->search_next_done = TOG_SEARCH_HAVE_MORE; s->matched_line = lineno; break; } + free(exstr); + exstr = NULL; if (view->searching == TOG_SEARCH_FORWARD) lineno++; else lineno--; } free(line); + free(exstr); if (s->matched_line) { s->first_displayed_line = s->matched_line; s->selected_line = 1; } - return NULL; + return err; } static const struct got_error * @@ -4697,6 +4807,21 @@ input_blame_view(struct tog_view **new_view, struct to int begin_x = 0, nscroll = view->nlines - 2; switch (ch) { + case '0': + view->x = 0; + break; + case '$': + view->x = MAX(view->maxx - view->ncols / 3, 0); + break; + case KEY_RIGHT: + case 'l': + if (view->x + view->ncols / 3 < view->maxx) + view->x += 2; /* move two columns right */ + break; + case KEY_LEFT: + case 'h': + view->x -= MIN(view->x, 2); /* move two columns back */ + break; case 'q': s->done = 1; break; @@ -5079,7 +5204,7 @@ draw_tree_entries(struct tog_view *view, const char *p if (limit == 0) return NULL; - err = format_line(&wline, &width, s->tree_label, view->ncols, 0); + err = format_line(&wline, &width, s->tree_label, view->ncols, 0, 0); if (err) return err; if (view_needs_focus_indication(view)) @@ -5100,7 +5225,7 @@ draw_tree_entries(struct tog_view *view, const char *p waddch(view->window, '\n'); if (--limit <= 0) return NULL; - err = format_line(&wline, &width, parent_path, view->ncols, 0); + err = format_line(&wline, &width, parent_path, view->ncols, 0, 0); if (err) return err; waddwstr(view->window, wline); @@ -5180,7 +5305,7 @@ draw_tree_entries(struct tog_view *view, const char *p } free(id_str); free(link_target); - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); if (err) { free(line); break; @@ -6291,7 +6416,7 @@ show_ref_view(struct tog_view *view) s->nrefs) == -1) return got_error_from_errno("asprintf"); - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); if (err) { free(line); return err; @@ -6345,7 +6470,7 @@ show_ref_view(struct tog_view *view) return got_error_from_errno("strdup"); } - err = format_line(&wline, &width, line, view->ncols, 0); + err = format_line(&wline, &width, line, view->ncols, 0, 0); if (err) { free(line); return err; -- Mark Jamsek GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68
[rfc] tog horizontal scroll (diff & blame view)