summaryrefslogtreecommitdiffstats
path: root/grid.c
diff options
context:
space:
mode:
authornicm <nicm>2017-11-15 19:21:24 +0000
committernicm <nicm>2017-11-15 19:21:24 +0000
commit533a5719c5edf53f0d7021d4340af230cc43ac8a (patch)
treeda123a233b70b2a4753fb5379fa0702efe218a42 /grid.c
parentaeda2e5808af7c4b629dce23d2b4331a77ecde83 (diff)
Completely rewrite the reflow code to correctly handle double width
characters (previously they were not accounted for).
Diffstat (limited to 'grid.c')
-rw-r--r--grid.c411
1 files changed, 252 insertions, 159 deletions
diff --git a/grid.c b/grid.c
index 74e80f96..1cd01cbd 100644
--- a/grid.c
+++ b/grid.c
@@ -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);
}