"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Mark Jamsek <mark@jamsek.com>
Subject:
[rfc] tog horizontal scroll (diff & blame view)
To:
gameoftrees@openbsd.org
Date:
Thu, 16 Jun 2022 00:04:17 +1000

Download raw body.

Thread
This patch introduces support for horizontal scrolling in diff and blame
views.

New key maps:

h - scroll left (left arrow works too)
l - scroll right (right arrow works too)
0 - scroll to the beginning of the line
$ - scroll to the end of longest line on screen

diff --git a/tog/tog.1 b/tog/tog.1
index 56d92245..d54d2135 100644
--- a/tog/tog.1
+++ b/tog/tog.1
@@ -219,6 +219,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 right to the end of the longest line on the screen.
+.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 +298,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-arroe, h
+Scroll view to the left.  File output moves right on the screen.
+.It Cm $
+Scroll view right to the end of the longest line on the screen.
+.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
diff --git a/tog/tog.c b/tog/tog.c
index d0d58364..6fbd65c9 100644
--- a/tog/tog.c
+++ b/tog/tog.c
@@ -498,6 +498,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;
@@ -1221,21 +1222,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;

@@ -1368,7 +1410,8 @@ format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
 	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 *
@@ -1466,7 +1509,7 @@ draw_commit(struct tog_view *view, struct got_commit_object *commit,
 	if (newline)
 		*newline = '\0';
 	limit = avail - col;
-	err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col);
+	err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col, 0);
 	if (err)
 		goto done;
 	waddwstr(view->window, wlogmsg);
@@ -1707,7 +1750,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;

@@ -2951,62 +2994,55 @@ match_color(struct tog_colors *colors, const char *line)

 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;
 }

@@ -3038,7 +3074,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;
@@ -3059,6 +3095,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);
@@ -3071,6 +3108,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,
@@ -3078,25 +3117,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++;
 	}
@@ -3115,7 +3156,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;
 		}
@@ -3484,8 +3526,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;

@@ -3523,25 +3566,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 *
@@ -3762,6 +3811,21 @@ input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
 	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')
@@ -3876,6 +3940,7 @@ input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
 		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);
@@ -3901,6 +3966,7 @@ input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
 		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);
@@ -4048,6 +4114,22 @@ struct tog_blame_line {
 	struct got_object_id *id;
 };

+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;
+}
+
 static const struct got_error *
 draw_blame(struct tog_view *view)
 {
@@ -4079,7 +4161,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)
@@ -4108,7 +4190,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)
@@ -4120,6 +4202,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) {
@@ -4133,6 +4216,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);

@@ -4185,16 +4270,16 @@ 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;
 			}
 			width += 9;
-		} else {
+		} else if (view->x < expanded_strsz(line, linelen) - 1) {
 			err = format_line(&wline, &width, line,
-			    view->ncols - 9, 9);
-			waddwstr(view->window, wline);
+			    view->x + view->ncols - 9, 9, 1);
+			waddwstr(view->window, wline + view->x);
 			free(wline);
 			wline = NULL;
 			width += 9;
@@ -4553,8 +4638,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;

@@ -4592,25 +4678,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 *
@@ -4647,6 +4739,21 @@ input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
 	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;
@@ -5018,7 +5125,7 @@ draw_tree_entries(struct tog_view *view, const char *parent_path)
 	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))
@@ -5039,7 +5146,7 @@ draw_tree_entries(struct tog_view *view, const char *parent_path)
 		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);
@@ -5119,7 +5226,7 @@ draw_tree_entries(struct tog_view *view, const char *parent_path)
 		}
 		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;
@@ -6219,7 +6326,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;
@@ -6273,7 +6380,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