vis: s/ops/vis_operators/g
[vis.git] / ui-curses.c
blob186de5842bb16cd8176880b3d8ba005a661f3619
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);
455 if (strcasecmp(s, "black") == 0)
456 return 0;
457 if (strcasecmp(s, "red") == 0)
458 return 1;
459 if (strcasecmp(s, "green") == 0)
460 return 2;
461 if (strcasecmp(s, "yellow") == 0)
462 return 3;
463 if (strcasecmp(s, "blue") == 0)
464 return 4;
465 if (strcasecmp(s, "magenta") == 0)
466 return 5;
467 if (strcasecmp(s, "cyan") == 0)
468 return 6;
469 if (strcasecmp(s, "white") == 0)
470 return 7;
471 return -1;
474 static inline unsigned int color_pair_hash(short fg, short bg) {
475 if (fg == -1)
476 fg = COLORS;
477 if (bg == -1)
478 bg = COLORS + 1;
479 return fg * (COLORS + 2) + bg;
482 static short color_pair_get(short fg, short bg) {
483 static bool has_default_colors;
484 static short *color2palette, default_fg, default_bg;
485 static short color_pairs_max, color_pair_current;
487 if (!color2palette) {
488 pair_content(0, &default_fg, &default_bg);
489 if (default_fg == -1)
490 default_fg = COLOR_WHITE;
491 if (default_bg == -1)
492 default_bg = COLOR_BLACK;
493 has_default_colors = (use_default_colors() == OK);
494 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
495 if (COLORS)
496 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
499 if (fg >= COLORS)
500 fg = default_fg;
501 if (bg >= COLORS)
502 bg = default_bg;
504 if (!has_default_colors) {
505 if (fg == -1)
506 fg = default_fg;
507 if (bg == -1)
508 bg = default_bg;
511 if (!color2palette || (fg == -1 && bg == -1))
512 return 0;
514 unsigned int index = color_pair_hash(fg, bg);
515 if (color2palette[index] == 0) {
516 short oldfg, oldbg;
517 if (++color_pair_current >= color_pairs_max)
518 color_pair_current = 1;
519 pair_content(color_pair_current, &oldfg, &oldbg);
520 unsigned int old_index = color_pair_hash(oldfg, oldbg);
521 if (init_pair(color_pair_current, fg, bg) == OK) {
522 color2palette[old_index] = 0;
523 color2palette[index] = color_pair_current;
527 return color2palette[index];
530 static inline attr_t style_to_attr(CellStyle *style) {
531 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
534 static bool ui_window_syntax_style(UiWin *w, int id, const char *style) {
535 UiCursesWin *win = (UiCursesWin*)w;
536 if (id >= UI_STYLE_MAX)
537 return false;
538 if (!style)
539 return true;
540 CellStyle cell_style = win->styles[UI_STYLE_DEFAULT];
541 char *style_copy = strdup(style), *option = style_copy, *next, *p;
542 while (option) {
543 if ((next = strchr(option, ',')))
544 *next++ = '\0';
545 if ((p = strchr(option, ':')))
546 *p++ = '\0';
547 if (!strcasecmp(option, "reverse")) {
548 cell_style.attr |= A_REVERSE;
549 } else if (!strcasecmp(option, "bold")) {
550 cell_style.attr |= A_BOLD;
551 } else if (!strcasecmp(option, "notbold")) {
552 cell_style.attr &= ~A_BOLD;
553 #ifdef A_ITALIC
554 } else if (!strcasecmp(option, "italics")) {
555 cell_style.attr |= A_ITALIC;
556 } else if (!strcasecmp(option, "notitalics")) {
557 cell_style.attr &= ~A_ITALIC;
558 #endif
559 } else if (!strcasecmp(option, "underlined")) {
560 cell_style.attr |= A_UNDERLINE;
561 } else if (!strcasecmp(option, "notunderlined")) {
562 cell_style.attr &= ~A_UNDERLINE;
563 } else if (!strcasecmp(option, "fore")) {
564 cell_style.fg = color_fromstring(p);
565 } else if (!strcasecmp(option, "back")) {
566 cell_style.bg = color_fromstring(p);
568 option = next;
570 win->styles[id] = cell_style;
571 free(style_copy);
572 return true;
575 static void ui_window_resize(UiCursesWin *win, int width, int height) {
576 win->width = width;
577 win->height = height;
578 if (win->winstatus)
579 wresize(win->winstatus, 1, width);
580 wresize(win->win, win->winstatus ? height - 1 : height, width - win->sidebar_width);
581 if (win->winside)
582 wresize(win->winside, height-1, win->sidebar_width);
583 view_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height);
584 view_update(win->view);
587 static void ui_window_move(UiCursesWin *win, int x, int y) {
588 win->x = x;
589 win->y = y;
590 mvwin(win->win, y, x + win->sidebar_width);
591 if (win->winside)
592 mvwin(win->winside, y, x);
593 if (win->winstatus)
594 mvwin(win->winstatus, y + win->height - 1, x);
597 static bool ui_window_draw_sidebar(UiCursesWin *win) {
598 if (!win->winside)
599 return true;
600 const Line *line = view_lines_get(win->view);
601 int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1;
602 if (win->sidebar_width != sidebar_width) {
603 win->sidebar_width = sidebar_width;
604 ui_window_resize(win, win->width, win->height);
605 ui_window_move(win, win->x, win->y);
606 return false;
607 } else {
608 int i = 0;
609 size_t prev_lineno = 0;
610 size_t cursor_lineno = view_cursor_getpos(win->view).line;
611 werase(win->winside);
612 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
613 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
614 for (const Line *l = line; l; l = l->next, i++) {
615 if (l->lineno && l->lineno != prev_lineno) {
616 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
617 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
618 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
619 size_t rel = l->lineno > cursor_lineno ?
620 l->lineno - cursor_lineno :
621 cursor_lineno - l->lineno;
622 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
625 prev_lineno = l->lineno;
627 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
628 return true;
632 static void ui_window_draw_status(UiWin *w) {
633 UiCursesWin *win = (UiCursesWin*)w;
634 if (!win->winstatus)
635 return;
636 UiCurses *uic = win->ui;
637 Vis *vis = uic->vis;
638 bool focused = uic->selwin == win;
639 const char *filename = vis_file_name(win->file);
640 const char *status = vis_mode_status(vis);
641 CursorPos pos = view_cursor_getpos(win->view);
642 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
643 mvwhline(win->winstatus, 0, 0, ' ', win->width);
644 mvwprintw(win->winstatus, 0, 0, "%s %s %s %s",
645 focused && status ? status : "",
646 filename ? filename : "[No Name]",
647 text_modified(vis_file_text(win->file)) ? "[+]" : "",
648 vis_macro_recording(vis) ? "recording": "");
649 char buf[win->width + 1];
650 int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col);
651 if (len > 0) {
652 buf[len] = '\0';
653 mvwaddstr(win->winstatus, 0, win->width - len - 1, buf);
657 static void ui_window_draw(UiWin *w) {
658 UiCursesWin *win = (UiCursesWin*)w;
659 if (!ui_window_draw_sidebar(win))
660 return;
661 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
662 wmove(win->win, 0, 0);
663 int width = view_width_get(win->view);
664 CellStyle *prev_style = NULL;
665 size_t cursor_lineno = -1;
666 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
667 Cursor *cursor = view_cursors(win->view);
668 Filerange selection = view_cursors_selection_get(cursor);
669 if (!view_cursors_next(cursor) && !text_range_valid(&selection))
670 cursor_lineno = view_cursor_getpos(win->view).line;
672 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
673 short cursor_line_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
674 attr_t attr = A_NORMAL;
675 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
676 bool cursor_line = l->lineno == cursor_lineno;
677 for (int x = 0; x < width; x++) {
678 CellStyle *style = &win->styles[l->cells[x].attr];
679 if (l->cells[x].cursor && win->ui->selwin == win) {
680 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
681 prev_style = NULL;
682 } else if (l->cells[x].selected) {
683 if (style->fg == selection_bg)
684 attr |= A_REVERSE;
685 else
686 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
687 prev_style = NULL;
688 } else if (cursor_line) {
689 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, cursor_line_bg));
690 prev_style = NULL;
691 } else if (style != prev_style) {
692 attr = style_to_attr(style);
693 prev_style = style;
695 wattrset(win->win, attr);
696 waddstr(win->win, l->cells[x].data);
698 /* try to fixup display issues, in theory we should always output a full line */
699 int x, y;
700 getyx(win->win, y, x);
701 (void)y;
702 wattrset(win->win, A_NORMAL);
703 for (; 0 < x && x < width; x++)
704 waddstr(win->win, " ");
706 wclrtobot(win->win);
708 if (win->winstatus)
709 ui_window_draw_status(w);
712 static void ui_window_reload(UiWin *w, File *file) {
713 UiCursesWin *win = (UiCursesWin*)w;
714 win->file = file;
715 win->sidebar_width = 0;
716 view_reload(win->view, vis_file_text(file));
717 ui_window_draw(w);
720 static void ui_window_update(UiCursesWin *win) {
721 if (win->winstatus)
722 wnoutrefresh(win->winstatus);
723 if (win->winside)
724 wnoutrefresh(win->winside);
725 wnoutrefresh(win->win);
728 static void ui_arrange(Ui *ui, enum UiLayout layout) {
729 UiCurses *uic = (UiCurses*)ui;
730 uic->layout = layout;
731 int n = 0, m = !!uic->info[0], x = 0, y = 0;
732 for (UiCursesWin *win = uic->windows; win; win = win->next) {
733 if (win->options & UI_OPTION_ONELINE)
734 m++;
735 else
736 n++;
738 int max_height = uic->height - m;
739 int width = (uic->width / MAX(1, n)) - 1;
740 int height = max_height / MAX(1, n);
741 for (UiCursesWin *win = uic->windows; win; win = win->next) {
742 if (win->options & UI_OPTION_ONELINE)
743 continue;
744 n--;
745 if (layout == UI_LAYOUT_HORIZONTAL) {
746 int h = n ? height : max_height - y;
747 ui_window_resize(win, uic->width, h);
748 ui_window_move(win, x, y);
749 y += h;
750 } else {
751 int w = n ? width : uic->width - x;
752 ui_window_resize(win, w, max_height);
753 ui_window_move(win, x, y);
754 x += w;
755 if (n)
756 mvvline(0, x++, ACS_VLINE, max_height);
760 if (layout == UI_LAYOUT_VERTICAL)
761 y = max_height;
763 for (UiCursesWin *win = uic->windows; win; win = win->next) {
764 if (!(win->options & UI_OPTION_ONELINE))
765 continue;
766 ui_window_resize(win, uic->width, 1);
767 ui_window_move(win, 0, y++);
771 static void ui_draw(Ui *ui) {
772 UiCurses *uic = (UiCurses*)ui;
773 erase();
774 ui_arrange(ui, uic->layout);
776 for (UiCursesWin *win = uic->windows; win; win = win->next)
777 ui_window_draw((UiWin*)win);
779 if (uic->info[0]) {
780 attrset(A_BOLD);
781 mvaddstr(uic->height-1, 0, uic->info);
784 wnoutrefresh(stdscr);
787 static void ui_redraw(Ui *ui) {
788 clear();
789 ui_draw(ui);
792 static void ui_resize_to(Ui *ui, int width, int height) {
793 UiCurses *uic = (UiCurses*)ui;
794 uic->width = width;
795 uic->height = height;
796 ui_draw(ui);
799 static void ui_resize(Ui *ui) {
800 struct winsize ws;
801 int width, height;
803 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
804 getmaxyx(stdscr, height, width);
805 } else {
806 width = ws.ws_col;
807 height = ws.ws_row;
810 resizeterm(height, width);
811 wresize(stdscr, height, width);
812 ui_resize_to(ui, width, height);
815 static void ui_update(Ui *ui) {
816 UiCurses *uic = (UiCurses*)ui;
817 if (need_resize) {
818 ui_resize(ui);
819 need_resize = false;
821 for (UiCursesWin *win = uic->windows; win; win = win->next) {
822 if (win != uic->selwin)
823 ui_window_update(win);
826 if (uic->selwin)
827 ui_window_update(uic->selwin);
828 doupdate();
831 static void ui_window_free(UiWin *w) {
832 UiCursesWin *win = (UiCursesWin*)w;
833 if (!win)
834 return;
835 UiCurses *uic = win->ui;
836 if (win->prev)
837 win->prev->next = win->next;
838 if (win->next)
839 win->next->prev = win->prev;
840 if (uic->windows == win)
841 uic->windows = win->next;
842 if (uic->selwin == win)
843 uic->selwin = NULL;
844 win->next = win->prev = NULL;
845 if (win->winstatus)
846 delwin(win->winstatus);
847 if (win->winside)
848 delwin(win->winside);
849 if (win->win)
850 delwin(win->win);
851 free(win);
854 static void ui_window_focus(UiWin *w) {
855 UiCursesWin *win = (UiCursesWin*)w;
856 UiCursesWin *oldsel = win->ui->selwin;
857 win->ui->selwin = win;
858 if (oldsel)
859 ui_window_draw((UiWin*)oldsel);
860 ui_window_draw(w);
863 static void ui_window_options_set(UiWin *w, enum UiOption options) {
864 UiCursesWin *win = (UiCursesWin*)w;
865 win->options = options;
866 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
867 if (!win->winside)
868 win->winside = newwin(1, 1, 1, 1);
869 } else {
870 if (win->winside) {
871 delwin(win->winside);
872 win->winside = NULL;
873 win->sidebar_width = 0;
876 if (options & UI_OPTION_STATUSBAR) {
877 if (!win->winstatus)
878 win->winstatus = newwin(1, 0, 0, 0);
879 } else {
880 if (win->winstatus)
881 delwin(win->winstatus);
882 win->winstatus = NULL;
885 if (options & UI_OPTION_ONELINE) {
886 /* move the new window to the end of the list */
887 UiCurses *uic = win->ui;
888 UiCursesWin *last = uic->windows;
889 while (last->next)
890 last = last->next;
891 if (last != win) {
892 if (win->prev)
893 win->prev->next = win->next;
894 if (win->next)
895 win->next->prev = win->prev;
896 if (uic->windows == win)
897 uic->windows = win->next;
898 last->next = win;
899 win->prev = last;
900 win->next = NULL;
904 ui_draw((Ui*)win->ui);
907 static enum UiOption ui_window_options_get(UiWin *w) {
908 UiCursesWin *win = (UiCursesWin*)w;
909 return win->options;
912 static UiWin *ui_window_new(Ui *ui, View *view, File *file, enum UiOption options) {
913 UiCurses *uic = (UiCurses*)ui;
914 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
915 if (!win)
916 return NULL;
918 win->uiwin = (UiWin) {
919 .draw = ui_window_draw,
920 .draw_status = ui_window_draw_status,
921 .options_set = ui_window_options_set,
922 .options_get = ui_window_options_get,
923 .reload = ui_window_reload,
924 .syntax_style = ui_window_syntax_style,
927 if (!(win->win = newwin(0, 0, 0, 0))) {
928 ui_window_free((UiWin*)win);
929 return NULL;
932 CellStyle style = (CellStyle) {
933 .fg = -1, .bg = -1, .attr = A_NORMAL,
936 for (int i = 0; i < UI_STYLE_MAX; i++) {
937 win->styles[i] = style;
940 style.attr |= A_REVERSE;
941 win->styles[UI_STYLE_CURSOR] = style;
942 win->styles[UI_STYLE_SELECTION] = style;
943 win->styles[UI_STYLE_COLOR_COLUMN] = style;
945 win->ui = uic;
946 win->view = view;
947 win->file = file;
948 view_ui(view, &win->uiwin);
950 if (uic->windows)
951 uic->windows->prev = win;
952 win->next = uic->windows;
953 uic->windows = win;
955 ui_window_options_set((UiWin*)win, options);
957 return &win->uiwin;
960 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
961 UiCurses *uic = (UiCurses*)ui;
962 endwin();
963 if (uic->termkey)
964 termkey_stop(uic->termkey);
965 vfprintf(stderr, msg, ap);
966 exit(EXIT_FAILURE);
969 static void ui_info(Ui *ui, const char *msg, va_list ap) {
970 UiCurses *uic = (UiCurses*)ui;
971 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
972 ui_draw(ui);
975 static void ui_info_hide(Ui *ui) {
976 UiCurses *uic = (UiCurses*)ui;
977 if (uic->info[0]) {
978 uic->info[0] = '\0';
979 ui_draw(ui);
983 static bool ui_init(Ui *ui, Vis *vis) {
984 UiCurses *uic = (UiCurses*)ui;
985 uic->vis = vis;
986 const char *theme = getenv("VIS_THEME");
987 if (theme && theme[0]) {
988 if (!vis_theme_load(vis, theme))
989 vis_info_show(vis, "Warning: failed to load theme `%s'", theme);
990 } else {
991 theme = COLORS <= 16 ? "default-16" : "default-256";
992 if (!vis_theme_load(vis, theme))
993 vis_info_show(vis, "Warning: failed to load theme `%s' set $VIS_PATH", theme);
995 return true;
998 static TermKey *ui_termkey_get(Ui *ui) {
999 UiCurses *uic = (UiCurses*)ui;
1000 return uic->termkey;
1003 static void ui_suspend(Ui *ui) {
1004 endwin();
1005 raise(SIGSTOP);
1008 static bool ui_haskey(Ui *ui) {
1009 nodelay(stdscr, TRUE);
1010 int c = getch();
1011 if (c != ERR)
1012 ungetch(c);
1013 nodelay(stdscr, FALSE);
1014 return c != ERR;
1017 static const char *ui_getkey(Ui *ui) {
1018 UiCurses *uic = (UiCurses*)ui;
1019 TermKeyKey key;
1020 TermKeyResult ret = termkey_getkey(uic->termkey, &key);
1022 if (ret == TERMKEY_RES_AGAIN) {
1023 struct pollfd fd;
1024 fd.fd = STDIN_FILENO;
1025 fd.events = POLLIN;
1026 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
1027 ret = termkey_getkey_force(uic->termkey, &key);
1030 if (ret != TERMKEY_RES_KEY)
1031 return NULL;
1032 termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM);
1033 return uic->key;
1036 static void ui_terminal_save(Ui *ui) {
1037 UiCurses *uic = (UiCurses*)ui;
1038 curs_set(1);
1039 reset_shell_mode();
1040 termkey_stop(uic->termkey);
1043 static void ui_terminal_restore(Ui *ui) {
1044 UiCurses *uic = (UiCurses*)ui;
1045 termkey_start(uic->termkey);
1046 reset_prog_mode();
1047 wclear(stdscr);
1048 curs_set(0);
1051 Ui *ui_curses_new(void) {
1053 UiCurses *uic = calloc(1, sizeof(UiCurses));
1054 Ui *ui = (Ui*)uic;
1055 if (!uic)
1056 return NULL;
1057 if (!(uic->termkey = termkey_new(STDIN_FILENO, TERMKEY_FLAG_UTF8)))
1058 goto err;
1059 termkey_set_canonflags(uic->termkey, TERMKEY_CANON_DELBS);
1060 setlocale(LC_CTYPE, "");
1061 if (!getenv("ESCDELAY"))
1062 set_escdelay(50);
1063 char *term = getenv("TERM");
1064 if (!term)
1065 term = "xterm";
1066 if (!newterm(term, stderr, stdin)) {
1067 snprintf(uic->info, sizeof(uic->info), "Warning: unknown term `%s'", term);
1068 if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin))
1069 goto err;
1071 start_color();
1072 use_default_colors();
1073 raw();
1074 noecho();
1075 nonl();
1076 keypad(stdscr, TRUE);
1077 meta(stdscr, TRUE);
1078 curs_set(0);
1079 /* needed because we use getch() which implicitly calls refresh() which
1080 would clear the screen (overwrite it with an empty / unused stdscr */
1081 refresh();
1083 *ui = (Ui) {
1084 .init = ui_init,
1085 .free = ui_curses_free,
1086 .termkey_get = ui_termkey_get,
1087 .suspend = ui_suspend,
1088 .resize = ui_resize,
1089 .update = ui_update,
1090 .window_new = ui_window_new,
1091 .window_free = ui_window_free,
1092 .window_focus = ui_window_focus,
1093 .draw = ui_draw,
1094 .redraw = ui_redraw,
1095 .arrange = ui_arrange,
1096 .die = ui_die,
1097 .info = ui_info,
1098 .info_hide = ui_info_hide,
1099 .haskey = ui_haskey,
1100 .getkey = ui_getkey,
1101 .terminal_save = ui_terminal_save,
1102 .terminal_restore = ui_terminal_restore,
1105 struct sigaction sa;
1106 sa.sa_flags = 0;
1107 sigemptyset(&sa.sa_mask);
1108 sa.sa_handler = sigwinch_handler;
1109 sigaction(SIGWINCH, &sa, NULL);
1110 sigaction(SIGCONT, &sa, NULL);
1112 ui_resize(ui);
1114 return ui;
1115 err:
1116 ui_curses_free(ui);
1117 return NULL;
1120 void ui_curses_free(Ui *ui) {
1121 UiCurses *uic = (UiCurses*)ui;
1122 if (!uic)
1123 return;
1124 while (uic->windows)
1125 ui_window_free((UiWin*)uic->windows);
1126 endwin();
1127 if (uic->termkey)
1128 termkey_destroy(uic->termkey);
1129 free(uic);