skip to content
==> src/render.c <==
1/* ratatui-core 0.1.2's BufferDiff and Terminal::draw, ratatui-crossterm 0.1.2's CrosstermBackend::draw. */
2#include "tui/render.h"
3
4#include "tui/term.h"
5#include "tui/utf8.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11void tui_out_put(tui_out *o, const char *s, size_t n) {
12    if (o->len + n + 1 > o->cap) {
13        size_t cap = o->cap ? o->cap : 4096;
14        while (cap < o->len + n + 1) cap *= 2;
15        o->data = realloc(o->data, cap);
16        if (!o->data) abort();
17        o->cap = cap;
18    }
19    memcpy(o->data + o->len, s, n);
20    o->len += n;
21    o->data[o->len] = 0;
22}
23
24static void outs(tui_out *o, const char *s) { tui_out_put(o, s, strlen(s)); }
25
26void tui_out_free(tui_out *o) {
27    free(o->data);
28    *o = (tui_out){0};
29}
30
31static bool cell_eq(const tui_cell *a, const tui_cell *b) {
32    return strcmp(tui_cell_symbol(a), tui_cell_symbol(b)) == 0 && tui_color_eq(a->fg, b->fg) &&
33           tui_color_eq(a->bg, b->bg) && a->mod == b->mod;
34}
35
36static uint16_t cw(const tui_cell *c) {
37    const char *s = tui_cell_symbol(c);
38    return tui_symbol_width(s, strlen(s));
39}
40
41static void color(tui_out *o, tui_color c, bool fg) {
42    char s[32];
43    switch (c.kind) {
44    case TUI_COLOR_RESET: outs(o, fg ? "\x1b[39m" : "\x1b[49m"); return;
45    case TUI_COLOR_RGB: snprintf(s, sizeof s, "\x1b[%d;2;%u;%u;%um", fg ? 38 : 48, c.r, c.g, c.b); break;
46    case TUI_COLOR_INDEXED: snprintf(s, sizeof s, "\x1b[%d;5;%um", fg ? 38 : 48, c.r); break;
47    default: {
48        int base = c.r < 8 ? 30 + c.r : 90 + (c.r - 8);
49        snprintf(s, sizeof s, "\x1b[%dm", fg ? base : base + 10);
50        break;
51    }
52    }
53    outs(o, s);
54}
55
56static void modifier_diff(tui_out *o, uint16_t from, uint16_t to) {
57    uint16_t removed = (uint16_t)(from & ~to), added = (uint16_t)(to & ~from);
58    if (removed & TUI_REVERSED) outs(o, "\x1b[27m");
59    if (removed & TUI_BOLD) {
60        outs(o, "\x1b[22m");
61        if (to & TUI_DIM) outs(o, "\x1b[2m");
62    }
63    if (removed & TUI_ITALIC) outs(o, "\x1b[23m");
64    if (removed & TUI_UNDERLINED) outs(o, "\x1b[24m");
65    if (removed & TUI_DIM) {
66        outs(o, "\x1b[22m");
67        if (to & TUI_BOLD) outs(o, "\x1b[1m");
68    }
69    if (removed & TUI_CROSSED_OUT) outs(o, "\x1b[29m");
70    if (removed & (TUI_SLOW_BLINK | TUI_RAPID_BLINK)) outs(o, "\x1b[25m");
71    if (removed & TUI_HIDDEN) outs(o, "\x1b[28m");
72    if (added & TUI_REVERSED) outs(o, "\x1b[7m");
73    if (added & TUI_BOLD) outs(o, "\x1b[1m");
74    if (added & TUI_ITALIC) outs(o, "\x1b[3m");
75    if (added & TUI_UNDERLINED) outs(o, "\x1b[4m");
76    if (added & TUI_DIM) outs(o, "\x1b[2m");
77    if (added & TUI_CROSSED_OUT) outs(o, "\x1b[9m");
78    if (added & TUI_SLOW_BLINK) outs(o, "\x1b[5m");
79    if (added & TUI_RAPID_BLINK) outs(o, "\x1b[6m");
80    if (added & TUI_HIDDEN) outs(o, "\x1b[8m");
81}
82
83typedef struct {
84    tui_out *o;
85    bool has_last;
86    uint16_t lx, ly;
87    uint16_t mod;
88    tui_color fg, bg;
89} pen;
90
91static void emit(pen *p, uint16_t x, uint16_t y, const tui_cell *c) {
92    if (!(p->has_last && p->ly == y && p->lx + 1 == x)) {
93        char s[32];
94        snprintf(s, sizeof s, "\x1b[%u;%uH", (unsigned)y + 1, (unsigned)x + 1);
95        outs(p->o, s);
96    }
97    p->has_last = true;
98    p->lx = x;
99    p->ly = y;
100    if (c->mod != p->mod) {
101        modifier_diff(p->o, p->mod, c->mod);
102        p->mod = c->mod;
103    }
104    if (!tui_color_eq(c->fg, p->fg) || !tui_color_eq(c->bg, p->bg)) {
105        color(p->o, c->fg, true);
106        color(p->o, c->bg, false);
107        p->fg = c->fg;
108        p->bg = c->bg;
109    }
110    outs(p->o, tui_cell_symbol(c));
111}
112
113void tui_render_diff(const tui_buffer *prev, const tui_buffer *next, tui_out *out) {
114    size_t w = next->area.width;
115    size_t len = tui_rect_area(next->area) < tui_rect_area(prev->area) ? tui_rect_area(next->area) : tui_rect_area(prev->area);
116    pen p = {out, false, 0, 0, 0, TUI_RESET, TUI_RESET};
117    for (size_t i = 0; i < len;) {
118        const tui_cell *cur = &next->cells[i], *old = &prev->cells[i];
119        size_t width = cw(cur);
120        if (cell_eq(cur, old)) {
121            i += width > 1 ? width : 1;
122            continue;
123        }
124        size_t prev_w = cw(old);
125        const char *sym = tui_cell_symbol(cur);
126        bool vs16 = width > 1 && strstr(sym, "\xEF\xB8\x8F") != NULL;
127        emit(&p, (uint16_t)(i % w + next->area.x), (uint16_t)(i / w + next->area.y), cur);
128        if (vs16) {
129            /* The cells under it are written when their symbols changed. */
130            size_t end = i + width < len ? i + width : len;
131            for (size_t j = i + 1; j < end;) {
132                size_t jw = cw(&next->cells[j]);
133                if (strcmp(tui_cell_symbol(&prev->cells[j]), tui_cell_symbol(&next->cells[j])) != 0)
134                    emit(&p, (uint16_t)(j % w + next->area.x), (uint16_t)(j / w + next->area.y), &next->cells[j]);
135                j += jw ? jw : 1;
136                if (j > end) end = j < len ? j : len;
137            }
138            i = end;
139        } else if (width > 1) {
140            i += width;
141        } else if (prev_w > width && (old->bg.kind != TUI_COLOR_RESET ||
142                                      (old->mod & (TUI_REVERSED | TUI_UNDERLINED | TUI_SLOW_BLINK | TUI_RAPID_BLINK | TUI_CROSSED_OUT)))) {
143            size_t end = i + prev_w;
144            for (size_t j = i + 1; j < end && j < len;) {
145                size_t jw = cw(&next->cells[j]);
146                emit(&p, (uint16_t)(j % w + next->area.x), (uint16_t)(j / w + next->area.y), &next->cells[j]);
147                j += jw ? jw : 1;
148                if (j > end) end = j;
149            }
150            i = end;
151        } else {
152            i++;
153        }
154    }
155    outs(out, "\x1b[39m\x1b[49m\x1b[0m");
156}
157
158void tui_terminal_init(tui_terminal *t, uint16_t width, uint16_t height) {
159    tui_term_size(&width, &height);
160    tui_buffer_init(&t->buffers[0], (tui_rect){0, 0, width, height});
161    tui_buffer_init(&t->buffers[1], (tui_rect){0, 0, width, height});
162    t->current = 0;
163    t->cleared = true;
164}
165
166void tui_terminal_free(tui_terminal *t) {
167    tui_buffer_free(&t->buffers[0]);
168    tui_buffer_free(&t->buffers[1]);
169}
170
171tui_buffer *tui_terminal_begin(tui_terminal *t) {
172    uint16_t w, h;
173    tui_term_resized();
174    if (tui_term_size(&w, &h) && (w != t->buffers[0].area.width || h != t->buffers[0].area.height)) {
175        tui_terminal_free(t);
176        tui_buffer_init(&t->buffers[0], (tui_rect){0, 0, w, h});
177        tui_buffer_init(&t->buffers[1], (tui_rect){0, 0, w, h});
178        t->cleared = true;
179    }
180    tui_buffer *cur = &t->buffers[t->current];
181    tui_buffer_clear(cur, cur->area);
182    return cur;
183}
184
185void tui_terminal_end(tui_terminal *t) {
186    tui_buffer *cur = &t->buffers[t->current], *prev = &t->buffers[1 - t->current];
187    tui_out o = {0};
188    if (t->cleared) {
189        outs(&o, "\x1b[2J");
190        tui_buffer_clear(prev, prev->area);
191        t->cleared = false;
192    }
193    tui_render_diff(prev, cur, &o);
194    outs(&o, "\x1b[?25l");
195    tui_term_write(o.data, o.len);
196    tui_out_free(&o);
197    t->current = 1 - t->current;
198}