#include "buffer.h" /* row operations */ void zero(Row *row, int start, int len) { int i; Cell cell = { .r = L'\0', .pen = {0}; }; for (i = start; i < len + start; i++) row->cells[i] = cell; row->dirty = true; } void roll(Row *start, Row *end, int count) { int n = end - start; count %= n; if (count < 0) count += n; if (count) { char buf[count * sizeof(Row)]; memcpy(buf, start, count * sizeof(Row)); memmove(start, start + count, (n - count) * sizeof(Row)); memcpy(end - count, buf, count * sizeof(Row)); for (Row *row = start; row < end; row++) row->dirty = true; } } /* buffer operations */ void bclear(Buffer *b) { int i; Cell cell = { .r = L'\0', .pen = { .state = PenNormal, .col.fg = -1, .col.bg = -1, }, }; for (i = 0; i < b->rows; i++) { Row *row = b->lines + i; for (int j = 0; j < b->cols; j++) { row->cells[j] = cell; row->dirty = true; } } } void bfree(Buffer *b) { int i; for (i = 0; i < b->rows; i++) free(b->lines[i].cells); free(b->lines); for (i = 0; i < b->scroll.size; i++) free(b->scroll.buf[i].cells); free(b->scroll.buf); free(b->tabs); } void bscroll(Buffer *b, int s) { /* work in screenfuls */ int ssz = b->scroll.bot - b->scroll.top; if (s > ssz) { bscroll(b, ssz); bscroll(b, s - ssz); return; } if (s < -ssz) { bscroll(b, -ssz); bscroll(b, s + ssz); return; } b->scroll.above += s; if (b->scroll.above >= b->scroll.size) b->scroll.above = b->scroll.size; if (s > 0 && b->scroll.size) { for (int i = 0; i < s; i++) { Row tmp = b->scroll.top[i]; b->scroll.top[i] = b->scroll.buf[b->scroll.index]; b->scroll.buf[b->scroll.index] = tmp; b->scroll.index++; if (b->scroll.index == b->scroll.size) b->scroll.index = 0; } } roll(b->scroll.top, b->scroll.bot, s); if (s < 0 && b->scroll.size) { for (int i = (-s) - 1; i >= 0; i--) { b->scroll.index--; if (b->scroll.index == -1) b->scroll.index = b->scroll.size - 1; Row tmp = b->scroll.top[i]; b->scroll.top[i] = b->scroll.buf[b->scroll.index]; b->scroll.buf[b->scroll.index] = tmp; b->scroll.top[i].dirty = true; } } } void bresize(Buffer *b, int rows, int cols) { Row *lines = b->lines; if (b->rows != rows) { if (b->crow >= lines + rows) { /* scroll up instead of simply chopping off bottom */ bscroll(b, (b->crow - b->lines) - rows + 1); } while (b->rows > rows) { free(lines[b->rows - 1].cells); b->rows--; } lines = realloc(lines, sizeof(Row) * rows); } if (b->maxcols < cols) { for (int row = 0; row < b->rows; row++) { lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols); if (b->cols < cols) zero(lines + row, b->cols, cols - b->cols); lines[row].dirty = true; } Row *sbuf = b->scroll.buf; for (int row = 0; row < b->scroll.size; row++) { sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols); if (b->cols < cols) zero(sbuf + row, b->cols, cols - b->cols); } b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols); for (int col = b->cols; col < cols; col++) b->tabs[col] = !(col & 7); b->maxcols = cols; b->cols = cols; } else if (b->cols != cols) { for (int row = 0; row < b->rows; row++) lines[row].dirty = true; b->cols = cols; } int deltarows = 0; if (b->rows < rows) { while (b->rows < rows) { lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell)); zero(lines + b->rows, 0, b->maxcols); b->rows++; } /* prepare for backfill */ if (b->crow >= b->scroll.bot - 1) { deltarows = b->lines + rows - b->crow - 1; if (deltarows > b->scroll.above) deltarows = b->scroll.above; } } b->crow += lines - b->lines; b->scroll.top = lines; b->scroll.bot = lines + rows; b->lines = lines; /* perform backfill */ if (deltarows > 0) { bscroll(b, -deltarows); b->crow += deltarows; } } bool binit(Buffer *b, int rows, int cols, int size) { b->pen.state = PenNormal; b->pen.col.fg = b->pen.col.fg = -1; if (size < 0) size = 0; if (size && !(b->scroll.buf = calloc(size, sizeof(Row)))) return false; b->scroll.size = size; bresize(b, rows, cols); return true; } void bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) { if (bs) *bs = nil; if (be) *be = nil; if (as) *as = nil; if (ae) *ae = nil; if (!b->scroll.size) return; if (b->scroll.above) { if (bs) *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size]; if (be) *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size]; } if (b->scroll.below) { if (as) *as = &b->scroll.buf[b->scroll.index]; if (ae) *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size]; } } Row * browfirst(Buffer *b) { Row *bstart; if (!b->scroll.size || !b->scroll.above) return b->lines; bboundary(b, &bstart, nil, nil, nil); return bstart; } Row * browlast(Buffer *b) { Row *aend; if (!b->scroll.size || !b->scroll.below) return b->lines + b->rows - 1; bboundary(b, nil, nil, nil, &aend); return aend; } Row * brownext(Buffer *b, Row *row) { Row *before_start, *before_end, *after_start, *after_end; Row *first = b->lines, *last = b->lines + b->rows - 1; if (!row) return nil; bboundary(b, &before_start, &before_end, &after_start, &after_end); if (row >= first && row < last) return ++row; if (row == last) return after_start; if (row == before_end) return first; if (row == after_end) return nil; if (row == &b->scroll.buf[b->scroll.size - 1]) return b->scroll.buf; return ++row; } Row * bprevrow(Buffer *b, Row *row) { Row *before_start, *before_end, *after_start, *after_end; Row *first = b->lines, *last = b->lines + b->rows - 1; if (!row) return nil; bboundary(b, &before_start, &before_end, &after_start, &after_end); if (row > first && row <= last) return --row; if (row == first) return before_end; if (row == before_start) return nil; if (row == after_start) return last; if (row == b->scroll.buf) return &b->scroll.buf[b->scroll.size - 1]; return --row; }