Update README to include recent changes to supported registers
[vis.git] / ui-curses.c
blob6ab34a306c6773dfe9618f7ba4455d638754e003
1 /*
2 * Copyright (c) 2014-2015 Marc André Tanner <mat at brain-dump.org>
3 * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 /* parts of the color handling code originates from tmux/colour.c */
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <limits.h>
23 #include <ctype.h>
24 #include <signal.h>
25 #include <locale.h>
26 #include <poll.h>
27 #include <sys/ioctl.h>
29 #include "ui-curses.h"
30 #include "vis.h"
31 #include "text.h"
32 #include "util.h"
33 #include "text-util.h"
35 #ifdef NCURSES_VERSION
36 # ifndef NCURSES_EXT_COLORS
37 # define NCURSES_EXT_COLORS 0
38 # endif
39 # if !NCURSES_EXT_COLORS
40 # define MAX_COLOR_PAIRS 256
41 # endif
42 #endif
43 #ifndef MAX_COLOR_PAIRS
44 # define MAX_COLOR_PAIRS COLOR_PAIRS
45 #endif
47 #ifdef PDCURSES
48 int ESCDELAY;
49 #endif
50 #ifndef NCURSES_REENTRANT
51 # define set_escdelay(d) (ESCDELAY = (d))
52 #endif
54 #define CONTROL(k) ((k)&0x1F)
56 #if 0
57 #define wresize(win, y, x) do { \
58 if (wresize(win, y, x) == ERR) { \
59 printf("ERROR resizing: %d x %d\n", x, y); \
60 } else { \
61 printf("OK resizing: %d x %d\n", x, y); \
62 } \
63 fflush(stdout); \
64 } while (0);
66 #define mvwin(win, y, x) do { \
67 if (mvwin(win, y, x) == ERR) { \
68 printf("ERROR moving: %d x %d\n", x, y); \
69 } else { \
70 printf("OK moving: %d x %d\n", x, y); \
71 } \
72 fflush(stdout); \
73 } while (0);
74 #endif
76 typedef struct {
77 attr_t attr;
78 short fg, bg;
79 } CellStyle;
81 typedef struct UiCursesWin UiCursesWin;
83 typedef struct {
84 Ui ui; /* generic ui interface, has to be the first struct member */
85 Vis *vis; /* editor instance to which this ui belongs */
86 UiCursesWin *windows; /* all windows managed by this ui */
87 UiCursesWin *selwin; /* the currently selected layout */
88 char info[255]; /* info message displayed at the bottom of the screen */
89 int width, height; /* terminal dimensions available for all windows */
90 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
91 TermKey *termkey; /* libtermkey instance to handle keyboard input */
92 char key[64]; /* string representation of last pressed key */
93 } UiCurses;
95 struct UiCursesWin {
96 UiWin uiwin; /* generic interface, has to be the first struct member */
97 UiCurses *ui; /* ui which manages this window */
98 File *file; /* file being displayed in this window */
99 View *view; /* current viewport */
100 WINDOW *win; /* curses window for the text area */
101 WINDOW *winstatus; /* curses window for the status bar */
102 WINDOW *winside; /* curses window for the side bar (line numbers) */
103 int width, height; /* window dimension including status bar */
104 int x, y; /* window position */
105 int sidebar_width; /* width of the sidebar showing line numbers etc. */
106 UiCursesWin *next, *prev; /* pointers to neighbouring windows */
107 enum UiOption options; /* display settings for this window */
108 CellStyle styles[UI_STYLE_MAX];
111 static volatile sig_atomic_t need_resize; /* TODO */
113 static void sigwinch_handler(int sig) {
114 need_resize = true;
117 typedef struct {
118 unsigned char i;
119 unsigned char r;
120 unsigned char g;
121 unsigned char b;
122 } Color;
124 static int color_compare(const void *lhs0, const void *rhs0) {
125 const Color *lhs = lhs0, *rhs = rhs0;
127 if (lhs->r < rhs->r)
128 return -1;
129 if (lhs->r > rhs->r)
130 return 1;
132 if (lhs->g < rhs->g)
133 return -1;
134 if (lhs->g > rhs->g)
135 return 1;
137 if (lhs->b < rhs->b)
138 return -1;
139 if (lhs->b > rhs->b)
140 return 1;
142 return 0;
145 /* Work out the nearest color from the 256 color set. */
146 static int color_find_rgb(unsigned char r, unsigned char g, unsigned char b)
148 static const Color color_from_256[] = {
149 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
150 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
151 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
152 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
153 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
154 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
155 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
156 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
157 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
158 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
159 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
160 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
161 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
162 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
163 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
164 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
165 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
166 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
167 { 36, 0x5f, 0x00, 0x00 }, { 37, 0x5f, 0x00, 0x5f },
168 { 38, 0x5f, 0x00, 0x87 }, { 39, 0x5f, 0x00, 0xaf },
169 { 40, 0x5f, 0x00, 0xd7 }, { 41, 0x5f, 0x00, 0xff },
170 { 42, 0x5f, 0x5f, 0x00 }, { 43, 0x5f, 0x5f, 0x5f },
171 { 44, 0x5f, 0x5f, 0x87 }, { 45, 0x5f, 0x5f, 0xaf },
172 { 46, 0x5f, 0x5f, 0xd7 }, { 47, 0x5f, 0x5f, 0xff },
173 { 48, 0x5f, 0x87, 0x00 }, { 49, 0x5f, 0x87, 0x5f },
174 { 50, 0x5f, 0x87, 0x87 }, { 51, 0x5f, 0x87, 0xaf },
175 { 52, 0x5f, 0x87, 0xd7 }, { 53, 0x5f, 0x87, 0xff },
176 { 54, 0x5f, 0xaf, 0x00 }, { 55, 0x5f, 0xaf, 0x5f },
177 { 56, 0x5f, 0xaf, 0x87 }, { 57, 0x5f, 0xaf, 0xaf },
178 { 58, 0x5f, 0xaf, 0xd7 }, { 59, 0x5f, 0xaf, 0xff },
179 { 60, 0x5f, 0xd7, 0x00 }, { 61, 0x5f, 0xd7, 0x5f },
180 { 62, 0x5f, 0xd7, 0x87 }, { 63, 0x5f, 0xd7, 0xaf },
181 { 64, 0x5f, 0xd7, 0xd7 }, { 65, 0x5f, 0xd7, 0xff },
182 { 66, 0x5f, 0xff, 0x00 }, { 67, 0x5f, 0xff, 0x5f },
183 { 68, 0x5f, 0xff, 0x87 }, { 69, 0x5f, 0xff, 0xaf },
184 { 70, 0x5f, 0xff, 0xd7 }, { 71, 0x5f, 0xff, 0xff },
185 { 72, 0x87, 0x00, 0x00 }, { 73, 0x87, 0x00, 0x5f },
186 { 74, 0x87, 0x00, 0x87 }, { 75, 0x87, 0x00, 0xaf },
187 { 76, 0x87, 0x00, 0xd7 }, { 77, 0x87, 0x00, 0xff },
188 { 78, 0x87, 0x5f, 0x00 }, { 79, 0x87, 0x5f, 0x5f },
189 { 80, 0x87, 0x5f, 0x87 }, { 81, 0x87, 0x5f, 0xaf },
190 { 82, 0x87, 0x5f, 0xd7 }, { 83, 0x87, 0x5f, 0xff },
191 { 84, 0x87, 0x87, 0x00 }, { 85, 0x87, 0x87, 0x5f },
192 { 86, 0x87, 0x87, 0x87 }, { 87, 0x87, 0x87, 0xaf },
193 { 88, 0x87, 0x87, 0xd7 }, { 89, 0x87, 0x87, 0xff },
194 { 90, 0x87, 0xaf, 0x00 }, { 91, 0x87, 0xaf, 0x5f },
195 { 92, 0x87, 0xaf, 0x87 }, { 93, 0x87, 0xaf, 0xaf },
196 { 94, 0x87, 0xaf, 0xd7 }, { 95, 0x87, 0xaf, 0xff },
197 { 96, 0x87, 0xd7, 0x00 }, { 97, 0x87, 0xd7, 0x5f },
198 { 98, 0x87, 0xd7, 0x87 }, { 99, 0x87, 0xd7, 0xaf },
199 { 100, 0x87, 0xd7, 0xd7 }, { 101, 0x87, 0xd7, 0xff },
200 { 102, 0x87, 0xff, 0x00 }, { 103, 0x87, 0xff, 0x5f },
201 { 104, 0x87, 0xff, 0x87 }, { 105, 0x87, 0xff, 0xaf },
202 { 106, 0x87, 0xff, 0xd7 }, { 107, 0x87, 0xff, 0xff },
203 { 108, 0xaf, 0x00, 0x00 }, { 109, 0xaf, 0x00, 0x5f },
204 { 110, 0xaf, 0x00, 0x87 }, { 111, 0xaf, 0x00, 0xaf },
205 { 112, 0xaf, 0x00, 0xd7 }, { 113, 0xaf, 0x00, 0xff },
206 { 114, 0xaf, 0x5f, 0x00 }, { 115, 0xaf, 0x5f, 0x5f },
207 { 116, 0xaf, 0x5f, 0x87 }, { 117, 0xaf, 0x5f, 0xaf },
208 { 118, 0xaf, 0x5f, 0xd7 }, { 119, 0xaf, 0x5f, 0xff },
209 { 120, 0xaf, 0x87, 0x00 }, { 121, 0xaf, 0x87, 0x5f },
210 { 122, 0xaf, 0x87, 0x87 }, { 123, 0xaf, 0x87, 0xaf },
211 { 124, 0xaf, 0x87, 0xd7 }, { 125, 0xaf, 0x87, 0xff },
212 { 126, 0xaf, 0xaf, 0x00 }, { 127, 0xaf, 0xaf, 0x5f },
213 { 128, 0xaf, 0xaf, 0x87 }, { 129, 0xaf, 0xaf, 0xaf },
214 { 130, 0xaf, 0xaf, 0xd7 }, { 131, 0xaf, 0xaf, 0xff },
215 { 132, 0xaf, 0xd7, 0x00 }, { 133, 0xaf, 0xd7, 0x5f },
216 { 134, 0xaf, 0xd7, 0x87 }, { 135, 0xaf, 0xd7, 0xaf },
217 { 136, 0xaf, 0xd7, 0xd7 }, { 137, 0xaf, 0xd7, 0xff },
218 { 138, 0xaf, 0xff, 0x00 }, { 139, 0xaf, 0xff, 0x5f },
219 { 140, 0xaf, 0xff, 0x87 }, { 141, 0xaf, 0xff, 0xaf },
220 { 142, 0xaf, 0xff, 0xd7 }, { 143, 0xaf, 0xff, 0xff },
221 { 144, 0xd7, 0x00, 0x00 }, { 145, 0xd7, 0x00, 0x5f },
222 { 146, 0xd7, 0x00, 0x87 }, { 147, 0xd7, 0x00, 0xaf },
223 { 148, 0xd7, 0x00, 0xd7 }, { 149, 0xd7, 0x00, 0xff },
224 { 150, 0xd7, 0x5f, 0x00 }, { 151, 0xd7, 0x5f, 0x5f },
225 { 152, 0xd7, 0x5f, 0x87 }, { 153, 0xd7, 0x5f, 0xaf },
226 { 154, 0xd7, 0x5f, 0xd7 }, { 155, 0xd7, 0x5f, 0xff },
227 { 156, 0xd7, 0x87, 0x00 }, { 157, 0xd7, 0x87, 0x5f },
228 { 158, 0xd7, 0x87, 0x87 }, { 159, 0xd7, 0x87, 0xaf },
229 { 160, 0xd7, 0x87, 0xd7 }, { 161, 0xd7, 0x87, 0xff },
230 { 162, 0xd7, 0xaf, 0x00 }, { 163, 0xd7, 0xaf, 0x5f },
231 { 164, 0xd7, 0xaf, 0x87 }, { 165, 0xd7, 0xaf, 0xaf },
232 { 166, 0xd7, 0xaf, 0xd7 }, { 167, 0xd7, 0xaf, 0xff },
233 { 168, 0xd7, 0xd7, 0x00 }, { 169, 0xd7, 0xd7, 0x5f },
234 { 170, 0xd7, 0xd7, 0x87 }, { 171, 0xd7, 0xd7, 0xaf },
235 { 172, 0xd7, 0xd7, 0xd7 }, { 173, 0xd7, 0xd7, 0xff },
236 { 174, 0xd7, 0xff, 0x00 }, { 175, 0xd7, 0xff, 0x5f },
237 { 176, 0xd7, 0xff, 0x87 }, { 177, 0xd7, 0xff, 0xaf },
238 { 178, 0xd7, 0xff, 0xd7 }, { 179, 0xd7, 0xff, 0xff },
239 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
240 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
241 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
242 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
243 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
244 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
245 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
246 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
247 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
248 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
249 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
250 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
251 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
252 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
253 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
254 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
255 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
256 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
257 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
258 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
259 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
260 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
261 { 224, 0x58, 0x58, 0x58 }, { 225, 0x62, 0x62, 0x62 },
262 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
263 { 228, 0x80, 0x80, 0x80 }, { 229, 0x8a, 0x8a, 0x8a },
264 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
265 { 232, 0xa8, 0xa8, 0xa8 }, { 233, 0xb2, 0xb2, 0xb2 },
266 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
267 { 236, 0xd0, 0xd0, 0xd0 }, { 237, 0xda, 0xda, 0xda },
268 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
271 static const Color color_to_256[] = {
272 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
273 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
274 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
275 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
276 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
277 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
278 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
279 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
280 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
281 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
282 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
283 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
284 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
285 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
286 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
287 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
288 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
289 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
290 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
291 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
292 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
293 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
294 { 224, 0x58, 0x58, 0x58 }, { 36, 0x5f, 0x00, 0x00 },
295 { 37, 0x5f, 0x00, 0x5f }, { 38, 0x5f, 0x00, 0x87 },
296 { 39, 0x5f, 0x00, 0xaf }, { 40, 0x5f, 0x00, 0xd7 },
297 { 41, 0x5f, 0x00, 0xff }, { 42, 0x5f, 0x5f, 0x00 },
298 { 43, 0x5f, 0x5f, 0x5f }, { 44, 0x5f, 0x5f, 0x87 },
299 { 45, 0x5f, 0x5f, 0xaf }, { 46, 0x5f, 0x5f, 0xd7 },
300 { 47, 0x5f, 0x5f, 0xff }, { 48, 0x5f, 0x87, 0x00 },
301 { 49, 0x5f, 0x87, 0x5f }, { 50, 0x5f, 0x87, 0x87 },
302 { 51, 0x5f, 0x87, 0xaf }, { 52, 0x5f, 0x87, 0xd7 },
303 { 53, 0x5f, 0x87, 0xff }, { 54, 0x5f, 0xaf, 0x00 },
304 { 55, 0x5f, 0xaf, 0x5f }, { 56, 0x5f, 0xaf, 0x87 },
305 { 57, 0x5f, 0xaf, 0xaf }, { 58, 0x5f, 0xaf, 0xd7 },
306 { 59, 0x5f, 0xaf, 0xff }, { 60, 0x5f, 0xd7, 0x00 },
307 { 61, 0x5f, 0xd7, 0x5f }, { 62, 0x5f, 0xd7, 0x87 },
308 { 63, 0x5f, 0xd7, 0xaf }, { 64, 0x5f, 0xd7, 0xd7 },
309 { 65, 0x5f, 0xd7, 0xff }, { 66, 0x5f, 0xff, 0x00 },
310 { 67, 0x5f, 0xff, 0x5f }, { 68, 0x5f, 0xff, 0x87 },
311 { 69, 0x5f, 0xff, 0xaf }, { 70, 0x5f, 0xff, 0xd7 },
312 { 71, 0x5f, 0xff, 0xff }, { 225, 0x62, 0x62, 0x62 },
313 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
314 { 228, 0x80, 0x80, 0x80 }, { 72, 0x87, 0x00, 0x00 },
315 { 73, 0x87, 0x00, 0x5f }, { 74, 0x87, 0x00, 0x87 },
316 { 75, 0x87, 0x00, 0xaf }, { 76, 0x87, 0x00, 0xd7 },
317 { 77, 0x87, 0x00, 0xff }, { 78, 0x87, 0x5f, 0x00 },
318 { 79, 0x87, 0x5f, 0x5f }, { 80, 0x87, 0x5f, 0x87 },
319 { 81, 0x87, 0x5f, 0xaf }, { 82, 0x87, 0x5f, 0xd7 },
320 { 83, 0x87, 0x5f, 0xff }, { 84, 0x87, 0x87, 0x00 },
321 { 85, 0x87, 0x87, 0x5f }, { 86, 0x87, 0x87, 0x87 },
322 { 87, 0x87, 0x87, 0xaf }, { 88, 0x87, 0x87, 0xd7 },
323 { 89, 0x87, 0x87, 0xff }, { 90, 0x87, 0xaf, 0x00 },
324 { 91, 0x87, 0xaf, 0x5f }, { 92, 0x87, 0xaf, 0x87 },
325 { 93, 0x87, 0xaf, 0xaf }, { 94, 0x87, 0xaf, 0xd7 },
326 { 95, 0x87, 0xaf, 0xff }, { 96, 0x87, 0xd7, 0x00 },
327 { 97, 0x87, 0xd7, 0x5f }, { 98, 0x87, 0xd7, 0x87 },
328 { 99, 0x87, 0xd7, 0xaf }, { 100, 0x87, 0xd7, 0xd7 },
329 { 101, 0x87, 0xd7, 0xff }, { 102, 0x87, 0xff, 0x00 },
330 { 103, 0x87, 0xff, 0x5f }, { 104, 0x87, 0xff, 0x87 },
331 { 105, 0x87, 0xff, 0xaf }, { 106, 0x87, 0xff, 0xd7 },
332 { 107, 0x87, 0xff, 0xff }, { 229, 0x8a, 0x8a, 0x8a },
333 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
334 { 232, 0xa8, 0xa8, 0xa8 }, { 108, 0xaf, 0x00, 0x00 },
335 { 109, 0xaf, 0x00, 0x5f }, { 110, 0xaf, 0x00, 0x87 },
336 { 111, 0xaf, 0x00, 0xaf }, { 112, 0xaf, 0x00, 0xd7 },
337 { 113, 0xaf, 0x00, 0xff }, { 114, 0xaf, 0x5f, 0x00 },
338 { 115, 0xaf, 0x5f, 0x5f }, { 116, 0xaf, 0x5f, 0x87 },
339 { 117, 0xaf, 0x5f, 0xaf }, { 118, 0xaf, 0x5f, 0xd7 },
340 { 119, 0xaf, 0x5f, 0xff }, { 120, 0xaf, 0x87, 0x00 },
341 { 121, 0xaf, 0x87, 0x5f }, { 122, 0xaf, 0x87, 0x87 },
342 { 123, 0xaf, 0x87, 0xaf }, { 124, 0xaf, 0x87, 0xd7 },
343 { 125, 0xaf, 0x87, 0xff }, { 126, 0xaf, 0xaf, 0x00 },
344 { 127, 0xaf, 0xaf, 0x5f }, { 128, 0xaf, 0xaf, 0x87 },
345 { 129, 0xaf, 0xaf, 0xaf }, { 130, 0xaf, 0xaf, 0xd7 },
346 { 131, 0xaf, 0xaf, 0xff }, { 132, 0xaf, 0xd7, 0x00 },
347 { 133, 0xaf, 0xd7, 0x5f }, { 134, 0xaf, 0xd7, 0x87 },
348 { 135, 0xaf, 0xd7, 0xaf }, { 136, 0xaf, 0xd7, 0xd7 },
349 { 137, 0xaf, 0xd7, 0xff }, { 138, 0xaf, 0xff, 0x00 },
350 { 139, 0xaf, 0xff, 0x5f }, { 140, 0xaf, 0xff, 0x87 },
351 { 141, 0xaf, 0xff, 0xaf }, { 142, 0xaf, 0xff, 0xd7 },
352 { 143, 0xaf, 0xff, 0xff }, { 233, 0xb2, 0xb2, 0xb2 },
353 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
354 { 236, 0xd0, 0xd0, 0xd0 }, { 144, 0xd7, 0x00, 0x00 },
355 { 145, 0xd7, 0x00, 0x5f }, { 146, 0xd7, 0x00, 0x87 },
356 { 147, 0xd7, 0x00, 0xaf }, { 148, 0xd7, 0x00, 0xd7 },
357 { 149, 0xd7, 0x00, 0xff }, { 150, 0xd7, 0x5f, 0x00 },
358 { 151, 0xd7, 0x5f, 0x5f }, { 152, 0xd7, 0x5f, 0x87 },
359 { 153, 0xd7, 0x5f, 0xaf }, { 154, 0xd7, 0x5f, 0xd7 },
360 { 155, 0xd7, 0x5f, 0xff }, { 156, 0xd7, 0x87, 0x00 },
361 { 157, 0xd7, 0x87, 0x5f }, { 158, 0xd7, 0x87, 0x87 },
362 { 159, 0xd7, 0x87, 0xaf }, { 160, 0xd7, 0x87, 0xd7 },
363 { 161, 0xd7, 0x87, 0xff }, { 162, 0xd7, 0xaf, 0x00 },
364 { 163, 0xd7, 0xaf, 0x5f }, { 164, 0xd7, 0xaf, 0x87 },
365 { 165, 0xd7, 0xaf, 0xaf }, { 166, 0xd7, 0xaf, 0xd7 },
366 { 167, 0xd7, 0xaf, 0xff }, { 168, 0xd7, 0xd7, 0x00 },
367 { 169, 0xd7, 0xd7, 0x5f }, { 170, 0xd7, 0xd7, 0x87 },
368 { 171, 0xd7, 0xd7, 0xaf }, { 172, 0xd7, 0xd7, 0xd7 },
369 { 173, 0xd7, 0xd7, 0xff }, { 174, 0xd7, 0xff, 0x00 },
370 { 175, 0xd7, 0xff, 0x5f }, { 176, 0xd7, 0xff, 0x87 },
371 { 177, 0xd7, 0xff, 0xaf }, { 178, 0xd7, 0xff, 0xd7 },
372 { 179, 0xd7, 0xff, 0xff }, { 237, 0xda, 0xda, 0xda },
373 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
374 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
375 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
376 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
377 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
378 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
379 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
380 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
381 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
382 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
383 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
384 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
385 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
386 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
387 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
388 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
389 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
390 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
391 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
394 static const unsigned char color_256_to_16[256] = {
395 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
396 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
397 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
398 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
399 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
400 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
401 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
402 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
403 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
404 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
405 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
406 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
407 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
408 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
409 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
410 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
413 Color rgb = { .r = r, .g = g, .b = b };
414 const Color *found = bsearch(&rgb, color_to_256, LENGTH(color_to_256),
415 sizeof color_to_256[0], color_compare);
417 if (!found) {
418 unsigned lowest = UINT_MAX;
419 found = color_from_256;
420 for (int i = 0; i < 240; i++) {
421 int dr = (int)color_from_256[i].r - r;
422 int dg = (int)color_from_256[i].g - g;
423 int db = (int)color_from_256[i].b - b;
425 unsigned int distance = dr * dr + dg * dg + db * db;
426 if (distance < lowest) {
427 lowest = distance;
428 found = &color_from_256[i];
433 if (COLORS <= 16)
434 return color_256_to_16[found->i + 16];
435 return found->i + 16;
438 /* Convert color from string. */
439 static int color_fromstring(const char *s)
441 if (!s)
442 return -1;
443 if (*s == '#' && strlen(s) == 7) {
444 const char *cp;
445 unsigned char r, g, b;
446 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
447 if (*cp != '\0')
448 return -1;
449 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
450 if (n != 3)
451 return -1;
452 return color_find_rgb(r, g, b);
453 } else if ('0' <= *s && *s <= '9') {
454 int col = atoi(s);
455 return (col <= 0 || col > 255) ? -1 : col;
458 if (strcasecmp(s, "black") == 0)
459 return 0;
460 if (strcasecmp(s, "red") == 0)
461 return 1;
462 if (strcasecmp(s, "green") == 0)
463 return 2;
464 if (strcasecmp(s, "yellow") == 0)
465 return 3;
466 if (strcasecmp(s, "blue") == 0)
467 return 4;
468 if (strcasecmp(s, "magenta") == 0)
469 return 5;
470 if (strcasecmp(s, "cyan") == 0)
471 return 6;
472 if (strcasecmp(s, "white") == 0)
473 return 7;
474 return -1;
477 static inline unsigned int color_pair_hash(short fg, short bg) {
478 if (fg == -1)
479 fg = COLORS;
480 if (bg == -1)
481 bg = COLORS + 1;
482 return fg * (COLORS + 2) + bg;
485 static short color_pair_get(short fg, short bg) {
486 static bool has_default_colors;
487 static short *color2palette, default_fg, default_bg;
488 static short color_pairs_max, color_pair_current;
490 if (!color2palette) {
491 pair_content(0, &default_fg, &default_bg);
492 if (default_fg == -1)
493 default_fg = COLOR_WHITE;
494 if (default_bg == -1)
495 default_bg = COLOR_BLACK;
496 has_default_colors = (use_default_colors() == OK);
497 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
498 if (COLORS)
499 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
502 if (fg >= COLORS)
503 fg = default_fg;
504 if (bg >= COLORS)
505 bg = default_bg;
507 if (!has_default_colors) {
508 if (fg == -1)
509 fg = default_fg;
510 if (bg == -1)
511 bg = default_bg;
514 if (!color2palette || (fg == -1 && bg == -1))
515 return 0;
517 unsigned int index = color_pair_hash(fg, bg);
518 if (color2palette[index] == 0) {
519 short oldfg, oldbg;
520 if (++color_pair_current >= color_pairs_max)
521 color_pair_current = 1;
522 pair_content(color_pair_current, &oldfg, &oldbg);
523 unsigned int old_index = color_pair_hash(oldfg, oldbg);
524 if (init_pair(color_pair_current, fg, bg) == OK) {
525 color2palette[old_index] = 0;
526 color2palette[index] = color_pair_current;
530 return color2palette[index];
533 static inline attr_t style_to_attr(CellStyle *style) {
534 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
537 static bool ui_window_syntax_style(UiWin *w, int id, const char *style) {
538 UiCursesWin *win = (UiCursesWin*)w;
539 if (id >= UI_STYLE_MAX)
540 return false;
541 if (!style)
542 return true;
543 CellStyle cell_style = win->styles[UI_STYLE_DEFAULT];
544 char *style_copy = strdup(style), *option = style_copy, *next, *p;
545 while (option) {
546 if ((next = strchr(option, ',')))
547 *next++ = '\0';
548 if ((p = strchr(option, ':')))
549 *p++ = '\0';
550 if (!strcasecmp(option, "reverse")) {
551 cell_style.attr |= A_REVERSE;
552 } else if (!strcasecmp(option, "bold")) {
553 cell_style.attr |= A_BOLD;
554 } else if (!strcasecmp(option, "notbold")) {
555 cell_style.attr &= ~A_BOLD;
556 #ifdef A_ITALIC
557 } else if (!strcasecmp(option, "italics")) {
558 cell_style.attr |= A_ITALIC;
559 } else if (!strcasecmp(option, "notitalics")) {
560 cell_style.attr &= ~A_ITALIC;
561 #endif
562 } else if (!strcasecmp(option, "underlined")) {
563 cell_style.attr |= A_UNDERLINE;
564 } else if (!strcasecmp(option, "notunderlined")) {
565 cell_style.attr &= ~A_UNDERLINE;
566 } else if (!strcasecmp(option, "blink")) {
567 cell_style.attr |= A_BLINK;
568 } else if (!strcasecmp(option, "notblink")) {
569 cell_style.attr &= ~A_BLINK;
570 } else if (!strcasecmp(option, "fore")) {
571 cell_style.fg = color_fromstring(p);
572 } else if (!strcasecmp(option, "back")) {
573 cell_style.bg = color_fromstring(p);
575 option = next;
577 win->styles[id] = cell_style;
578 free(style_copy);
579 return true;
582 static void ui_window_resize(UiCursesWin *win, int width, int height) {
583 win->width = width;
584 win->height = height;
585 if (win->winstatus)
586 wresize(win->winstatus, 1, width);
587 wresize(win->win, win->winstatus ? height - 1 : height, width - win->sidebar_width);
588 if (win->winside)
589 wresize(win->winside, height-1, win->sidebar_width);
590 view_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height);
591 view_update(win->view);
594 static void ui_window_move(UiCursesWin *win, int x, int y) {
595 win->x = x;
596 win->y = y;
597 mvwin(win->win, y, x + win->sidebar_width);
598 if (win->winside)
599 mvwin(win->winside, y, x);
600 if (win->winstatus)
601 mvwin(win->winstatus, y + win->height - 1, x);
604 static bool ui_window_draw_sidebar(UiCursesWin *win) {
605 if (!win->winside)
606 return true;
607 const Line *line = view_lines_get(win->view);
608 int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1;
609 if (win->sidebar_width != sidebar_width) {
610 win->sidebar_width = sidebar_width;
611 ui_window_resize(win, win->width, win->height);
612 ui_window_move(win, win->x, win->y);
613 return false;
614 } else {
615 int i = 0;
616 size_t prev_lineno = 0;
617 size_t cursor_lineno = view_cursor_getpos(win->view).line;
618 werase(win->winside);
619 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
620 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
621 for (const Line *l = line; l; l = l->next, i++) {
622 if (l->lineno && l->lineno != prev_lineno) {
623 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
624 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
625 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
626 size_t rel = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
627 if (l->lineno > cursor_lineno)
628 rel = l->lineno - cursor_lineno;
629 else if (l->lineno < cursor_lineno)
630 rel = cursor_lineno - l->lineno;
631 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
634 prev_lineno = l->lineno;
636 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
637 return true;
641 static void ui_window_draw_status(UiWin *w) {
642 UiCursesWin *win = (UiCursesWin*)w;
643 if (!win->winstatus)
644 return;
645 UiCurses *uic = win->ui;
646 Vis *vis = uic->vis;
647 bool focused = uic->selwin == win;
648 const char *filename = vis_file_name(win->file);
649 const char *status = vis_mode_status(vis);
650 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
651 mvwhline(win->winstatus, 0, 0, ' ', win->width);
652 mvwprintw(win->winstatus, 0, 0, "%s %s %s %s",
653 focused && status ? status : "",
654 filename ? filename : "[No Name]",
655 text_modified(vis_file_text(win->file)) ? "[+]" : "",
656 vis_macro_recording(vis) ? "recording": "");
658 if (!(win->options & UI_OPTION_LARGE_FILE)) {
659 CursorPos pos = view_cursor_getpos(win->view);
660 char buf[win->width + 1];
661 int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col);
662 if (len > 0) {
663 buf[len] = '\0';
664 mvwaddstr(win->winstatus, 0, win->width - len - 1, buf);
669 static void ui_window_draw(UiWin *w) {
670 UiCursesWin *win = (UiCursesWin*)w;
671 if (!ui_window_draw_sidebar(win))
672 return;
674 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
675 wmove(win->win, 0, 0);
676 int width = view_width_get(win->view);
677 CellStyle *prev_style = NULL;
678 size_t cursor_lineno = -1;
680 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
681 Cursor *cursor = view_cursors(win->view);
682 Filerange selection = view_cursors_selection_get(cursor);
683 if (!view_cursors_next(cursor) && !text_range_valid(&selection))
684 cursor_lineno = view_cursor_getpos(win->view).line;
687 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
688 short cursor_line_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
689 bool multiple_cursors = view_cursors_multiple(win->view);
690 attr_t attr = A_NORMAL;
692 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
693 bool cursor_line = l->lineno == cursor_lineno;
694 for (int x = 0; x < width; x++) {
695 enum UiStyles style_id = l->cells[x].style;
696 if (style_id == 0)
697 style_id = UI_STYLE_DEFAULT;
698 CellStyle *style = &win->styles[style_id];
700 if (l->cells[x].cursor && win->ui->selwin == win) {
701 if (multiple_cursors && l->cells[x].cursor_primary)
702 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR_PRIMARY]);
703 else
704 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
705 prev_style = NULL;
706 } else if (l->cells[x].selected) {
707 if (style->fg == selection_bg)
708 attr |= A_REVERSE;
709 else
710 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
711 prev_style = NULL;
712 } else if (cursor_line) {
713 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, cursor_line_bg));
714 prev_style = NULL;
715 } else if (style != prev_style) {
716 attr = style_to_attr(style);
717 prev_style = style;
719 wattrset(win->win, attr);
720 if (multiple_cursors && l->cells[x].cursor_primary && l->cells[x].data[0] == ' ')
721 waddstr(win->win, "â–ˆ");
722 else
723 waddstr(win->win, l->cells[x].data);
725 /* try to fixup display issues, in theory we should always output a full line */
726 int x, y;
727 getyx(win->win, y, x);
728 (void)y;
729 wattrset(win->win, A_NORMAL);
730 for (; 0 < x && x < width; x++)
731 waddstr(win->win, " ");
734 wclrtobot(win->win);
736 if (win->winstatus)
737 ui_window_draw_status(w);
740 static void ui_window_reload(UiWin *w, File *file) {
741 UiCursesWin *win = (UiCursesWin*)w;
742 win->file = file;
743 win->sidebar_width = 0;
744 view_reload(win->view, vis_file_text(file));
745 ui_window_draw(w);
748 static void ui_window_update(UiCursesWin *win) {
749 if (win->winstatus)
750 wnoutrefresh(win->winstatus);
751 if (win->winside)
752 wnoutrefresh(win->winside);
753 wnoutrefresh(win->win);
756 static void ui_arrange(Ui *ui, enum UiLayout layout) {
757 UiCurses *uic = (UiCurses*)ui;
758 uic->layout = layout;
759 int n = 0, m = !!uic->info[0], x = 0, y = 0;
760 for (UiCursesWin *win = uic->windows; win; win = win->next) {
761 if (win->options & UI_OPTION_ONELINE)
762 m++;
763 else
764 n++;
766 int max_height = uic->height - m;
767 int width = (uic->width / MAX(1, n)) - 1;
768 int height = max_height / MAX(1, n);
769 for (UiCursesWin *win = uic->windows; win; win = win->next) {
770 if (win->options & UI_OPTION_ONELINE)
771 continue;
772 n--;
773 if (layout == UI_LAYOUT_HORIZONTAL) {
774 int h = n ? height : max_height - y;
775 ui_window_resize(win, uic->width, h);
776 ui_window_move(win, x, y);
777 y += h;
778 } else {
779 int w = n ? width : uic->width - x;
780 ui_window_resize(win, w, max_height);
781 ui_window_move(win, x, y);
782 x += w;
783 if (n)
784 mvvline(0, x++, ACS_VLINE, max_height);
788 if (layout == UI_LAYOUT_VERTICAL)
789 y = max_height;
791 for (UiCursesWin *win = uic->windows; win; win = win->next) {
792 if (!(win->options & UI_OPTION_ONELINE))
793 continue;
794 ui_window_resize(win, uic->width, 1);
795 ui_window_move(win, 0, y++);
799 static void ui_draw(Ui *ui) {
800 UiCurses *uic = (UiCurses*)ui;
801 erase();
802 ui_arrange(ui, uic->layout);
804 for (UiCursesWin *win = uic->windows; win; win = win->next)
805 ui_window_draw((UiWin*)win);
807 if (uic->info[0]) {
808 attrset(A_BOLD);
809 mvaddstr(uic->height-1, 0, uic->info);
812 wnoutrefresh(stdscr);
815 static void ui_redraw(Ui *ui) {
816 clear();
817 ui_draw(ui);
820 static void ui_resize_to(Ui *ui, int width, int height) {
821 UiCurses *uic = (UiCurses*)ui;
822 uic->width = width;
823 uic->height = height;
824 ui_draw(ui);
827 static void ui_resize(Ui *ui) {
828 struct winsize ws;
829 int width, height;
831 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
832 getmaxyx(stdscr, height, width);
833 } else {
834 width = ws.ws_col;
835 height = ws.ws_row;
838 resizeterm(height, width);
839 wresize(stdscr, height, width);
840 ui_resize_to(ui, width, height);
843 static void ui_update(Ui *ui) {
844 UiCurses *uic = (UiCurses*)ui;
845 if (need_resize) {
846 ui_resize(ui);
847 need_resize = false;
849 for (UiCursesWin *win = uic->windows; win; win = win->next) {
850 if (win != uic->selwin)
851 ui_window_update(win);
854 if (uic->selwin)
855 ui_window_update(uic->selwin);
856 doupdate();
859 static void ui_window_free(UiWin *w) {
860 UiCursesWin *win = (UiCursesWin*)w;
861 if (!win)
862 return;
863 UiCurses *uic = win->ui;
864 if (win->prev)
865 win->prev->next = win->next;
866 if (win->next)
867 win->next->prev = win->prev;
868 if (uic->windows == win)
869 uic->windows = win->next;
870 if (uic->selwin == win)
871 uic->selwin = NULL;
872 win->next = win->prev = NULL;
873 if (win->winstatus)
874 delwin(win->winstatus);
875 if (win->winside)
876 delwin(win->winside);
877 if (win->win)
878 delwin(win->win);
879 free(win);
882 static void ui_window_focus(UiWin *w) {
883 UiCursesWin *win = (UiCursesWin*)w;
884 UiCursesWin *oldsel = win->ui->selwin;
885 win->ui->selwin = win;
886 if (oldsel)
887 ui_window_draw((UiWin*)oldsel);
888 ui_window_draw(w);
891 static void ui_window_options_set(UiWin *w, enum UiOption options) {
892 UiCursesWin *win = (UiCursesWin*)w;
893 win->options = options;
894 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
895 if (!win->winside)
896 win->winside = newwin(1, 1, 1, 1);
897 } else {
898 if (win->winside) {
899 delwin(win->winside);
900 win->winside = NULL;
901 win->sidebar_width = 0;
904 if (options & UI_OPTION_STATUSBAR) {
905 if (!win->winstatus)
906 win->winstatus = newwin(1, 0, 0, 0);
907 } else {
908 if (win->winstatus)
909 delwin(win->winstatus);
910 win->winstatus = NULL;
913 if (options & UI_OPTION_ONELINE) {
914 /* move the new window to the end of the list */
915 UiCurses *uic = win->ui;
916 UiCursesWin *last = uic->windows;
917 while (last->next)
918 last = last->next;
919 if (last != win) {
920 if (win->prev)
921 win->prev->next = win->next;
922 if (win->next)
923 win->next->prev = win->prev;
924 if (uic->windows == win)
925 uic->windows = win->next;
926 last->next = win;
927 win->prev = last;
928 win->next = NULL;
932 ui_draw((Ui*)win->ui);
935 static enum UiOption ui_window_options_get(UiWin *w) {
936 UiCursesWin *win = (UiCursesWin*)w;
937 return win->options;
940 static UiWin *ui_window_new(Ui *ui, View *view, File *file, enum UiOption options) {
941 UiCurses *uic = (UiCurses*)ui;
942 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
943 if (!win)
944 return NULL;
946 win->uiwin = (UiWin) {
947 .draw = ui_window_draw,
948 .draw_status = ui_window_draw_status,
949 .options_set = ui_window_options_set,
950 .options_get = ui_window_options_get,
951 .reload = ui_window_reload,
952 .syntax_style = ui_window_syntax_style,
955 if (!(win->win = newwin(0, 0, 0, 0))) {
956 ui_window_free((UiWin*)win);
957 return NULL;
961 for (int i = 0; i < UI_STYLE_MAX; i++) {
962 win->styles[i] = (CellStyle) {
963 .fg = -1, .bg = -1, .attr = A_NORMAL,
967 win->styles[UI_STYLE_CURSOR].attr |= A_REVERSE;
968 win->styles[UI_STYLE_CURSOR_PRIMARY].attr |= A_REVERSE|A_BLINK;
969 win->styles[UI_STYLE_SELECTION].attr |= A_REVERSE;
970 win->styles[UI_STYLE_COLOR_COLUMN].attr |= A_REVERSE;
972 win->ui = uic;
973 win->view = view;
974 win->file = file;
975 view_ui(view, &win->uiwin);
977 if (uic->windows)
978 uic->windows->prev = win;
979 win->next = uic->windows;
980 uic->windows = win;
982 ui_window_options_set((UiWin*)win, options);
984 return &win->uiwin;
987 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
988 UiCurses *uic = (UiCurses*)ui;
989 endwin();
990 if (uic->termkey)
991 termkey_stop(uic->termkey);
992 vfprintf(stderr, msg, ap);
993 exit(EXIT_FAILURE);
996 static void ui_info(Ui *ui, const char *msg, va_list ap) {
997 UiCurses *uic = (UiCurses*)ui;
998 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
999 ui_draw(ui);
1002 static void ui_info_hide(Ui *ui) {
1003 UiCurses *uic = (UiCurses*)ui;
1004 if (uic->info[0]) {
1005 uic->info[0] = '\0';
1006 ui_draw(ui);
1010 static bool ui_init(Ui *ui, Vis *vis) {
1011 UiCurses *uic = (UiCurses*)ui;
1012 uic->vis = vis;
1013 return true;
1016 static bool ui_start(Ui *ui) {
1017 Vis *vis = ((UiCurses*)ui)->vis;
1018 const char *theme = getenv("VIS_THEME");
1019 if (theme && theme[0]) {
1020 if (!vis_theme_load(vis, theme))
1021 vis_info_show(vis, "Warning: failed to load theme `%s'", theme);
1022 } else {
1023 theme = COLORS <= 16 ? "default-16" : "default-256";
1024 if (!vis_theme_load(vis, theme))
1025 vis_info_show(vis, "Warning: failed to load theme `%s' set $VIS_PATH", theme);
1027 return true;
1030 static TermKey *ui_termkey_get(Ui *ui) {
1031 UiCurses *uic = (UiCurses*)ui;
1032 return uic->termkey;
1035 static void ui_suspend(Ui *ui) {
1036 endwin();
1037 raise(SIGSTOP);
1040 static bool ui_haskey(Ui *ui) {
1041 nodelay(stdscr, TRUE);
1042 int c = getch();
1043 if (c != ERR)
1044 ungetch(c);
1045 nodelay(stdscr, FALSE);
1046 return c != ERR;
1049 static const char *ui_getkey(Ui *ui) {
1050 UiCurses *uic = (UiCurses*)ui;
1051 TermKeyKey key;
1052 TermKeyResult ret = termkey_getkey(uic->termkey, &key);
1054 if (ret == TERMKEY_RES_AGAIN) {
1055 struct pollfd fd;
1056 fd.fd = STDIN_FILENO;
1057 fd.events = POLLIN;
1058 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
1059 ret = termkey_getkey_force(uic->termkey, &key);
1062 if (ret != TERMKEY_RES_KEY)
1063 return NULL;
1064 termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM);
1065 return uic->key;
1068 static void ui_terminal_save(Ui *ui) {
1069 UiCurses *uic = (UiCurses*)ui;
1070 curs_set(1);
1071 reset_shell_mode();
1072 termkey_stop(uic->termkey);
1075 static void ui_terminal_restore(Ui *ui) {
1076 UiCurses *uic = (UiCurses*)ui;
1077 termkey_start(uic->termkey);
1078 reset_prog_mode();
1079 wclear(stdscr);
1080 curs_set(0);
1083 Ui *ui_curses_new(void) {
1085 UiCurses *uic = calloc(1, sizeof(UiCurses));
1086 Ui *ui = (Ui*)uic;
1087 if (!uic)
1088 return NULL;
1089 if (!(uic->termkey = termkey_new(STDIN_FILENO, TERMKEY_FLAG_UTF8)))
1090 goto err;
1091 termkey_set_canonflags(uic->termkey, TERMKEY_CANON_DELBS);
1092 setlocale(LC_CTYPE, "");
1093 if (!getenv("ESCDELAY"))
1094 set_escdelay(50);
1095 char *term = getenv("TERM");
1096 if (!term)
1097 term = "xterm";
1098 if (!newterm(term, stderr, stdin)) {
1099 snprintf(uic->info, sizeof(uic->info), "Warning: unknown term `%s'", term);
1100 if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin))
1101 goto err;
1103 start_color();
1104 use_default_colors();
1105 raw();
1106 noecho();
1107 nonl();
1108 keypad(stdscr, TRUE);
1109 meta(stdscr, TRUE);
1110 curs_set(0);
1111 /* needed because we use getch() which implicitly calls refresh() which
1112 would clear the screen (overwrite it with an empty / unused stdscr */
1113 refresh();
1115 *ui = (Ui) {
1116 .init = ui_init,
1117 .start = ui_start,
1118 .free = ui_curses_free,
1119 .termkey_get = ui_termkey_get,
1120 .suspend = ui_suspend,
1121 .resize = ui_resize,
1122 .update = ui_update,
1123 .window_new = ui_window_new,
1124 .window_free = ui_window_free,
1125 .window_focus = ui_window_focus,
1126 .draw = ui_draw,
1127 .redraw = ui_redraw,
1128 .arrange = ui_arrange,
1129 .die = ui_die,
1130 .info = ui_info,
1131 .info_hide = ui_info_hide,
1132 .haskey = ui_haskey,
1133 .getkey = ui_getkey,
1134 .terminal_save = ui_terminal_save,
1135 .terminal_restore = ui_terminal_restore,
1138 struct sigaction sa;
1139 sa.sa_flags = 0;
1140 sigemptyset(&sa.sa_mask);
1141 sa.sa_handler = sigwinch_handler;
1142 sigaction(SIGWINCH, &sa, NULL);
1143 sigaction(SIGCONT, &sa, NULL);
1145 ui_resize(ui);
1147 return ui;
1148 err:
1149 ui_curses_free(ui);
1150 return NULL;
1153 void ui_curses_free(Ui *ui) {
1154 UiCurses *uic = (UiCurses*)ui;
1155 if (!uic)
1156 return;
1157 while (uic->windows)
1158 ui_window_free((UiWin*)uic->windows);
1159 endwin();
1160 if (uic->termkey)
1161 termkey_destroy(uic->termkey);
1162 free(uic);