vis
a vi-like editor based on Plan 9's structural regular expressions
git clone https://9o.is/git/vis.git
ui-terminal-curses.c
(7773B)
1 /* This file is included from ui-terminal.c */
2 #include <stdio.h>
3 #include <curses.h>
4
5 #define UI_TERMKEY_FLAGS (TERMKEY_FLAG_UTF8|TERMKEY_FLAG_NOTERMIOS)
6
7 #define CELL_COLOR_BLACK COLOR_BLACK
8 #define CELL_COLOR_RED COLOR_RED
9 #define CELL_COLOR_GREEN COLOR_GREEN
10 #define CELL_COLOR_YELLOW COLOR_YELLOW
11 #define CELL_COLOR_BLUE COLOR_BLUE
12 #define CELL_COLOR_MAGENTA COLOR_MAGENTA
13 #define CELL_COLOR_CYAN COLOR_CYAN
14 #define CELL_COLOR_WHITE COLOR_WHITE
15 #define CELL_COLOR_DEFAULT (-1)
16
17 #ifndef A_ITALIC
18 #define A_ITALIC A_NORMAL
19 #endif
20 #define CELL_ATTR_NORMAL A_NORMAL
21 #define CELL_ATTR_UNDERLINE A_UNDERLINE
22 #define CELL_ATTR_REVERSE A_REVERSE
23 #define CELL_ATTR_BLINK A_BLINK
24 #define CELL_ATTR_BOLD A_BOLD
25 #define CELL_ATTR_ITALIC A_ITALIC
26 #define CELL_ATTR_DIM A_DIM
27
28 #ifdef NCURSES_VERSION
29 # ifndef NCURSES_EXT_COLORS
30 # define NCURSES_EXT_COLORS 0
31 # endif
32 # if !NCURSES_EXT_COLORS
33 # define MAX_COLOR_PAIRS MIN(COLOR_PAIRS, 256)
34 # endif
35 #endif
36 #ifndef MAX_COLOR_PAIRS
37 # define MAX_COLOR_PAIRS COLOR_PAIRS
38 #endif
39
40 #define MAX_COLOR_CLOBBER 240
41
42 static int change_colors = -1;
43 static short default_fg = -1;
44 static short default_bg = -1;
45
46 static inline bool cell_color_equal(CellColor c1, CellColor c2) {
47 return c1 == c2;
48 }
49
50 /* Calculate r,g,b components of one of the standard upper 240 colors */
51 static void get_6cube_rgb(unsigned int n, int *r, int *g, int *b)
52 {
53 if (n < 16) {
54 return;
55 } else if (n < 232) {
56 n -= 16;
57 *r = (n / 36) ? (n / 36) * 40 + 55 : 0;
58 *g = ((n / 6) % 6) ? ((n / 6) % 6) * 40 + 55 : 0;
59 *b = (n % 6) ? (n % 6) * 40 + 55 : 0;
60 } else if (n < 256) {
61 n -= 232;
62 *r = n * 10 + 8;
63 *g = n * 10 + 8;
64 *b = n * 10 + 8;
65 }
66 }
67
68 /* Reset color palette to default values using OSC 104 */
69 static void undo_palette(void)
70 {
71 fputs("\033]104;\a", stderr);
72 fflush(stderr);
73 }
74
75 /* Work out the nearest color from the 256 color set, or perhaps exactly. */
76 static CellColor color_rgb(Ui *ui, uint8_t r, uint8_t g, uint8_t b)
77 {
78 static short color_clobber_idx = 0;
79 static uint32_t clobbering_colors[MAX_COLOR_CLOBBER];
80
81 if (change_colors == -1)
82 change_colors = ui->vis->change_colors && can_change_color() && COLORS >= 256;
83 if (change_colors) {
84 uint32_t hexrep = (r << 16) | (g << 8) | b;
85 for (short i = 0; i < MAX_COLOR_CLOBBER; ++i) {
86 if (clobbering_colors[i] == hexrep)
87 return i + 16;
88 else if (!clobbering_colors[i])
89 break;
90 }
91
92 short i = color_clobber_idx;
93 clobbering_colors[i] = hexrep;
94 init_color(i + 16, (r * 1000) / 0xff, (g * 1000) / 0xff,
95 (b * 1000) / 0xff);
96
97 /* in the unlikely case a user requests this many colors, reuse old slots */
98 if (++color_clobber_idx >= MAX_COLOR_CLOBBER)
99 color_clobber_idx = 0;
100
101 return i + 16;
102 }
103
104 static const unsigned char color_256_to_16[256] = {
105 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
106 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
107 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
108 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
109 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
110 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
111 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
112 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
113 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
114 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
115 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
116 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
117 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
118 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
119 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
120 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
121 };
122
123 int i = 0;
124 if ((!r || (r - 55) % 40 == 0) &&
125 (!g || (g - 55) % 40 == 0) &&
126 (!b || (b - 55) % 40 == 0)) {
127 i = 16;
128 i += r ? ((r - 55) / 40) * 36 : 0;
129 i += g ? ((g - 55) / 40) * 6 : 0;
130 i += g ? ((b - 55) / 40) : 0;
131 } else if (r == g && g == b && (r - 8) % 10 == 0 && r < 239) {
132 i = 232 + ((r - 8) / 10);
133 } else {
134 unsigned lowest = UINT_MAX;
135 for (int j = 16; j < 256; ++j) {
136 int jr = 0, jg = 0, jb = 0;
137 get_6cube_rgb(j, &jr, &jg, &jb);
138 int dr = jr - r;
139 int dg = jg - g;
140 int db = jb - b;
141 unsigned int distance = dr * dr + dg * dg + db * db;
142 if (distance < lowest) {
143 lowest = distance;
144 i = j;
145 }
146 }
147 }
148
149 if (COLORS <= 16)
150 return color_256_to_16[i];
151 return i;
152 }
153
154 static CellColor color_terminal(Ui *ui, uint8_t index) {
155 return index;
156 }
157
158 static inline unsigned int color_pair_hash(short fg, short bg) {
159 if (fg == CELL_COLOR_DEFAULT)
160 fg = COLORS;
161 if (bg == CELL_COLOR_DEFAULT)
162 bg = COLORS + 1;
163 return fg * (COLORS + 2) + bg;
164 }
165
166 static short color_pair_get(short fg, short bg) {
167 static bool has_default_colors;
168 static short *color2palette;
169 static short color_pairs_max, color_pair_current;
170
171 if (!color2palette) {
172 pair_content(0, &default_fg, &default_bg);
173 has_default_colors = (use_default_colors() == OK);
174 color_pairs_max = MIN(MAX_COLOR_PAIRS, SHRT_MAX);
175 if (COLORS)
176 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
177 }
178
179 if (fg >= COLORS)
180 fg = default_fg;
181 if (bg >= COLORS)
182 bg = default_bg;
183
184 if (!has_default_colors) {
185 if (fg == -1)
186 fg = default_fg;
187 if (bg == -1)
188 bg = default_bg;
189 }
190
191 if (!color2palette)
192 return 0;
193
194 unsigned int index = color_pair_hash(fg, bg);
195 if (color2palette[index] == 0) {
196 short oldfg, oldbg;
197 if (++color_pair_current >= color_pairs_max)
198 color_pair_current = 1;
199 pair_content(color_pair_current, &oldfg, &oldbg);
200 unsigned int old_index = color_pair_hash(oldfg, oldbg);
201 if (init_pair(color_pair_current, fg, bg) == OK) {
202 color2palette[old_index] = 0;
203 color2palette[index] = color_pair_current;
204 }
205 }
206
207 return color2palette[index];
208 }
209
210 static inline attr_t style_to_attr(CellStyle *style) {
211 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
212 }
213
214 static void ui_term_backend_blit(Ui *tui) {
215 int w = tui->width, h = tui->height;
216 Cell *cell = tui->cells;
217 for (int y = 0; y < h; y++) {
218 for (int x = 0; x < w; x++) {
219 attrset(style_to_attr(&cell->style));
220 mvaddstr(y, x, cell->data);
221 cell++;
222 }
223 }
224 wnoutrefresh(stdscr);
225 if (tui->doupdate)
226 doupdate();
227 }
228
229 static void ui_term_backend_clear(Ui *tui) {
230 clear();
231 }
232
233 static bool ui_term_backend_resize(Ui *tui, int width, int height) {
234 return resizeterm(height, width) == OK &&
235 wresize(stdscr, height, width) == OK;
236 }
237
238 static void ui_term_backend_save(Ui *tui, bool fscr) {
239 curs_set(1);
240 if (fscr) {
241 def_prog_mode();
242 endwin();
243 } else {
244 reset_shell_mode();
245 }
246 }
247
248 static void ui_term_backend_restore(Ui *tui) {
249 reset_prog_mode();
250 redrawwin(stdscr);
251 curs_set(0);
252 }
253
254 int ui_terminal_colors(void) {
255 return COLORS;
256 }
257
258 static bool ui_term_backend_init(Ui *tui, char *term) {
259 if (!newterm(term, stderr, stdin)) {
260 snprintf(tui->info, sizeof(tui->info), "Warning: unknown term `%s'", term);
261 if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin))
262 return false;
263 }
264 start_color();
265 use_default_colors();
266 cbreak();
267 noecho();
268 nonl();
269 keypad(stdscr, TRUE);
270 meta(stdscr, TRUE);
271 curs_set(0);
272 return true;
273 }
274
275 static bool ui_backend_init(Ui *ui) {
276 return true;
277 }
278
279 void ui_terminal_resume(Ui *term) { }
280
281 static void ui_term_backend_suspend(Ui *term) {
282 if (change_colors == 1)
283 undo_palette();
284 }
285
286 static void ui_term_backend_free(Ui *term) {
287 ui_term_backend_suspend(term);
288 endwin();
289 }
290
291 static bool is_default_color(CellColor c) {
292 return c == CELL_COLOR_DEFAULT;
293 }
294
295 static void cursor_visible(bool visible) {
296 curs_set(visible);
297 }