==> 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}