HelenOS sources
This source file includes following definitions.
- _lines_empty
- _line_idx_inc
- _line_idx_dec
- _cell_buffer_shrink
- _line_buffer_shrink
- _evict_cells
- _index_valid
- _evict_oldest_line
- _cell_buffer_try_extend
- _line_buffer_try_extend
- _cell_buffer_fits_line
- _current_line
- _alloc_line
- _alloc_cells
- _history_append
- _scrollback_active
- _history_line_rows
- _history_scroll_down
- _history_scroll_up
- _history_scroll_to_top
- _history_scroll_to_bottom
- _history_scroll
- _history_reflow
- _history_viewport_rows
- _update_blank
- _adjust_row_delta
- _history_iter_rows
- _history_append_row
#include "history.h"
#include <assert.h>
#include <limits.h>
#include <macros.h>
#include <mem.h>
#include <stdio.h>
#include <stdlib.h>
#define BLANK_CELLS_LEN 64
static const termui_cell_t _blank_cells[BLANK_CELLS_LEN];
static bool _lines_empty(struct line_buffer *lines)
{
return lines->head == lines->tail;
}
static void _line_idx_inc(const struct line_buffer *lines, size_t *idx)
{
if (*idx == lines->buf_len - 1)
*idx = 0;
else
(*idx)++;
}
static void _line_idx_dec(const struct line_buffer *lines, size_t *idx)
{
if (*idx == 0)
*idx = lines->buf_len - 1;
else
(*idx)--;
}
static void _cell_buffer_shrink(struct cell_buffer *cells)
{
assert(cells->max_len > 0);
assert(cells->buf_len > cells->max_len);
size_t new_len = max(cells->max_len, cells->head_top);
termui_cell_t *new_buf = reallocarray(cells->buf,
new_len, sizeof(termui_cell_t));
if (new_buf) {
cells->buf = new_buf;
cells->buf_len = new_len;
}
}
static void _line_buffer_shrink(struct line_buffer *lines)
{
assert(lines->max_len > 0);
assert(lines->buf_len > lines->max_len);
assert(lines->head <= lines->tail);
size_t new_len = max(lines->max_len, lines->tail + 1);
struct history_line *new_buf = reallocarray(lines->buf,
new_len, sizeof(struct history_line));
if (new_buf) {
lines->buf = new_buf;
lines->buf_len = new_len;
}
}
static void _evict_cells(struct cell_buffer *cells, size_t idx, size_t len)
{
assert(idx == cells->head_offset);
assert(len <= cells->head_top);
assert(idx <= cells->head_top - len);
cells->head_offset += len;
if (cells->head_offset >= cells->head_top) {
cells->head_offset = 0;
cells->head_top = cells->tail_top;
cells->tail_top = 0;
if (cells->buf_len > cells->max_len)
_cell_buffer_shrink(cells);
}
}
static bool _index_valid(const struct history *history, size_t idx)
{
const struct line_buffer *lines = &history->lines;
if (lines->head <= lines->tail)
return idx >= lines->head && idx < lines->tail;
else
return (idx >= lines->head && idx < lines->buf_len) ||
(idx < lines->tail);
}
#define _history_check(history) do { \
assert(history->lines.head < history->lines.buf_len); \
assert(history->lines.tail < history->lines.buf_len); \
assert(history->cells.tail_top <= history->cells.head_offset); \
assert(history->cells.head_offset <= history->cells.head_top); \
assert(history->cells.head_top <= history->cells.buf_len); \
assert(_index_valid(history, history->viewport_top) || history->viewport_top == history->lines.tail); \
if (history->append) assert(!_lines_empty(&history->lines)); \
} while (false)
static void _evict_oldest_line(struct history *history)
{
struct line_buffer *lines = &history->lines;
struct cell_buffer *cells = &history->cells;
_history_check(history);
bool head = (history->viewport_top == lines->head);
struct history_line line = lines->buf[lines->head];
_line_idx_inc(lines, &lines->head);
if (lines->head == lines->tail) {
lines->head = 0;
lines->tail = 0;
history->viewport_top = 0;
history->append = false;
history->row_delta = 0;
}
if (head) {
history->viewport_top = lines->head;
history->row_delta = 0;
}
_history_check(history);
if (lines->head == 0 && lines->buf_len > lines->max_len)
_line_buffer_shrink(lines);
_history_check(history);
_evict_cells(cells, line.idx, line.len);
_history_check(history);
}
static void _cell_buffer_try_extend(struct cell_buffer *cells, size_t len)
{
static const size_t MIN_EXTEND_LEN = 128;
if (cells->buf_len >= cells->max_len)
return;
if (cells->tail_top > 0 && len <= cells->buf_len - cells->tail_top) {
return;
}
len = max(len, MIN_EXTEND_LEN);
len = max(len, cells->buf_len);
len = min(len, cells->max_len - cells->head_top);
size_t new_len =
min(cells->head_top + len, SIZE_MAX / sizeof(termui_cell_t));
assert(new_len > cells->buf_len);
assert(new_len <= cells->max_len);
termui_cell_t *new_buf =
realloc(cells->buf, new_len * sizeof(termui_cell_t));
if (!new_buf) {
fprintf(stderr, "termui: Out of memory for scrollback\n");
return;
}
cells->buf = new_buf;
cells->buf_len = new_len;
}
static void _line_buffer_try_extend(struct line_buffer *lines)
{
static const size_t MIN_EXTEND_LEN = 128;
if (lines->buf_len >= lines->max_len)
return;
if (lines->tail < lines->head)
return;
size_t len = MIN_EXTEND_LEN;
len = max(len, lines->buf_len);
len = min(len, lines->max_len - lines->buf_len);
size_t new_len = min(lines->buf_len + len, SIZE_MAX - sizeof(struct history_line));
assert(new_len >= lines->buf_len);
assert(new_len <= lines->max_len);
if (new_len == lines->buf_len)
return;
struct history_line *new_buf =
realloc(lines->buf, new_len * sizeof(struct history_line));
if (!new_buf) {
fprintf(stderr, "termui: Out of memory for scrollback\n");
return;
}
lines->buf = new_buf;
lines->buf_len = new_len;
}
static bool _cell_buffer_fits_line(const struct cell_buffer *cells, size_t len)
{
if (cells->tail_top > 0) {
return len <= cells->head_offset - cells->tail_top;
} else {
return len <= cells->buf_len - cells->head_top || len <= cells->head_offset;
}
}
static struct history_line *_current_line(struct line_buffer *lines)
{
assert(!_lines_empty(lines));
return &lines->buf[(lines->tail ? lines->tail : lines->buf_len) - 1];
}
static void _alloc_line(struct history *history)
{
struct line_buffer *lines = &history->lines;
size_t idx = 0;
if (!_lines_empty(lines))
idx = _current_line(lines)->idx + _current_line(lines)->len;
if (lines->buf_len == 0) {
_line_buffer_try_extend(lines);
if (lines->buf_len == 0) {
fprintf(stderr, "termui: Could not allocate initial scrollback buffer\n");
return;
}
}
assert(lines->tail < lines->buf_len);
bool viewport_inactive = (history->viewport_top == lines->tail);
lines->tail++;
if (lines->tail >= lines->buf_len)
_line_buffer_try_extend(lines);
if (lines->tail >= lines->buf_len)
lines->tail = 0;
if (lines->tail == lines->head)
_evict_oldest_line(history);
assert(lines->tail != lines->head);
if (viewport_inactive)
history->viewport_top = lines->tail;
_current_line(lines)->idx = idx;
_current_line(lines)->len = 0;
history->append = true;
_history_check(history);
}
static size_t _alloc_cells(struct cell_buffer *cells, size_t len)
{
assert(_cell_buffer_fits_line(cells, len));
size_t idx;
if (cells->tail_top == 0 && cells->buf_len - cells->head_top >= len) {
idx = cells->head_top;
cells->head_top += len;
assert(cells->head_top <= cells->buf_len);
} else {
idx = cells->tail_top;
cells->tail_top += len;
assert(cells->tail_top <= cells->head_offset);
}
return idx;
}
static termui_cell_t *_history_append(struct history *history, size_t len)
{
struct line_buffer *lines = &history->lines;
struct cell_buffer *cells = &history->cells;
if (!_cell_buffer_fits_line(cells, len))
_cell_buffer_try_extend(cells, len);
if (len > cells->buf_len) {
return NULL;
}
while (!_cell_buffer_fits_line(cells, len)) {
assert(!_lines_empty(lines));
_evict_oldest_line(history);
}
size_t idx = _alloc_cells(cells, len);
if (!history->append || _lines_empty(lines)) {
_alloc_line(history);
if (_lines_empty(lines)) {
return NULL;
}
}
struct history_line *line = _current_line(lines);
assert(idx == line->idx + line->len || idx == 0);
if (idx != line->idx + line->len) {
if (line->len > 0) {
_alloc_line(history);
line = _current_line(lines);
}
line->idx = 0;
}
line->len += len;
return &cells->buf[idx];
}
bool _scrollback_active(const struct history *history)
{
if (history->viewport_top == history->lines.tail)
return false;
assert(_index_valid(history, history->viewport_top));
return true;
}
static size_t _history_line_rows(const struct history *history, size_t idx)
{
assert(_index_valid(history, idx));
struct history_line line = history->lines.buf[idx];
if (line.len == 0)
return 1;
return (line.len - 1) / history->cols + 1;
}
static int _history_scroll_down(struct history *history, int requested)
{
assert(requested > 0);
size_t delta = requested;
if (history->row_delta > 0) {
size_t rows = _history_line_rows(history, history->viewport_top);
assert(rows > history->row_delta);
if (delta < rows - history->row_delta) {
history->row_delta += delta;
_history_check(history);
return requested;
}
delta -= rows - history->row_delta;
history->row_delta = 0;
_line_idx_inc(&history->lines, &history->viewport_top);
}
while (_scrollback_active(history)) {
size_t rows = _history_line_rows(history, history->viewport_top);
if (delta < rows) {
history->row_delta = delta;
_history_check(history);
return requested;
}
delta -= rows;
_line_idx_inc(&history->lines, &history->viewport_top);
}
_history_check(history);
return requested - delta;
}
static int _history_scroll_up(struct history *history, int requested)
{
assert(requested < 0);
if (history->row_delta > INT_MAX) {
history->row_delta += requested;
_history_check(history);
return requested;
}
int delta = requested + (int) history->row_delta;
history->row_delta = 0;
while (delta < 0 && history->viewport_top != history->lines.head) {
_line_idx_dec(&history->lines, &history->viewport_top);
size_t rows = _history_line_rows(history, history->viewport_top);
if (rows > INT_MAX) {
history->row_delta = rows + delta;
_history_check(history);
return requested;
}
delta += (int) rows;
}
_history_check(history);
if (delta < 0)
return requested - delta;
assert(delta >= 0);
history->row_delta = (size_t) delta;
return requested;
}
static int _history_scroll_to_top(struct history *history)
{
history->viewport_top = history->lines.head;
history->row_delta = 0;
_history_check(history);
return INT_MIN;
}
static int _history_scroll_to_bottom(struct history *history)
{
history->viewport_top = history->lines.tail;
_history_check(history);
return INT_MAX;
}
int _history_scroll(struct history *history, int delta)
{
if (delta == INT_MIN)
return _history_scroll_to_top(history);
if (delta == INT_MAX)
return _history_scroll_to_bottom(history);
if (delta > 0)
return _history_scroll_down(history, delta);
if (delta < 0)
return _history_scroll_up(history, delta);
return 0;
}
const termui_cell_t *_history_reflow(struct history *history, size_t new_cols, size_t *recouped)
{
history->row_delta = (history->row_delta * history->cols) / new_cols;
history->cols = new_cols;
if (!history->append) {
*recouped = 0;
return NULL;
}
assert(!_lines_empty(&history->lines));
size_t last_idx = history->lines.tail;
_line_idx_dec(&history->lines, &last_idx);
struct history_line *last = &history->lines.buf[last_idx];
*recouped = last->len % new_cols;
if (last->idx + last->len == history->cells.head_top) {
history->cells.head_top -= *recouped;
} else {
assert(last->idx + last->len == history->cells.tail_top);
history->cells.tail_top -= *recouped;
}
last->len -= *recouped;
if (last->len == 0 && last->idx == 0) {
assert(history->cells.tail_top == 0);
last->idx = history->cells.head_top;
}
return &history->cells.buf[last->idx + last->len];
}
int _history_viewport_rows(const struct history *history, size_t max)
{
if (!_scrollback_active(history))
return 0;
size_t current = history->viewport_top;
size_t rows = _history_line_rows(history, current) - history->row_delta;
_line_idx_inc(&history->lines, ¤t);
while (rows < max && current != history->lines.tail) {
rows += _history_line_rows(history, current);
_line_idx_inc(&history->lines, ¤t);
}
return (rows > max) ? max : rows;
}
static void _update_blank(int col, int row, int len, termui_update_cb_t cb, void *udata)
{
while (len > BLANK_CELLS_LEN) {
cb(udata, col, row, _blank_cells, BLANK_CELLS_LEN);
col += BLANK_CELLS_LEN;
len -= BLANK_CELLS_LEN;
}
if (len > 0)
cb(udata, col, row, _blank_cells, len);
}
static void _adjust_row_delta(const struct history *history, size_t *line_idx, size_t *delta)
{
while (*line_idx != history->lines.tail) {
size_t rows = _history_line_rows(history, *line_idx);
if (*delta < rows)
return;
*delta -= rows;
_line_idx_inc(&history->lines, line_idx);
}
}
int _history_iter_rows(const struct history *history, int row, int count, termui_update_cb_t cb, void *udata)
{
assert(history->row_delta <= SIZE_MAX - row);
size_t current_line = history->viewport_top;
size_t delta = history->row_delta + (size_t) row;
_adjust_row_delta(history, ¤t_line, &delta);
int initial_count = count;
while (count > 0 && current_line != history->lines.tail) {
assert(_index_valid(history, current_line));
struct history_line line = history->lines.buf[current_line];
assert(line.len <= history->cells.buf_len);
assert(line.idx <= history->cells.buf_len - line.len);
if (line.len == 0) {
_update_blank(0, row, history->cols, cb, udata);
row++;
count--;
_line_idx_inc(&history->lines, ¤t_line);
continue;
}
const termui_cell_t *cells = &history->cells.buf[line.idx];
size_t line_offset = delta * history->cols;
assert(line_offset < line.len);
delta = 0;
while (count > 0 && line_offset + history->cols <= line.len) {
assert(line.idx + line_offset <= history->cells.buf_len - history->cols);
cb(udata, 0, row, &cells[line_offset], history->cols);
line_offset += history->cols;
row++;
count--;
}
if (count > 0 && line_offset < line.len) {
cb(udata, 0, row, &cells[line_offset], line.len - line_offset);
size_t col = line.len - line_offset;
assert(col < history->cols);
_update_blank(col, row, history->cols - col, cb, udata);
row++;
count--;
}
_line_idx_inc(&history->lines, ¤t_line);
}
return initial_count - count;
}
void _history_append_row(struct history *history, const termui_cell_t *b, bool last)
{
size_t len = history->cols;
if (last) {
while (len > 1 && _cell_is_empty(b[len - 1]) && _cell_is_empty(b[len - 2]))
len--;
}
memcpy(_history_append(history, len), b, sizeof(termui_cell_t) * len);
if (last)
history->append = false;
}
HelenOS homepage, sources at GitHub