diff options
author | Nicholas Marriott <nicm@cvs.openbsd.org> | 2017-11-15 19:21:25 +0000 |
---|---|---|
committer | Nicholas Marriott <nicm@cvs.openbsd.org> | 2017-11-15 19:21:25 +0000 |
commit | dd19a0f04db7657e686ec49283ddf67b1ca67ffb (patch) | |
tree | bd6acbebc74c28c1ec621bad0c29f2d9be11aa35 /usr.bin | |
parent | b2747378564f0d3d1877c17fcb354cfe59c5055b (diff) |
Completely rewrite the reflow code to correctly handle double width
characters (previously they were not accounted for).
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/tmux/grid.c | 413 | ||||
-rw-r--r-- | usr.bin/tmux/screen-write.c | 7 | ||||
-rw-r--r-- | usr.bin/tmux/screen.c | 13 | ||||
-rw-r--r-- | usr.bin/tmux/tmux.h | 4 |
4 files changed, 262 insertions, 175 deletions
diff --git a/usr.bin/tmux/grid.c b/usr.bin/tmux/grid.c index 9832e75b150..233d3e2a199 100644 --- a/usr.bin/tmux/grid.c +++ b/usr.bin/tmux/grid.c @@ -1,4 +1,4 @@ -/* $OpenBSD: grid.c,v 1.78 2017/11/03 17:02:33 nicm Exp $ */ +/* $OpenBSD: grid.c,v 1.79 2017/11/15 19:21:24 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -43,22 +43,8 @@ static const struct grid_cell_entry grid_default_entry = { 0, { .data = { 0, 8, 8, ' ' } } }; -static void grid_expand_line(struct grid *, u_int, u_int, u_int); static void grid_empty_line(struct grid *, u_int, u_int); -static void grid_reflow_copy(struct grid_line *, u_int, struct grid_line *, - u_int, u_int); -static void grid_reflow_join(struct grid *, u_int *, struct grid_line *, - u_int); -static void grid_reflow_split(struct grid *, u_int *, struct grid_line *, - u_int, u_int); -static void grid_reflow_move(struct grid *, u_int *, struct grid_line *); - -static size_t grid_string_cells_fg(const struct grid_cell *, int *); -static size_t grid_string_cells_bg(const struct grid_cell *, int *); -static void grid_string_cells_code(const struct grid_cell *, - const struct grid_cell *, char *, size_t, int); - /* Store cell in entry. */ static void grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, @@ -423,20 +409,11 @@ grid_peek_line(struct grid *gd, u_int py) return (&gd->linedata[py]); } -/* Get cell for reading. */ -void -grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) +/* Get cell from line. */ +static void +grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) { - struct grid_line *gl; - struct grid_cell_entry *gce; - - if (grid_check_y(gd, py) != 0 || px >= gd->linedata[py].cellsize) { - memcpy(gc, &grid_default_cell, sizeof *gc); - return; - } - - gl = &gd->linedata[py]; - gce = &gl->celldata[px]; + struct grid_cell_entry *gce = &gl->celldata[px]; if (gce->flags & GRID_FLAG_EXTENDED) { if (gce->offset >= gl->extdsize) @@ -457,6 +434,17 @@ grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) utf8_set(&gc->data, gce->data.data); } +/* Get cell for reading. */ +void +grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) +{ + if (grid_check_y(gd, py) != 0 || px >= gd->linedata[py].cellsize) { + memcpy(gc, &grid_default_cell, sizeof *gc); + return; + } + return (grid_get_cell1(&gd->linedata[py], px, gc)); +} + /* Set cell at relative position. */ void grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) @@ -953,164 +941,269 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, } } -/* Copy a section of a line. */ +/* Join line below onto this one. */ static void -grid_reflow_copy(struct grid_line *dst_gl, u_int to, struct grid_line *src_gl, - u_int from, u_int to_copy) +grid_reflow_join(struct grid *gd, u_int sx, u_int yy, u_int width, u_int *cy) { - struct grid_cell_entry *gce; - u_int i, was; + struct grid_line *gl = &gd->linedata[yy], *from; + struct grid_cell gc; + u_int lines, want, left, i, at = gl->cellused, line; + int wrapped = 1; + + lines = 0; + for (;;) { + /* + * If this is now the last line, there is nothing more to be + * done. + */ + if (yy + lines == gd->hsize + gd->sy) + break; + line = yy + 1 + lines; + + /* If the next line is empty, skip it. */ + if (~gd->linedata[line].flags & GRID_LINE_WRAPPED) + wrapped = 0; + if (gd->linedata[line].cellused == 0) { + if (!wrapped) + break; + continue; + } - memcpy(&dst_gl->celldata[to], &src_gl->celldata[from], - to_copy * sizeof *dst_gl->celldata); + /* + * Is the destination line now full? Copy the first character + * separately because we need to leave "from" set to the last + * line if this line is full. + */ + grid_get_cell1(&gd->linedata[line], 0, &gc); + if (width + gc.data.width > sx) + break; + width += gc.data.width; + grid_set_cell(gd, at, yy, &gc); + at++; + + /* Join as much more as possible onto the current line. */ + from = &gd->linedata[line]; + for (want = 1; want < from->cellused; want++) { + grid_get_cell1(from, want, &gc); + if (width + gc.data.width > sx) + break; + width += gc.data.width; + + grid_set_cell(gd, at, yy, &gc); + at++; + } + lines++; - for (i = to; i < to + to_copy; i++) { - gce = &dst_gl->celldata[i]; - if (~gce->flags & GRID_FLAG_EXTENDED) - continue; - was = gce->offset; + /* + * If this line wasn't wrapped or we didn't consume the entire + * line, don't try to join any further lines. + */ + if (!wrapped || want != from->cellused || width == sx) + break; + } + if (lines == 0) + return; - dst_gl->extddata = xreallocarray(dst_gl->extddata, - dst_gl->extdsize + 1, sizeof *dst_gl->extddata); - gce->offset = dst_gl->extdsize++; - memcpy(&dst_gl->extddata[gce->offset], &src_gl->extddata[was], - sizeof *dst_gl->extddata); + /* + * If we didn't consume the entire final line, then remove what we did + * consume. If we consumed the entire line and it wasn't wrapped, + * remove the wrap flag from this line. + */ + left = from->cellused - want; + if (left != 0) { + grid_move_cells(gd, 0, want, yy + lines, left, 8); + from->cellsize = from->cellused = left; + lines--; + } else if (!wrapped) + gl->flags &= ~GRID_LINE_WRAPPED; + + /* Remove the lines that were completely consumed. */ + if (lines != 0) { + if (yy + lines != gd->hsize + gd->sy) { + memmove(&gd->linedata[yy + 1], + &gd->linedata[yy + lines + 1], + ((gd->hsize + gd->sy) - (yy + lines + 1)) * + (sizeof *gd->linedata)); + } + if (gd->hsize >= lines) + gd->hsize -= lines; + else { + lines -= gd->hsize; + gd->hsize = 0; + for (i = 1; i < lines + 1; i++) + grid_empty_line(gd, gd->hsize + gd->sy - i, 8); + } } + + /* Adjust cursor and scroll positions. */ + if (*cy > yy + lines) + *cy -= lines; + else if (*cy > yy) + *cy = yy; + if (gd->hscrolled > yy + lines) + gd->hscrolled -= lines; + else if (gd->hscrolled > yy) + gd->hscrolled = yy; } -/* Join line data. */ +/* Split this line into several new ones */ static void -grid_reflow_join(struct grid *dst, u_int *py, struct grid_line *src_gl, - u_int new_x) +grid_reflow_split(struct grid *gd, u_int sx, u_int yy, u_int at, u_int *cy) { - struct grid_line *dst_gl = &dst->linedata[(*py) - 1]; - u_int left, to_copy, ox, nx; - - /* How much is left on the old line? */ - left = new_x - dst_gl->cellused; - - /* Work out how much to append. */ - to_copy = src_gl->cellused; - if (to_copy > left) - to_copy = left; - ox = dst_gl->cellused; - nx = ox + to_copy; - - /* Resize the destination line. */ - dst_gl->celldata = xreallocarray(dst_gl->celldata, nx, - sizeof *dst_gl->celldata); - dst_gl->cellsize = dst_gl->cellused = nx; - - /* Append as much as possible. */ - grid_reflow_copy(dst_gl, ox, src_gl, 0, to_copy); + struct grid_line *gl = &gd->linedata[yy]; + struct grid_cell gc; + u_int line, lines, width, i, used = gl->cellused, xx; + int flags = gl->flags; + + /* How many lines do we need to insert? We know we need at least one. */ + if (~gl->flags & GRID_LINE_EXTENDED) + lines = (gl->cellused - 1) / sx; + else { + lines = 1; + width = 0; + for (i = at; i < used; i++) { + grid_get_cell1(gl, i, &gc); + if (width + gc.data.width > sx) { + lines++; + width = 0; + } + width += gc.data.width; + } + } - /* If there is any left in the source, split it. */ - if (src_gl->cellused > to_copy) { - dst_gl->flags |= GRID_LINE_WRAPPED; + /* Trim the original line size. */ + gl->cellsize = gl->cellused = at; + gl->flags |= GRID_LINE_WRAPPED; - src_gl->cellused -= to_copy; - grid_reflow_split(dst, py, src_gl, new_x, to_copy); + /* Insert new lines. */ + gd->linedata = xreallocarray(gd->linedata, gd->hsize + gd->sy + lines, + sizeof *gd->linedata); + memmove(&gd->linedata[yy + lines + 1], &gd->linedata[yy + 1], + ((gd->hsize + gd->sy) - (yy + 1)) * (sizeof *gd->linedata)); + gd->hsize += lines; + for (i = 0; i < lines; i++) + grid_empty_line(gd, yy + 1 + i, 8); + gl = &gd->linedata[yy]; + + /* Copy sections from the original line. */ + line = yy + 1; + width = 0; + xx = 0; + for (i = at; i < used; i++) { + grid_get_cell1(gl, i, &gc); + if (width + gc.data.width > sx) { + gd->linedata[line].flags |= GRID_LINE_WRAPPED; + + line++; + width = 0; + xx = 0; + } + width += gc.data.width; + grid_set_cell(gd, xx, line, &gc); + xx++; } -} + if (flags & GRID_LINE_WRAPPED) + gd->linedata[line].flags |= GRID_LINE_WRAPPED; -/* Split line data. */ -static void -grid_reflow_split(struct grid *dst, u_int *py, struct grid_line *src_gl, - u_int new_x, u_int offset) -{ - struct grid_line *dst_gl = NULL; - u_int to_copy; - - /* Loop and copy sections of the source line. */ - while (src_gl->cellused > 0) { - /* Create new line. */ - if (*py >= dst->hsize + dst->sy) - grid_scroll_history(dst, 8); - dst_gl = &dst->linedata[*py]; - (*py)++; - - /* How much should we copy? */ - to_copy = new_x; - if (to_copy > src_gl->cellused) - to_copy = src_gl->cellused; - - /* Expand destination line. */ - dst_gl->celldata = xreallocarray(NULL, to_copy, - sizeof *dst_gl->celldata); - dst_gl->cellsize = dst_gl->cellused = to_copy; - dst_gl->flags |= GRID_LINE_WRAPPED; - - /* Copy the data. */ - grid_reflow_copy(dst_gl, 0, src_gl, offset, to_copy); - - /* Move offset and reduce old line size. */ - offset += to_copy; - src_gl->cellused -= to_copy; - } + /* Adjust the cursor and scroll positions. */ + if (yy <= *cy) + (*cy) += lines; + if (yy <= gd->hscrolled) + gd->hscrolled += lines; - /* Last line is not wrapped. */ - if (dst_gl != NULL) - dst_gl->flags &= ~GRID_LINE_WRAPPED; + /* + * If the original line had the wrapped flag and there is still space + * in the last new line, try to join with the next lines. + */ + if (width < sx && (flags & GRID_LINE_WRAPPED)) + grid_reflow_join(gd, sx, line, width, cy); } -/* Move line data. */ -static void -grid_reflow_move(struct grid *dst, u_int *py, struct grid_line *src_gl) +/* Reflow lines on grid to new width. */ +void +grid_reflow(struct grid *gd, u_int sx, u_int *cursor) { - struct grid_line *dst_gl; - - /* Create new line. */ - if (*py >= dst->hsize + dst->sy) - grid_scroll_history(dst, 8); - dst_gl = &dst->linedata[*py]; - (*py)++; + struct grid_line *gl; + struct grid_cell gc; + u_int yy, cy, width, i, at, first; + struct timeval start, tv; - /* Copy the old line. */ - memcpy(dst_gl, src_gl, sizeof *dst_gl); - dst_gl->flags &= ~GRID_LINE_WRAPPED; + gettimeofday(&start, NULL); - /* Clear old line. */ - src_gl->celldata = NULL; - src_gl->extddata = NULL; -} + log_debug("%s: %u lines, new width %u", __func__, gd->hsize + gd->sy, + sx); + cy = gd->hsize + (*cursor); -/* - * Reflow lines from src grid into dst grid of width new_x. Returns number of - * lines fewer in the visible area. The source grid is destroyed. - */ -u_int -grid_reflow(struct grid *dst, struct grid *src, u_int new_x) -{ - u_int py, sy, line; - int previous_wrapped; - struct grid_line *src_gl; - - py = 0; - sy = src->sy; - - previous_wrapped = 0; - for (line = 0; line < sy + src->hsize; line++) { - src_gl = src->linedata + line; - if (!previous_wrapped) { - /* Wasn't wrapped. If smaller, move to destination. */ - if (src_gl->cellused <= new_x) - grid_reflow_move(dst, &py, src_gl); + /* + * Loop over lines from top to bottom. The size may change during the + * loop, but it is OK because we are always adding or removing lines + * below the current one. + */ + for (yy = 0; yy < gd->hsize + gd->sy; yy++) { + gl = &gd->linedata[yy]; + + /* Work out the width of this line. */ + first = at = width = 0; + if (~gl->flags & GRID_LINE_EXTENDED) { + first = 1; + width = gl->cellused; + if (width > sx) + at = sx; else - grid_reflow_split(dst, &py, src_gl, new_x, 0); + at = width; } else { - /* Previous was wrapped. Try to join. */ - grid_reflow_join(dst, &py, src_gl, new_x); + for (i = 0; i < gl->cellused; i++) { + grid_get_cell1(gl, i, &gc); + if (i == 0) + first = gc.data.width; + if (at == 0 && width + gc.data.width > sx) + at = i; + width += gc.data.width; + } + } + + /* + * If the line is exactly right, there is no need to do + * anything. + */ + if (width == sx) + continue; + + /* + * If the first character is wider than the target width, there + * is no point in trying to do anything. + */ + if (first > sx) + continue; + + /* + * If the line is too big, it needs to be split, whether or not + * it was previously wrapped. + */ + if (width > sx) { + grid_reflow_split(gd, sx, yy, at, &cy); + continue; } - previous_wrapped = (src_gl->flags & GRID_LINE_WRAPPED); - /* This is where we started scrolling. */ - if (line == sy + src->hsize - src->hscrolled - 1) - dst->hscrolled = 0; + /* + * If the line was previously wrapped, join as much as possible + * of the next line. + */ + if (gl->flags & GRID_LINE_WRAPPED) + grid_reflow_join(gd, sx, yy, width, &cy); + } - grid_destroy(src); + if (gd->hscrolled > gd->hsize) + gd->hscrolled = gd->hsize; + if (cy < gd->hsize) + *cursor = 0; + else + *cursor = cy - gd->hsize; - if (py > sy) - return (0); - return (sy - py); + gettimeofday(&tv, NULL); + timersub(&tv, &start, &tv); + log_debug("%s: now %u lines (in %llu.%06u seconds)", __func__, + gd->hsize + gd->sy, (unsigned long long)tv.tv_sec, + (u_int)tv.tv_usec); } diff --git a/usr.bin/tmux/screen-write.c b/usr.bin/tmux/screen-write.c index 028ddcf5dac..6b738ca5724 100644 --- a/usr.bin/tmux/screen-write.c +++ b/usr.bin/tmux/screen-write.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-write.c,v 1.134 2017/11/03 17:02:33 nicm Exp $ */ +/* $OpenBSD: screen-write.c,v 1.135 2017/11/15 19:21:24 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -378,7 +378,8 @@ screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, gc.bg = mgc->bg; } } - screen_write_cell(ctx, &gc); + if (xx + gc.data.width <= px + nx) + screen_write_cell(ctx, &gc); } cy++; screen_write_cursormove(ctx, cx, cy); @@ -410,6 +411,8 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, if (xx >= gd->linedata[yy].cellsize) break; grid_get_cell(gd, xx, yy, &gc); + if (xx + gc.data.width > px + nx) + break; if (!grid_cells_equal(&gc, &grid_default_cell)) grid_view_set_cell(ctx->s->grid, cx, cy, &gc); cx++; diff --git a/usr.bin/tmux/screen.c b/usr.bin/tmux/screen.c index 2963c55aa9d..83ee65edbe8 100644 --- a/usr.bin/tmux/screen.c +++ b/usr.bin/tmux/screen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen.c,v 1.49 2017/11/02 18:26:38 nicm Exp $ */ +/* $OpenBSD: screen.c,v 1.50 2017/11/15 19:21:24 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -470,14 +470,5 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, static void screen_reflow(struct screen *s, u_int new_x) { - struct grid *old = s->grid; - u_int change; - - s->grid = grid_create(old->sx, old->sy, old->hlimit); - - change = grid_reflow(s->grid, old, new_x); - if (change < s->cy) - s->cy -= change; - else - s->cy = 0; + grid_reflow(s->grid, new_x, &s->cy); } diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index 32704f672e2..cb120955757 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.812 2017/11/03 17:02:33 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.813 2017/11/15 19:21:24 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -1996,7 +1996,7 @@ char *grid_string_cells(struct grid *, u_int, u_int, u_int, struct grid_cell **, int, int, int); void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int, u_int); -u_int grid_reflow(struct grid *, struct grid *, u_int); +void grid_reflow(struct grid *, u_int, u_int *); /* grid-view.c */ void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *); |