From: Stefan Sperling Subject: fix diff+blame view search for horizontal scrolling with unicode To: gameoftrees@openbsd.org Date: Thu, 16 Jun 2022 22:20:40 +0200 This makes horizontal scrolling work on lines matched while searching, even when matched lines contain unicode. The regex matches on the underlying bytestring. Since a valid pattern is unlikely to match multi-byte characters in the middle, we can split the line into 3 segments, before, within, and after the matched region, and then convert each segment to wide characters separately. In my testing I can succesfully match and highlight english text as well as copy-pasted japanese text. Horizontal scrolling seems to work correctly in either case. ok? diff 2e5be6f6aff8a33d8189b76f25c2bec556de60c3 eb4ee4d26869424aab2f102bd9b156fdab1a39e7 blob - bdff5844ccda157277e95325887def00e1f69e73 blob + 7abd8ac2deaa36fa7cc9dc16462f7acb50b71f78 --- tog/tog.c +++ tog/tog.c @@ -3122,56 +3122,107 @@ 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, int skip, regmatch_t *regmatch) + WINDOW *window, int skipcol, regmatch_t *regmatch) { const struct got_error *err = NULL; - wchar_t *wline; - int rme, rms, n, width; + wchar_t *wline = NULL; + int rme, rms, n, width, scrollx; + int width0 = 0, width1 = 0, width2 = 0; + char *seg0 = NULL, *seg1 = NULL, *seg2 = NULL; *wtotal = 0; + rms = regmatch->rm_so; rme = regmatch->rm_eo; - err = format_line(&wline, &width, NULL, line, 0, wlimit + skip, - col_tab_align, 1); - if (err) - return err; + /* Split the line into 3 segments, according to match offsets. */ + seg0 = strndup(line, rms); + if (seg0 == NULL) + return got_error_from_errno("strndup"); + seg1 = strndup(line + rms, rme - rms); + if (seg1 == NULL) { + err = got_error_from_errno("strndup"); + goto done; + } + seg2 = strdup(line + rme); + if (seg1 == NULL) { + err = got_error_from_errno("strndup"); + goto done; + } /* draw up to matched token if we haven't scrolled past it */ - n = MAX(rms - skip, 0); + err = format_line(&wline, &width0, NULL, seg0, 0, wlimit, + col_tab_align, 1); + if (err) + goto done; + n = MAX(width0 - skipcol, 0); if (n) { - waddnwstr(window, wline + skip, n); - wlimit -= n; - *wtotal += n; + free(wline); + err = format_line(&wline, &width, &scrollx, seg0, skipcol, + wlimit, col_tab_align, 1); + if (err) + goto done; + waddwstr(window, &wline[scrollx]); + wlimit -= width; + *wtotal += width; } if (wlimit > 0) { - int len = rme - rms; - n = 0; - if (skip > rms) { - n = skip - rms; - len = MAX(len - n, 0); + int i = 0, w = 0; + size_t wlen; + + free(wline); + err = format_line(&wline, &width1, NULL, seg1, 0, wlimit, + col_tab_align, 1); + if (err) + goto done; + wlen = wcslen(wline); + while (i < wlen) { + width = wcwidth(wline[i]); + if (width == -1) { + /* should not happen, tabs are expanded */ + err = got_error(GOT_ERR_RANGE); + goto done; + } + if (width0 + w + width > skipcol) + break; + w += width; + i++; } /* draw (visible part of) matched token (if scrolled into it) */ - if (len) { + if (width1 - w > 0) { wattron(window, A_STANDOUT); - waddnwstr(window, wline + rms + n, len); + waddwstr(window, &wline[i]); wattroff(window, A_STANDOUT); - wlimit -= len; - *wtotal += len; + wlimit -= (width1 - w); + *wtotal += (width1 - w); } } - 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); + if (wlimit > 0) { /* draw rest of line */ + free(wline); + if (skipcol > width0 + width1) { + err = format_line(&wline, &width2, &scrollx, seg2, + skipcol - (width0 + width1), wlimit, + col_tab_align, 1); + if (err) + goto done; + waddwstr(window, &wline[scrollx]); + } else { + err = format_line(&wline, &width2, NULL, seg2, 0, + wlimit, col_tab_align, 1); + if (err) + goto done; + waddwstr(window, wline); + } + *wtotal += width2; } - - *wtotal = width; +done: free(wline); - return NULL; + free(seg0); + free(seg1); + free(seg2); + return err; } static const struct got_error * @@ -3237,6 +3288,17 @@ draw_file(struct tog_view *view, const char *header) return got_ferror(s->f, GOT_ERR_IO); } + /* Set view->maxx based on full line length. */ + err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, + view->x ? 1 : 0); + if (err) { + free(line); + return err; + } + view->maxx = MAX(view->maxx, width); + free(wline); + wline = NULL; + tc = match_color(&s->colors, line); if (tc) wattr_on(view->window, @@ -3249,24 +3311,8 @@ draw_file(struct tog_view *view, const char *header) free(line); return err; } - view->maxx = MAX(view->maxx, width); } else { int skip; - - /* Set view->maxx based on full line length. */ - err = format_line(&wline, &width, NULL, line, - 0, INT_MAX, 0, view->x ? 1 : 0); - if (err) { - free(line); - return err; - } - view->maxx = MAX(view->maxx, width); - - /* - * Get a new version of the line for display. - * This will be scrolled and/or trimmed in length. - */ - free(wline); err = format_line(&wline, &width, &skip, line, view->x, view->ncols, 0, view->x ? 1 : 0); if (err) { @@ -4355,6 +4401,16 @@ draw_blame(struct tog_view *view) if (++lineno < s->first_displayed_line) continue; + /* Set view->maxx based on full line length. */ + err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 9, 1); + if (err) { + free(line); + return err; + } + free(wline); + wline = NULL; + view->maxx = MAX(view->maxx, width); + if (view->focussed && nprinted == s->selected_line - 1) wstandout(view->window); @@ -4398,12 +4454,6 @@ draw_blame(struct tog_view *view) if (view->ncols <= 9) { width = 9; - wline = wcsdup(L""); - if (wline == NULL) { - err = got_error_from_errno("wcsdup"); - free(line); - return err; - } } else if (s->first_displayed_line + nprinted == s->matched_line && regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) { @@ -4413,25 +4463,9 @@ draw_blame(struct tog_view *view) free(line); return err; } - view->maxx = MAX(view->maxx, width); width += 9; } else { int skip; - - /* Set view->maxx based on full line length. */ - err = format_line(&wline, &width, NULL, line, - 0, INT_MAX, 9, 1); - if (err) { - free(line); - return err; - } - view->maxx = MAX(view->maxx, width); - - /* - * Get a new version of the line for display. - * This will be scrolled and/or trimmed in length. - */ - free(wline); err = format_line(&wline, &width, &skip, line, view->x, view->ncols - 9, 9, 1); if (err) {