commit 897272f33780a8e30538598a8ae09810ffd49fa7
parent ea522242163fa4b3e60003d35d1c4ed40e508b02
Author: Claudio Alessi <smoppy@gmail.com>
Date: Sat, 13 Dec 2025 23:45:51 +0100
Stub of cells-based rendering.
Text data is rendered into an array of cells which allow to abstract
away fine clipping, horizontal scroll and most importantly allow to
simplify UTF-8 handling.
The design includes a linear buffer (arena pool) which is used for large
Unicode sequences (> 8 bytes).
This commit introduces a few regressions (fine clipping and horizontal
scroll) but also fixes a few bugs. It's still a work in progress but it
should be a very solid and future-proof approach to rendering.
Diffstat:
| M | edo.c | | | 148 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
| M | tui.c | | | 19 | +++++++++++++++++++ |
| M | ui.h | | | 38 | ++++++++++++++++++++++++++++++++++++-- |
3 files changed, 188 insertions(+), 17 deletions(-)
diff --git a/edo.c b/edo.c
@@ -1,6 +1,10 @@
/* osaentuhaosenuhaoesnuthaoesnutha oesnthaoesuntha snethu asoenhu saoenhtuaoesn uthaoesunthaoesuntaoeh usaoneth asoenth aoesnth aoesnthaoseuthaoseuthaoesunthaoeusnh asoentuh */
+
+/* 👩❤️💋👩 */
+
#include <assert.h>
#include <stdarg.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -65,7 +69,11 @@ void view_cursor_down(View *v);
int view_idx2col(View *v, Line *line, int idx);
int view_col2idx(View *v, Line *line, int col);
void view_scroll_fix(View *v);
+char *cell_get_text(Cell *cell, char *pool_base);
+void view_place_cursor(View *v);
void draw_view(View *v);
+void textpool_ensure_cap(TextPool *pool, int len);
+int textpool_insert(TextPool *pool, char *s, int len);
/* function implementations */
void
@@ -116,7 +124,6 @@ line_insert_char(Line *line, size_t index, char c) {
size_t newlen = line->len + 1;
assert(index >= 0 && index <= line->len);
-
if(newlen > line->cap) {
line->cap = line->cap ? line->cap * 2 : 16;
line->buf = erealloc(line->buf, line->cap);
@@ -309,6 +316,7 @@ view_cursor_down(View *v) {
int
view_idx2col(View *v, Line *line, int idx) {
(void)v;
+ if(!line->len) return 0;
return ui->text_width(line->buf, idx);
}
@@ -333,8 +341,89 @@ view_scroll_fix(View *v) {
v->col_offset = v->col_num - v->screen_cols + 1;
}
+/*
+ * TODO
+ *
+ * - arena pool
+ * - fine clipping
+ * - horizontal scroll
+ */
void
-draw_view(View *v) {
+render(Cell *cells, char *buf, int buflen, int xoff, int cols) {
+ int vx = 0, len, w, i;
+
+ for(i = 0; i < buflen && vx < cols; i++, vx += w) {
+ if(buf[i] == '\t') {
+ /* Note: assume tabstop (8) is <= CELL_POOL_THRESHOLD */
+ w = 8 - (vx % 8);
+ len = w;
+ for(int j = 0; j < w; j++)
+ cells[i].data.text[j] = ' ';
+ }
+ else {
+ w = 1;
+ len = 1;
+ cells[i].data.text[0] = buf[i];
+ }
+
+ cells[i].len = len;
+ cells[i].width = w;
+
+ //cells[i].data.pool_idx = 0;
+ /*
+ char text[CELL_POOL_THRESHOLD];
+ uint32_t pool_idx;
+ */
+ }
+}
+
+/* TODO: is this the cleaner way to do it? */
+void
+view_place_cursor(View *v) {
+ Line *l;
+ int x, y;
+
+ x = v->col_offset;
+ l = buffer_get_line(v->buf, v->line_num);
+ if(l) {
+ x = view_idx2col(v, l, v->col_num);
+ x -= v->col_offset;
+ y = v->line_num - v->row_offset;
+ } else {
+ x = y = 0;
+ }
+ ui->move_cursor(x, y);
+}
+
+void
+draw_view_with_cells(View *v) {
+ Line *l;
+ int row, y;
+
+ ui->frame_start();
+ view_scroll_fix(v);
+
+ Cell *cells = ecalloc(1, sizeof(Cell) * v->screen_cols);
+
+ for(y = 0; y < v->screen_rows; y++) {
+ row = v->row_offset + y;
+ l = buffer_get_line(v->buf, row);
+ if(!l) {
+ ui->draw_symbol(0, y, SYM_EMPTYLINE);
+ continue;
+ }
+ render(cells, l->buf, l->len, v->col_offset, v->screen_cols);
+ ui->draw_line_from_cells(ui, 0, y, cells, l->len > v->screen_cols ? v->screen_cols : l->len);
+ }
+
+ free(cells);
+
+ view_place_cursor(v);
+ ui->frame_flush();
+}
+
+void
+draw_view_old(View *v) {
Line *l;
int row, len, y;
int idx, sx, shift;
@@ -365,20 +454,44 @@ draw_view(View *v) {
ui->draw_line(shift, y, l->buf + idx, len);
}
- l = buffer_get_line(v->buf, v->line_num);
- if(l) {
- row = view_idx2col(v, l, v->col_num);
- row -= v->col_offset;
- y = v->line_num - v->row_offset;
- } else {
- row = y = 0;
- }
-
- ui->move_cursor(row, y);
+ view_place_cursor(v);
ui->frame_flush();
}
void
+draw_view(View *v) {
+ draw_view_with_cells(v);
+ //draw_view_old(v);
+}
+
+void
+textpool_ensure_cap(TextPool *pool, int len) {
+ size_t newlen = pool->len + len;
+
+ if(newlen <= pool->cap) return;
+ pool->cap *= 2;
+ if(pool->cap < newlen) pool->cap = newlen + 1024;
+ pool->data = erealloc(pool->data, pool->cap);
+}
+
+int
+textpool_insert(TextPool *pool, char *s, int len) {
+ int olen = len;
+
+ textpool_ensure_cap(pool, len);
+ memcpy(pool->data + pool->len, s, len);
+ pool->len += len;
+ return olen;
+}
+
+char *
+cell_get_text(Cell *c, char *pool_base) {
+ if(c->len > CELL_POOL_THRESHOLD)
+ return pool_base + c->data.pool_idx;
+ return c->data.text;
+}
+
+void
run(void) {
Event ev;
@@ -393,9 +506,14 @@ run(void) {
else if(ev.key == 'q') running = 0;
else if(ev.key == 'K') {
Line *l = line_create(NULL);
- buffer_insert_line(vcur->buf, vcur->line_num + 0, l);
+ buffer_insert_line(vcur->buf, vcur->line_num, l);
+
+ /* we should call view_cursor_hfix() here since we're moving into
+ * another line (the new one). Since only col_num may be wrong we
+ * can avoid a function call by setting it manually. */
+ vcur->col_num = 0;
}
- else if(ev.key == 'J') {
+ else if(ev.key == 'J' || ev.key == '\n') {
Line *l = line_create(NULL);
buffer_insert_line(vcur->buf, vcur->line_num + 1, l);
view_cursor_down(vcur);
@@ -419,7 +537,6 @@ main(int argc, char *argv[]) {
if(argc == 2) fn = argv[1];
ui = &ui_tui; /* the one and only... */
-
ui->init();
Buffer *b = buffer_create(fn);
View *v = view_create(b);
@@ -429,5 +546,6 @@ main(int argc, char *argv[]) {
buffer_destroy(v->buf);
view_destroy(v);
ui->exit();
+ free(ui->pool.data);
return 0;
}
diff --git a/tui.c b/tui.c
@@ -29,6 +29,7 @@ Abuf frame;
extern void *ecalloc(size_t nmemb, size_t size);
extern void *erealloc(void *p, size_t size);
extern void die(const char *fmt, ...);
+extern char *cell_get_text(Cell *cell, char *pool_base);
/* function declarations */
void ab_free(Abuf *ab);
@@ -43,6 +44,7 @@ void tui_get_window_size(int *rows, int *cols);
void tui_exit(void);
void tui_move_cursor(int x, int y);
void tui_draw_line(int c, int r, char *txt, int len);
+void tui_draw_line_from_cells(UI *ui, int x, int y, Cell *cells, int screen_cols);
void tui_draw_symbol(int r, int c, Symbol sym);
void tui_init(void);
@@ -150,6 +152,7 @@ tui_get_window_size(int *rows, int *cols) {
void
tui_exit(void) {
tcsetattr(0, TCSANOW, &origti);
+ printf(CURPOS CLEARRIGHT, ws.ws_row, 0);
}
void
@@ -158,6 +161,21 @@ tui_move_cursor(int c, int r) {
ab_printf(&frame, CURPOS, y, x);
}
+void tui_draw_line_from_cells(UI *ui, int x, int y, Cell *cells, int count) {
+ assert(x < ws.ws_col && y < ws.ws_row);
+ int w = 0, i;
+ char *txt;
+
+ tui_move_cursor(x, y);
+ for(i = 0; i < count; i++) {
+ w += cells[i].width;
+ txt = cell_get_text(&cells[i], ui->pool.data);
+ ab_write(&frame, txt, cells[i].len);
+ if(w >= ws.ws_col) break;
+ }
+ ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT));
+}
+
void
tui_draw_line(int c, int r, char *txt, int len) {
if(c >= ws.ws_col || r >= ws.ws_row) return;
@@ -255,6 +273,7 @@ UI ui_tui = {
.text_index_at = tui_text_index_at,
.move_cursor = tui_move_cursor,
.draw_line = tui_draw_line,
+ .draw_line_from_cells = tui_draw_line_from_cells,
.draw_symbol = tui_draw_symbol,
.get_window_size = tui_get_window_size,
.next_event = tui_next_event
diff --git a/ui.h b/ui.h
@@ -1,6 +1,9 @@
#ifndef UI_H
#define UI_H
+#include <stdint.h>
+#include <stdio.h>
+
typedef enum {
SYM_EMPTYLINE
} Symbol;
@@ -14,11 +17,41 @@ typedef struct {
EventType type;
int key;
int mod;
- int col, row;
+ int row;
+ int col;
} Event;
typedef struct {
+ char *data;
+ size_t cap;
+ size_t len;
+} TextPool;
+
+#define CELL_POOL_THRESHOLD 8
+typedef struct {
+ union {
+ char text[CELL_POOL_THRESHOLD];
+ uint32_t pool_idx;
+ } data;
+ /*
+ * For smarter backend we can also provide this fields:
+ *
+ * idx: index of the bytes into the file buffer
+ * type: char, tab, newline, eof, ecc.
+ *
+ * So the backend may say for example "Oh, it's a tab. Let's ignore the
+ * rendered cells with spaces and use an beatuful icon instead."
+ *
+ * We should also add the style here.
+ */
+ uint16_t len;
+ uint16_t width;
+} Cell;
+
+typedef struct UI UI;
+struct UI {
const char *name;
+ TextPool pool;
void (*init)(void);
void (*exit)(void);
void (*frame_start)(void);
@@ -27,10 +60,11 @@ typedef struct {
int (*text_index_at)(char *s, int idx);
void (*move_cursor)(int x, int y);
void (*draw_line)(int r, int c, char *txt, int len);
+ void (*draw_line_from_cells)(UI *ui, int x, int y, Cell *cells, int count);
void (*draw_symbol)(int r, int c, Symbol sym);
void (*get_window_size)(int *rows, int *cols);
Event (*next_event)(void);
-} UI;
+};
extern UI ui_tui;