edo

Experimental text editor.
Log | Files | Refs | LICENSE

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:
Medo.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtui.c | 19+++++++++++++++++++
Mui.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;