lexer: sort list of file extensions
[vis.git] / ui-curses.c
blob1b3af1e9a13a0772e3633825e641fe51eb916ec2
1 /* parts of the color handling code originates from tmux/colour.c and is
2 * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
3 */
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <strings.h>
8 #include <limits.h>
9 #include <ctype.h>
10 #include <signal.h>
11 #include <locale.h>
12 #include <poll.h>
13 #include <sys/ioctl.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <termios.h>
18 #include <termkey.h>
20 #include "ui-curses.h"
21 #include "vis.h"
22 #include "vis-core.h"
23 #include "text.h"
24 #include "util.h"
25 #include "text-util.h"
27 #ifdef NCURSES_VERSION
28 # ifndef NCURSES_EXT_COLORS
29 # define NCURSES_EXT_COLORS 0
30 # endif
31 # if !NCURSES_EXT_COLORS
32 # define MAX_COLOR_PAIRS 256
33 # endif
34 #endif
35 #ifndef MAX_COLOR_PAIRS
36 # define MAX_COLOR_PAIRS COLOR_PAIRS
37 #endif
39 #ifdef PDCURSES
40 int ESCDELAY;
41 #endif
42 #ifndef NCURSES_REENTRANT
43 # define set_escdelay(d) (ESCDELAY = (d))
44 #endif
46 #define CONTROL(k) ((k)&0x1F)
48 #if 0
49 #define wresize(win, y, x) do { \
50 if (wresize(win, y, x) == ERR) { \
51 printf("ERROR resizing: %d x %d\n", x, y); \
52 } else { \
53 printf("OK resizing: %d x %d\n", x, y); \
54 } \
55 fflush(stdout); \
56 } while (0);
58 #define mvwin(win, y, x) do { \
59 if (mvwin(win, y, x) == ERR) { \
60 printf("ERROR moving: %d x %d\n", x, y); \
61 } else { \
62 printf("OK moving: %d x %d\n", x, y); \
63 } \
64 fflush(stdout); \
65 } while (0);
66 #endif
68 typedef struct {
69 attr_t attr;
70 short fg, bg;
71 } CellStyle;
73 typedef struct UiCursesWin UiCursesWin;
75 typedef struct {
76 Ui ui; /* generic ui interface, has to be the first struct member */
77 Vis *vis; /* editor instance to which this ui belongs */
78 UiCursesWin *windows; /* all windows managed by this ui */
79 UiCursesWin *selwin; /* the currently selected layout */
80 char info[255]; /* info message displayed at the bottom of the screen */
81 int width, height; /* terminal dimensions available for all windows */
82 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
83 TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */
84 struct termios tio; /* terminal state to restore before exiting */
85 char key[64]; /* string representation of last pressed key */
86 } UiCurses;
88 struct UiCursesWin {
89 UiWin uiwin; /* generic interface, has to be the first struct member */
90 UiCurses *ui; /* ui which manages this window */
91 File *file; /* file being displayed in this window */
92 View *view; /* current viewport */
93 WINDOW *win; /* curses window for the text area */
94 WINDOW *winstatus; /* curses window for the status bar */
95 WINDOW *winside; /* curses window for the side bar (line numbers) */
96 int width, height; /* window dimension including status bar */
97 int x, y; /* window position */
98 int sidebar_width; /* width of the sidebar showing line numbers etc. */
99 UiCursesWin *next, *prev; /* pointers to neighbouring windows */
100 enum UiOption options; /* display settings for this window */
101 CellStyle styles[UI_STYLE_MAX];
104 static volatile sig_atomic_t need_resize; /* TODO */
106 static void sigwinch_handler(int sig) {
107 need_resize = true;
110 typedef struct {
111 unsigned char i;
112 unsigned char r;
113 unsigned char g;
114 unsigned char b;
115 } Color;
117 static int color_compare(const void *lhs0, const void *rhs0) {
118 const Color *lhs = lhs0, *rhs = rhs0;
120 if (lhs->r < rhs->r)
121 return -1;
122 if (lhs->r > rhs->r)
123 return 1;
125 if (lhs->g < rhs->g)
126 return -1;
127 if (lhs->g > rhs->g)
128 return 1;
130 if (lhs->b < rhs->b)
131 return -1;
132 if (lhs->b > rhs->b)
133 return 1;
135 return 0;
138 /* Work out the nearest color from the 256 color set. */
139 static int color_find_rgb(unsigned char r, unsigned char g, unsigned char b)
141 static const Color color_from_256[] = {
142 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
143 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
144 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
145 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
146 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
147 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
148 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
149 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
150 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
151 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
152 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
153 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
154 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
155 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
156 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
157 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
158 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
159 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
160 { 36, 0x5f, 0x00, 0x00 }, { 37, 0x5f, 0x00, 0x5f },
161 { 38, 0x5f, 0x00, 0x87 }, { 39, 0x5f, 0x00, 0xaf },
162 { 40, 0x5f, 0x00, 0xd7 }, { 41, 0x5f, 0x00, 0xff },
163 { 42, 0x5f, 0x5f, 0x00 }, { 43, 0x5f, 0x5f, 0x5f },
164 { 44, 0x5f, 0x5f, 0x87 }, { 45, 0x5f, 0x5f, 0xaf },
165 { 46, 0x5f, 0x5f, 0xd7 }, { 47, 0x5f, 0x5f, 0xff },
166 { 48, 0x5f, 0x87, 0x00 }, { 49, 0x5f, 0x87, 0x5f },
167 { 50, 0x5f, 0x87, 0x87 }, { 51, 0x5f, 0x87, 0xaf },
168 { 52, 0x5f, 0x87, 0xd7 }, { 53, 0x5f, 0x87, 0xff },
169 { 54, 0x5f, 0xaf, 0x00 }, { 55, 0x5f, 0xaf, 0x5f },
170 { 56, 0x5f, 0xaf, 0x87 }, { 57, 0x5f, 0xaf, 0xaf },
171 { 58, 0x5f, 0xaf, 0xd7 }, { 59, 0x5f, 0xaf, 0xff },
172 { 60, 0x5f, 0xd7, 0x00 }, { 61, 0x5f, 0xd7, 0x5f },
173 { 62, 0x5f, 0xd7, 0x87 }, { 63, 0x5f, 0xd7, 0xaf },
174 { 64, 0x5f, 0xd7, 0xd7 }, { 65, 0x5f, 0xd7, 0xff },
175 { 66, 0x5f, 0xff, 0x00 }, { 67, 0x5f, 0xff, 0x5f },
176 { 68, 0x5f, 0xff, 0x87 }, { 69, 0x5f, 0xff, 0xaf },
177 { 70, 0x5f, 0xff, 0xd7 }, { 71, 0x5f, 0xff, 0xff },
178 { 72, 0x87, 0x00, 0x00 }, { 73, 0x87, 0x00, 0x5f },
179 { 74, 0x87, 0x00, 0x87 }, { 75, 0x87, 0x00, 0xaf },
180 { 76, 0x87, 0x00, 0xd7 }, { 77, 0x87, 0x00, 0xff },
181 { 78, 0x87, 0x5f, 0x00 }, { 79, 0x87, 0x5f, 0x5f },
182 { 80, 0x87, 0x5f, 0x87 }, { 81, 0x87, 0x5f, 0xaf },
183 { 82, 0x87, 0x5f, 0xd7 }, { 83, 0x87, 0x5f, 0xff },
184 { 84, 0x87, 0x87, 0x00 }, { 85, 0x87, 0x87, 0x5f },
185 { 86, 0x87, 0x87, 0x87 }, { 87, 0x87, 0x87, 0xaf },
186 { 88, 0x87, 0x87, 0xd7 }, { 89, 0x87, 0x87, 0xff },
187 { 90, 0x87, 0xaf, 0x00 }, { 91, 0x87, 0xaf, 0x5f },
188 { 92, 0x87, 0xaf, 0x87 }, { 93, 0x87, 0xaf, 0xaf },
189 { 94, 0x87, 0xaf, 0xd7 }, { 95, 0x87, 0xaf, 0xff },
190 { 96, 0x87, 0xd7, 0x00 }, { 97, 0x87, 0xd7, 0x5f },
191 { 98, 0x87, 0xd7, 0x87 }, { 99, 0x87, 0xd7, 0xaf },
192 { 100, 0x87, 0xd7, 0xd7 }, { 101, 0x87, 0xd7, 0xff },
193 { 102, 0x87, 0xff, 0x00 }, { 103, 0x87, 0xff, 0x5f },
194 { 104, 0x87, 0xff, 0x87 }, { 105, 0x87, 0xff, 0xaf },
195 { 106, 0x87, 0xff, 0xd7 }, { 107, 0x87, 0xff, 0xff },
196 { 108, 0xaf, 0x00, 0x00 }, { 109, 0xaf, 0x00, 0x5f },
197 { 110, 0xaf, 0x00, 0x87 }, { 111, 0xaf, 0x00, 0xaf },
198 { 112, 0xaf, 0x00, 0xd7 }, { 113, 0xaf, 0x00, 0xff },
199 { 114, 0xaf, 0x5f, 0x00 }, { 115, 0xaf, 0x5f, 0x5f },
200 { 116, 0xaf, 0x5f, 0x87 }, { 117, 0xaf, 0x5f, 0xaf },
201 { 118, 0xaf, 0x5f, 0xd7 }, { 119, 0xaf, 0x5f, 0xff },
202 { 120, 0xaf, 0x87, 0x00 }, { 121, 0xaf, 0x87, 0x5f },
203 { 122, 0xaf, 0x87, 0x87 }, { 123, 0xaf, 0x87, 0xaf },
204 { 124, 0xaf, 0x87, 0xd7 }, { 125, 0xaf, 0x87, 0xff },
205 { 126, 0xaf, 0xaf, 0x00 }, { 127, 0xaf, 0xaf, 0x5f },
206 { 128, 0xaf, 0xaf, 0x87 }, { 129, 0xaf, 0xaf, 0xaf },
207 { 130, 0xaf, 0xaf, 0xd7 }, { 131, 0xaf, 0xaf, 0xff },
208 { 132, 0xaf, 0xd7, 0x00 }, { 133, 0xaf, 0xd7, 0x5f },
209 { 134, 0xaf, 0xd7, 0x87 }, { 135, 0xaf, 0xd7, 0xaf },
210 { 136, 0xaf, 0xd7, 0xd7 }, { 137, 0xaf, 0xd7, 0xff },
211 { 138, 0xaf, 0xff, 0x00 }, { 139, 0xaf, 0xff, 0x5f },
212 { 140, 0xaf, 0xff, 0x87 }, { 141, 0xaf, 0xff, 0xaf },
213 { 142, 0xaf, 0xff, 0xd7 }, { 143, 0xaf, 0xff, 0xff },
214 { 144, 0xd7, 0x00, 0x00 }, { 145, 0xd7, 0x00, 0x5f },
215 { 146, 0xd7, 0x00, 0x87 }, { 147, 0xd7, 0x00, 0xaf },
216 { 148, 0xd7, 0x00, 0xd7 }, { 149, 0xd7, 0x00, 0xff },
217 { 150, 0xd7, 0x5f, 0x00 }, { 151, 0xd7, 0x5f, 0x5f },
218 { 152, 0xd7, 0x5f, 0x87 }, { 153, 0xd7, 0x5f, 0xaf },
219 { 154, 0xd7, 0x5f, 0xd7 }, { 155, 0xd7, 0x5f, 0xff },
220 { 156, 0xd7, 0x87, 0x00 }, { 157, 0xd7, 0x87, 0x5f },
221 { 158, 0xd7, 0x87, 0x87 }, { 159, 0xd7, 0x87, 0xaf },
222 { 160, 0xd7, 0x87, 0xd7 }, { 161, 0xd7, 0x87, 0xff },
223 { 162, 0xd7, 0xaf, 0x00 }, { 163, 0xd7, 0xaf, 0x5f },
224 { 164, 0xd7, 0xaf, 0x87 }, { 165, 0xd7, 0xaf, 0xaf },
225 { 166, 0xd7, 0xaf, 0xd7 }, { 167, 0xd7, 0xaf, 0xff },
226 { 168, 0xd7, 0xd7, 0x00 }, { 169, 0xd7, 0xd7, 0x5f },
227 { 170, 0xd7, 0xd7, 0x87 }, { 171, 0xd7, 0xd7, 0xaf },
228 { 172, 0xd7, 0xd7, 0xd7 }, { 173, 0xd7, 0xd7, 0xff },
229 { 174, 0xd7, 0xff, 0x00 }, { 175, 0xd7, 0xff, 0x5f },
230 { 176, 0xd7, 0xff, 0x87 }, { 177, 0xd7, 0xff, 0xaf },
231 { 178, 0xd7, 0xff, 0xd7 }, { 179, 0xd7, 0xff, 0xff },
232 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
233 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
234 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
235 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
236 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
237 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
238 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
239 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
240 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
241 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
242 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
243 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
244 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
245 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
246 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
247 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
248 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
249 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
250 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
251 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
252 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
253 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
254 { 224, 0x58, 0x58, 0x58 }, { 225, 0x62, 0x62, 0x62 },
255 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
256 { 228, 0x80, 0x80, 0x80 }, { 229, 0x8a, 0x8a, 0x8a },
257 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
258 { 232, 0xa8, 0xa8, 0xa8 }, { 233, 0xb2, 0xb2, 0xb2 },
259 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
260 { 236, 0xd0, 0xd0, 0xd0 }, { 237, 0xda, 0xda, 0xda },
261 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
264 static const Color color_to_256[] = {
265 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
266 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
267 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
268 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
269 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
270 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
271 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
272 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
273 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
274 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
275 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
276 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
277 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
278 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
279 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
280 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
281 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
282 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
283 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
284 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
285 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
286 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
287 { 224, 0x58, 0x58, 0x58 }, { 36, 0x5f, 0x00, 0x00 },
288 { 37, 0x5f, 0x00, 0x5f }, { 38, 0x5f, 0x00, 0x87 },
289 { 39, 0x5f, 0x00, 0xaf }, { 40, 0x5f, 0x00, 0xd7 },
290 { 41, 0x5f, 0x00, 0xff }, { 42, 0x5f, 0x5f, 0x00 },
291 { 43, 0x5f, 0x5f, 0x5f }, { 44, 0x5f, 0x5f, 0x87 },
292 { 45, 0x5f, 0x5f, 0xaf }, { 46, 0x5f, 0x5f, 0xd7 },
293 { 47, 0x5f, 0x5f, 0xff }, { 48, 0x5f, 0x87, 0x00 },
294 { 49, 0x5f, 0x87, 0x5f }, { 50, 0x5f, 0x87, 0x87 },
295 { 51, 0x5f, 0x87, 0xaf }, { 52, 0x5f, 0x87, 0xd7 },
296 { 53, 0x5f, 0x87, 0xff }, { 54, 0x5f, 0xaf, 0x00 },
297 { 55, 0x5f, 0xaf, 0x5f }, { 56, 0x5f, 0xaf, 0x87 },
298 { 57, 0x5f, 0xaf, 0xaf }, { 58, 0x5f, 0xaf, 0xd7 },
299 { 59, 0x5f, 0xaf, 0xff }, { 60, 0x5f, 0xd7, 0x00 },
300 { 61, 0x5f, 0xd7, 0x5f }, { 62, 0x5f, 0xd7, 0x87 },
301 { 63, 0x5f, 0xd7, 0xaf }, { 64, 0x5f, 0xd7, 0xd7 },
302 { 65, 0x5f, 0xd7, 0xff }, { 66, 0x5f, 0xff, 0x00 },
303 { 67, 0x5f, 0xff, 0x5f }, { 68, 0x5f, 0xff, 0x87 },
304 { 69, 0x5f, 0xff, 0xaf }, { 70, 0x5f, 0xff, 0xd7 },
305 { 71, 0x5f, 0xff, 0xff }, { 225, 0x62, 0x62, 0x62 },
306 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
307 { 228, 0x80, 0x80, 0x80 }, { 72, 0x87, 0x00, 0x00 },
308 { 73, 0x87, 0x00, 0x5f }, { 74, 0x87, 0x00, 0x87 },
309 { 75, 0x87, 0x00, 0xaf }, { 76, 0x87, 0x00, 0xd7 },
310 { 77, 0x87, 0x00, 0xff }, { 78, 0x87, 0x5f, 0x00 },
311 { 79, 0x87, 0x5f, 0x5f }, { 80, 0x87, 0x5f, 0x87 },
312 { 81, 0x87, 0x5f, 0xaf }, { 82, 0x87, 0x5f, 0xd7 },
313 { 83, 0x87, 0x5f, 0xff }, { 84, 0x87, 0x87, 0x00 },
314 { 85, 0x87, 0x87, 0x5f }, { 86, 0x87, 0x87, 0x87 },
315 { 87, 0x87, 0x87, 0xaf }, { 88, 0x87, 0x87, 0xd7 },
316 { 89, 0x87, 0x87, 0xff }, { 90, 0x87, 0xaf, 0x00 },
317 { 91, 0x87, 0xaf, 0x5f }, { 92, 0x87, 0xaf, 0x87 },
318 { 93, 0x87, 0xaf, 0xaf }, { 94, 0x87, 0xaf, 0xd7 },
319 { 95, 0x87, 0xaf, 0xff }, { 96, 0x87, 0xd7, 0x00 },
320 { 97, 0x87, 0xd7, 0x5f }, { 98, 0x87, 0xd7, 0x87 },
321 { 99, 0x87, 0xd7, 0xaf }, { 100, 0x87, 0xd7, 0xd7 },
322 { 101, 0x87, 0xd7, 0xff }, { 102, 0x87, 0xff, 0x00 },
323 { 103, 0x87, 0xff, 0x5f }, { 104, 0x87, 0xff, 0x87 },
324 { 105, 0x87, 0xff, 0xaf }, { 106, 0x87, 0xff, 0xd7 },
325 { 107, 0x87, 0xff, 0xff }, { 229, 0x8a, 0x8a, 0x8a },
326 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
327 { 232, 0xa8, 0xa8, 0xa8 }, { 108, 0xaf, 0x00, 0x00 },
328 { 109, 0xaf, 0x00, 0x5f }, { 110, 0xaf, 0x00, 0x87 },
329 { 111, 0xaf, 0x00, 0xaf }, { 112, 0xaf, 0x00, 0xd7 },
330 { 113, 0xaf, 0x00, 0xff }, { 114, 0xaf, 0x5f, 0x00 },
331 { 115, 0xaf, 0x5f, 0x5f }, { 116, 0xaf, 0x5f, 0x87 },
332 { 117, 0xaf, 0x5f, 0xaf }, { 118, 0xaf, 0x5f, 0xd7 },
333 { 119, 0xaf, 0x5f, 0xff }, { 120, 0xaf, 0x87, 0x00 },
334 { 121, 0xaf, 0x87, 0x5f }, { 122, 0xaf, 0x87, 0x87 },
335 { 123, 0xaf, 0x87, 0xaf }, { 124, 0xaf, 0x87, 0xd7 },
336 { 125, 0xaf, 0x87, 0xff }, { 126, 0xaf, 0xaf, 0x00 },
337 { 127, 0xaf, 0xaf, 0x5f }, { 128, 0xaf, 0xaf, 0x87 },
338 { 129, 0xaf, 0xaf, 0xaf }, { 130, 0xaf, 0xaf, 0xd7 },
339 { 131, 0xaf, 0xaf, 0xff }, { 132, 0xaf, 0xd7, 0x00 },
340 { 133, 0xaf, 0xd7, 0x5f }, { 134, 0xaf, 0xd7, 0x87 },
341 { 135, 0xaf, 0xd7, 0xaf }, { 136, 0xaf, 0xd7, 0xd7 },
342 { 137, 0xaf, 0xd7, 0xff }, { 138, 0xaf, 0xff, 0x00 },
343 { 139, 0xaf, 0xff, 0x5f }, { 140, 0xaf, 0xff, 0x87 },
344 { 141, 0xaf, 0xff, 0xaf }, { 142, 0xaf, 0xff, 0xd7 },
345 { 143, 0xaf, 0xff, 0xff }, { 233, 0xb2, 0xb2, 0xb2 },
346 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
347 { 236, 0xd0, 0xd0, 0xd0 }, { 144, 0xd7, 0x00, 0x00 },
348 { 145, 0xd7, 0x00, 0x5f }, { 146, 0xd7, 0x00, 0x87 },
349 { 147, 0xd7, 0x00, 0xaf }, { 148, 0xd7, 0x00, 0xd7 },
350 { 149, 0xd7, 0x00, 0xff }, { 150, 0xd7, 0x5f, 0x00 },
351 { 151, 0xd7, 0x5f, 0x5f }, { 152, 0xd7, 0x5f, 0x87 },
352 { 153, 0xd7, 0x5f, 0xaf }, { 154, 0xd7, 0x5f, 0xd7 },
353 { 155, 0xd7, 0x5f, 0xff }, { 156, 0xd7, 0x87, 0x00 },
354 { 157, 0xd7, 0x87, 0x5f }, { 158, 0xd7, 0x87, 0x87 },
355 { 159, 0xd7, 0x87, 0xaf }, { 160, 0xd7, 0x87, 0xd7 },
356 { 161, 0xd7, 0x87, 0xff }, { 162, 0xd7, 0xaf, 0x00 },
357 { 163, 0xd7, 0xaf, 0x5f }, { 164, 0xd7, 0xaf, 0x87 },
358 { 165, 0xd7, 0xaf, 0xaf }, { 166, 0xd7, 0xaf, 0xd7 },
359 { 167, 0xd7, 0xaf, 0xff }, { 168, 0xd7, 0xd7, 0x00 },
360 { 169, 0xd7, 0xd7, 0x5f }, { 170, 0xd7, 0xd7, 0x87 },
361 { 171, 0xd7, 0xd7, 0xaf }, { 172, 0xd7, 0xd7, 0xd7 },
362 { 173, 0xd7, 0xd7, 0xff }, { 174, 0xd7, 0xff, 0x00 },
363 { 175, 0xd7, 0xff, 0x5f }, { 176, 0xd7, 0xff, 0x87 },
364 { 177, 0xd7, 0xff, 0xaf }, { 178, 0xd7, 0xff, 0xd7 },
365 { 179, 0xd7, 0xff, 0xff }, { 237, 0xda, 0xda, 0xda },
366 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
367 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
368 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
369 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
370 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
371 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
372 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
373 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
374 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
375 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
376 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
377 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
378 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
379 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
380 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
381 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
382 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
383 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
384 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
387 static const unsigned char color_256_to_16[256] = {
388 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
389 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
390 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
391 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
392 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
393 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
394 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
395 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
396 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
397 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
398 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
399 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
400 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
401 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
402 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
403 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
406 Color rgb = { .r = r, .g = g, .b = b };
407 const Color *found = bsearch(&rgb, color_to_256, LENGTH(color_to_256),
408 sizeof color_to_256[0], color_compare);
410 if (!found) {
411 unsigned lowest = UINT_MAX;
412 found = color_from_256;
413 for (int i = 0; i < 240; i++) {
414 int dr = (int)color_from_256[i].r - r;
415 int dg = (int)color_from_256[i].g - g;
416 int db = (int)color_from_256[i].b - b;
418 unsigned int distance = dr * dr + dg * dg + db * db;
419 if (distance < lowest) {
420 lowest = distance;
421 found = &color_from_256[i];
426 if (COLORS <= 16)
427 return color_256_to_16[found->i + 16];
428 return found->i + 16;
431 /* Convert color from string. */
432 static int color_fromstring(const char *s)
434 if (!s)
435 return -1;
436 if (*s == '#' && strlen(s) == 7) {
437 const char *cp;
438 unsigned char r, g, b;
439 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
440 if (*cp != '\0')
441 return -1;
442 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
443 if (n != 3)
444 return -1;
445 return color_find_rgb(r, g, b);
446 } else if ('0' <= *s && *s <= '9') {
447 int col = atoi(s);
448 return (col <= 0 || col > 255) ? -1 : col;
451 if (strcasecmp(s, "black") == 0)
452 return 0;
453 if (strcasecmp(s, "red") == 0)
454 return 1;
455 if (strcasecmp(s, "green") == 0)
456 return 2;
457 if (strcasecmp(s, "yellow") == 0)
458 return 3;
459 if (strcasecmp(s, "blue") == 0)
460 return 4;
461 if (strcasecmp(s, "magenta") == 0)
462 return 5;
463 if (strcasecmp(s, "cyan") == 0)
464 return 6;
465 if (strcasecmp(s, "white") == 0)
466 return 7;
467 return -1;
470 static inline unsigned int color_pair_hash(short fg, short bg) {
471 if (fg == -1)
472 fg = COLORS;
473 if (bg == -1)
474 bg = COLORS + 1;
475 return fg * (COLORS + 2) + bg;
478 static short color_pair_get(short fg, short bg) {
479 static bool has_default_colors;
480 static short *color2palette, default_fg, default_bg;
481 static short color_pairs_max, color_pair_current;
483 if (!color2palette) {
484 pair_content(0, &default_fg, &default_bg);
485 if (default_fg == -1)
486 default_fg = COLOR_WHITE;
487 if (default_bg == -1)
488 default_bg = COLOR_BLACK;
489 has_default_colors = (use_default_colors() == OK);
490 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
491 if (COLORS)
492 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
495 if (fg >= COLORS)
496 fg = default_fg;
497 if (bg >= COLORS)
498 bg = default_bg;
500 if (!has_default_colors) {
501 if (fg == -1)
502 fg = default_fg;
503 if (bg == -1)
504 bg = default_bg;
507 if (!color2palette || (fg == -1 && bg == -1))
508 return 0;
510 unsigned int index = color_pair_hash(fg, bg);
511 if (color2palette[index] == 0) {
512 short oldfg, oldbg;
513 if (++color_pair_current >= color_pairs_max)
514 color_pair_current = 1;
515 pair_content(color_pair_current, &oldfg, &oldbg);
516 unsigned int old_index = color_pair_hash(oldfg, oldbg);
517 if (init_pair(color_pair_current, fg, bg) == OK) {
518 color2palette[old_index] = 0;
519 color2palette[index] = color_pair_current;
523 return color2palette[index];
526 static inline attr_t style_to_attr(CellStyle *style) {
527 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
530 static bool ui_window_syntax_style(UiWin *w, int id, const char *style) {
531 UiCursesWin *win = (UiCursesWin*)w;
532 if (id >= UI_STYLE_MAX)
533 return false;
534 if (!style)
535 return true;
536 CellStyle cell_style = win->styles[UI_STYLE_DEFAULT];
537 char *style_copy = strdup(style), *option = style_copy, *next, *p;
538 while (option) {
539 if ((next = strchr(option, ',')))
540 *next++ = '\0';
541 if ((p = strchr(option, ':')))
542 *p++ = '\0';
543 if (!strcasecmp(option, "reverse")) {
544 cell_style.attr |= A_REVERSE;
545 } else if (!strcasecmp(option, "bold")) {
546 cell_style.attr |= A_BOLD;
547 } else if (!strcasecmp(option, "notbold")) {
548 cell_style.attr &= ~A_BOLD;
549 #ifdef A_ITALIC
550 } else if (!strcasecmp(option, "italics")) {
551 cell_style.attr |= A_ITALIC;
552 } else if (!strcasecmp(option, "notitalics")) {
553 cell_style.attr &= ~A_ITALIC;
554 #endif
555 } else if (!strcasecmp(option, "underlined")) {
556 cell_style.attr |= A_UNDERLINE;
557 } else if (!strcasecmp(option, "notunderlined")) {
558 cell_style.attr &= ~A_UNDERLINE;
559 } else if (!strcasecmp(option, "blink")) {
560 cell_style.attr |= A_BLINK;
561 } else if (!strcasecmp(option, "notblink")) {
562 cell_style.attr &= ~A_BLINK;
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 const Line *cursor_line = view_line_get(win->view);
611 size_t cursor_lineno = cursor_line->lineno;
612 werase(win->winside);
613 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
614 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
615 for (const Line *l = line; l; l = l->next, i++) {
616 if (l->lineno && l->lineno != prev_lineno) {
617 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
618 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
619 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
620 size_t rel = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
621 if (l->lineno > cursor_lineno)
622 rel = l->lineno - cursor_lineno;
623 else if (l->lineno < cursor_lineno)
624 rel = cursor_lineno - l->lineno;
625 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
628 prev_lineno = l->lineno;
630 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
631 return true;
635 static void ui_window_draw_status(UiWin *w) {
636 UiCursesWin *win = (UiCursesWin*)w;
637 if (!win->winstatus)
638 return;
639 UiCurses *uic = win->ui;
640 Vis *vis = uic->vis;
641 bool focused = uic->selwin == win;
642 const char *filename = win->file->name;
643 const char *status = vis_mode_status(vis);
644 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
645 mvwhline(win->winstatus, 0, 0, ' ', win->width);
646 mvwprintw(win->winstatus, 0, 0, "%s %s %s %s",
647 focused && status ? status : "",
648 filename ? filename : "[No Name]",
649 text_modified(win->file->text) ? "[+]" : "",
650 vis_macro_recording(vis) ? "recording": "");
652 char buf[4*32] = "", *msg = buf;
653 int cursor_count = view_cursors_count(win->view);
654 if (cursor_count > 1) {
655 Cursor *c = view_cursors_primary_get(win->view);
656 int cursor_number = view_cursors_number(c) + 1;
657 msg += sprintf(msg, "[%d/%d] ", cursor_number, cursor_count);
660 if (!(win->options & UI_OPTION_LARGE_FILE)) {
661 Cursor *cur = view_cursors_primary_get(win->view);
662 size_t line = view_cursors_line(cur);
663 size_t col = view_cursors_col(cur);
664 if (col > UI_LARGE_FILE_LINE_SIZE)
665 win->options |= UI_OPTION_LARGE_FILE;
666 msg += sprintf(msg, "%zu, %zu", line, col);
669 if (buf[0])
670 mvwaddstr(win->winstatus, 0, win->width - (msg - buf) - 1, buf);
673 static void ui_window_draw(UiWin *w) {
674 UiCursesWin *win = (UiCursesWin*)w;
675 if (!ui_window_draw_sidebar(win))
676 return;
678 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
679 wmove(win->win, 0, 0);
680 int width = view_width_get(win->view);
681 CellStyle *prev_style = NULL;
682 size_t cursor_lineno = -1;
684 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
685 Filerange selection = view_selection_get(win->view);
686 if (!view_cursors_multiple(win->view) && !text_range_valid(&selection)) {
687 const Line *line = view_line_get(win->view);
688 cursor_lineno = line->lineno;
692 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
693 short cursor_line_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
694 bool multiple_cursors = view_cursors_multiple(win->view);
695 attr_t attr = A_NORMAL;
697 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
698 bool cursor_line = l->lineno == cursor_lineno;
699 for (int x = 0; x < width; x++) {
700 enum UiStyles style_id = l->cells[x].style;
701 if (style_id == 0)
702 style_id = UI_STYLE_DEFAULT;
703 CellStyle *style = &win->styles[style_id];
705 if (l->cells[x].cursor && win->ui->selwin == win) {
706 if (multiple_cursors && l->cells[x].cursor_primary)
707 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR_PRIMARY]);
708 else
709 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
710 prev_style = NULL;
711 } else if (l->cells[x].selected) {
712 if (style->fg == selection_bg)
713 attr |= A_REVERSE;
714 else
715 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
716 prev_style = NULL;
717 } else if (cursor_line) {
718 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, cursor_line_bg));
719 prev_style = NULL;
720 } else if (style != prev_style) {
721 attr = style_to_attr(style);
722 prev_style = style;
724 wattrset(win->win, attr);
725 waddstr(win->win, l->cells[x].data);
727 /* try to fixup display issues, in theory we should always output a full line */
728 int x, y;
729 getyx(win->win, y, x);
730 (void)y;
731 wattrset(win->win, A_NORMAL);
732 for (; 0 < x && x < width; x++)
733 waddstr(win->win, " ");
736 wclrtobot(win->win);
738 if (win->winstatus)
739 ui_window_draw_status(w);
742 static void ui_window_reload(UiWin *w, File *file) {
743 UiCursesWin *win = (UiCursesWin*)w;
744 win->file = file;
745 win->sidebar_width = 0;
746 view_reload(win->view, file->text);
747 ui_window_draw(w);
750 static void ui_window_update(UiCursesWin *win) {
751 if (win->winstatus)
752 wnoutrefresh(win->winstatus);
753 if (win->winside)
754 wnoutrefresh(win->winside);
755 wnoutrefresh(win->win);
758 static void ui_arrange(Ui *ui, enum UiLayout layout) {
759 UiCurses *uic = (UiCurses*)ui;
760 uic->layout = layout;
761 int n = 0, m = !!uic->info[0], x = 0, y = 0;
762 for (UiCursesWin *win = uic->windows; win; win = win->next) {
763 if (win->options & UI_OPTION_ONELINE)
764 m++;
765 else
766 n++;
768 int max_height = uic->height - m;
769 int width = (uic->width / MAX(1, n)) - 1;
770 int height = max_height / MAX(1, n);
771 for (UiCursesWin *win = uic->windows; win; win = win->next) {
772 if (win->options & UI_OPTION_ONELINE)
773 continue;
774 n--;
775 if (layout == UI_LAYOUT_HORIZONTAL) {
776 int h = n ? height : max_height - y;
777 ui_window_resize(win, uic->width, h);
778 ui_window_move(win, x, y);
779 y += h;
780 } else {
781 int w = n ? width : uic->width - x;
782 ui_window_resize(win, w, max_height);
783 ui_window_move(win, x, y);
784 x += w;
785 if (n)
786 mvvline(0, x++, ACS_VLINE, max_height);
790 if (layout == UI_LAYOUT_VERTICAL)
791 y = max_height;
793 for (UiCursesWin *win = uic->windows; win; win = win->next) {
794 if (!(win->options & UI_OPTION_ONELINE))
795 continue;
796 ui_window_resize(win, uic->width, 1);
797 ui_window_move(win, 0, y++);
801 static void ui_draw(Ui *ui) {
802 UiCurses *uic = (UiCurses*)ui;
803 erase();
804 ui_arrange(ui, uic->layout);
806 for (UiCursesWin *win = uic->windows; win; win = win->next)
807 ui_window_draw((UiWin*)win);
809 if (uic->info[0]) {
810 attrset(A_BOLD);
811 mvaddstr(uic->height-1, 0, uic->info);
814 wnoutrefresh(stdscr);
817 static void ui_redraw(Ui *ui) {
818 clear();
819 ui_draw(ui);
822 static void ui_resize_to(Ui *ui, int width, int height) {
823 UiCurses *uic = (UiCurses*)ui;
824 uic->width = width;
825 uic->height = height;
826 ui_draw(ui);
829 static void ui_resize(Ui *ui) {
830 struct winsize ws;
831 int width, height;
833 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
834 getmaxyx(stdscr, height, width);
835 } else {
836 width = ws.ws_col;
837 height = ws.ws_row;
840 resizeterm(height, width);
841 wresize(stdscr, height, width);
842 ui_resize_to(ui, width, height);
845 static void ui_update(Ui *ui) {
846 UiCurses *uic = (UiCurses*)ui;
847 if (need_resize) {
848 ui_resize(ui);
849 need_resize = false;
851 for (UiCursesWin *win = uic->windows; win; win = win->next) {
852 if (win != uic->selwin)
853 ui_window_update(win);
856 if (uic->selwin)
857 ui_window_update(uic->selwin);
858 doupdate();
861 static void ui_window_free(UiWin *w) {
862 UiCursesWin *win = (UiCursesWin*)w;
863 if (!win)
864 return;
865 UiCurses *uic = win->ui;
866 if (win->prev)
867 win->prev->next = win->next;
868 if (win->next)
869 win->next->prev = win->prev;
870 if (uic->windows == win)
871 uic->windows = win->next;
872 if (uic->selwin == win)
873 uic->selwin = NULL;
874 win->next = win->prev = NULL;
875 if (win->winstatus)
876 delwin(win->winstatus);
877 if (win->winside)
878 delwin(win->winside);
879 if (win->win)
880 delwin(win->win);
881 free(win);
884 static void ui_window_focus(UiWin *w) {
885 UiCursesWin *win = (UiCursesWin*)w;
886 UiCursesWin *oldsel = win->ui->selwin;
887 win->ui->selwin = win;
888 if (oldsel)
889 ui_window_draw((UiWin*)oldsel);
890 ui_window_draw(w);
893 static void ui_window_options_set(UiWin *w, enum UiOption options) {
894 UiCursesWin *win = (UiCursesWin*)w;
895 win->options = options;
896 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
897 if (!win->winside)
898 win->winside = newwin(1, 1, 1, 1);
899 } else {
900 if (win->winside) {
901 delwin(win->winside);
902 win->winside = NULL;
903 win->sidebar_width = 0;
906 if (options & UI_OPTION_STATUSBAR) {
907 if (!win->winstatus)
908 win->winstatus = newwin(1, 0, 0, 0);
909 } else {
910 if (win->winstatus)
911 delwin(win->winstatus);
912 win->winstatus = NULL;
915 if (options & UI_OPTION_ONELINE) {
916 /* move the new window to the end of the list */
917 UiCurses *uic = win->ui;
918 UiCursesWin *last = uic->windows;
919 while (last->next)
920 last = last->next;
921 if (last != win) {
922 if (win->prev)
923 win->prev->next = win->next;
924 if (win->next)
925 win->next->prev = win->prev;
926 if (uic->windows == win)
927 uic->windows = win->next;
928 last->next = win;
929 win->prev = last;
930 win->next = NULL;
934 ui_draw((Ui*)win->ui);
937 static enum UiOption ui_window_options_get(UiWin *w) {
938 UiCursesWin *win = (UiCursesWin*)w;
939 return win->options;
942 static void ui_window_swap(UiWin *aw, UiWin *bw) {
943 UiCursesWin *a = (UiCursesWin*)aw;
944 UiCursesWin *b = (UiCursesWin*)bw;
945 if (a == b || !a || !b)
946 return;
947 UiCurses *ui = a->ui;
948 UiCursesWin *tmp = a->next;
949 a->next = b->next;
950 b->next = tmp;
951 if (a->next)
952 a->next->prev = a;
953 if (b->next)
954 b->next->prev = b;
955 tmp = a->prev;
956 a->prev = b->prev;
957 b->prev = tmp;
958 if (a->prev)
959 a->prev->next = a;
960 if (b->prev)
961 b->prev->next = b;
962 if (ui->windows == a)
963 ui->windows = b;
964 else if (ui->windows == b)
965 ui->windows = a;
966 if (ui->selwin == a)
967 ui_window_focus(bw);
968 else if (ui->selwin == b)
969 ui_window_focus(aw);
972 static UiWin *ui_window_new(Ui *ui, View *view, File *file, enum UiOption options) {
973 UiCurses *uic = (UiCurses*)ui;
974 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
975 if (!win)
976 return NULL;
978 win->uiwin = (UiWin) {
979 .draw = ui_window_draw,
980 .draw_status = ui_window_draw_status,
981 .options_set = ui_window_options_set,
982 .options_get = ui_window_options_get,
983 .reload = ui_window_reload,
984 .syntax_style = ui_window_syntax_style,
987 if (!(win->win = newwin(0, 0, 0, 0))) {
988 ui_window_free((UiWin*)win);
989 return NULL;
993 for (int i = 0; i < UI_STYLE_MAX; i++) {
994 win->styles[i] = (CellStyle) {
995 .fg = -1, .bg = -1, .attr = A_NORMAL,
999 win->styles[UI_STYLE_CURSOR].attr |= A_REVERSE;
1000 win->styles[UI_STYLE_CURSOR_PRIMARY].attr |= A_REVERSE|A_BLINK;
1001 win->styles[UI_STYLE_SELECTION].attr |= A_REVERSE;
1002 win->styles[UI_STYLE_COLOR_COLUMN].attr |= A_REVERSE;
1004 win->ui = uic;
1005 win->view = view;
1006 win->file = file;
1007 view_ui(view, &win->uiwin);
1009 if (uic->windows)
1010 uic->windows->prev = win;
1011 win->next = uic->windows;
1012 uic->windows = win;
1014 if (text_size(file->text) > UI_LARGE_FILE_SIZE) {
1015 options |= UI_OPTION_LARGE_FILE;
1016 options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
1019 ui_window_options_set((UiWin*)win, options);
1021 return &win->uiwin;
1024 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
1025 UiCurses *uic = (UiCurses*)ui;
1026 endwin();
1027 if (uic->termkey)
1028 termkey_stop(uic->termkey);
1029 tcsetattr(STDERR_FILENO, TCSANOW, &uic->tio);
1030 vfprintf(stderr, msg, ap);
1031 exit(EXIT_FAILURE);
1034 __attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) {
1035 va_list ap;
1036 va_start(ap, msg);
1037 ui_die(ui, msg, ap);
1038 va_end(ap);
1041 static void ui_info(Ui *ui, const char *msg, va_list ap) {
1042 UiCurses *uic = (UiCurses*)ui;
1043 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
1044 ui_draw(ui);
1047 static void ui_info_hide(Ui *ui) {
1048 UiCurses *uic = (UiCurses*)ui;
1049 if (uic->info[0]) {
1050 uic->info[0] = '\0';
1051 ui_draw(ui);
1055 static bool ui_init(Ui *ui, Vis *vis) {
1056 UiCurses *uic = (UiCurses*)ui;
1057 uic->vis = vis;
1058 return true;
1061 static bool ui_start(Ui *ui) {
1062 Vis *vis = ((UiCurses*)ui)->vis;
1063 const char *theme = getenv("VIS_THEME");
1064 if (theme && theme[0]) {
1065 if (!vis_theme_load(vis, theme))
1066 vis_info_show(vis, "Warning: failed to load theme `%s'", theme);
1067 } else {
1068 theme = COLORS <= 16 ? "default-16" : "default-256";
1069 if (!vis_theme_load(vis, theme))
1070 vis_info_show(vis, "Warning: failed to load theme `%s' set $VIS_PATH", theme);
1072 return true;
1075 static TermKey *ui_termkey_new(int fd) {
1076 TermKey *termkey = termkey_new(fd, TERMKEY_FLAG_UTF8);
1077 if (termkey)
1078 termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
1079 return termkey;
1082 static void ui_suspend(Ui *ui) {
1083 endwin();
1084 raise(SIGSTOP);
1087 static void ui_needkey(Ui *ui) {
1088 UiCurses *uic = (UiCurses*)ui;
1089 termkey_advisereadable(uic->termkey);
1092 static const char *ui_getkey(Ui *ui) {
1093 UiCurses *uic = (UiCurses*)ui;
1094 TermKeyKey key;
1095 TermKeyResult ret = termkey_getkey(uic->termkey, &key);
1097 if (ret == TERMKEY_RES_EOF) {
1098 int tty = open("/dev/tty", O_RDWR);
1099 if (tty == -1)
1100 goto fatal;
1101 if (tty != STDIN_FILENO && dup2(tty, STDIN_FILENO) == -1)
1102 goto fatal;
1103 termkey_destroy(uic->termkey);
1104 if (!(uic->termkey = ui_termkey_new(STDIN_FILENO)))
1105 goto fatal;
1106 return NULL;
1109 if (ret == TERMKEY_RES_AGAIN) {
1110 struct pollfd fd;
1111 fd.fd = STDIN_FILENO;
1112 fd.events = POLLIN;
1113 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
1114 ret = termkey_getkey_force(uic->termkey, &key);
1117 if (ret != TERMKEY_RES_KEY)
1118 return NULL;
1119 termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM);
1120 return uic->key;
1122 fatal:
1123 ui_die_msg(ui, "Failed to re-open stdin as /dev/tty\n");
1124 return NULL;
1127 static void ui_terminal_save(Ui *ui) {
1128 UiCurses *uic = (UiCurses*)ui;
1129 curs_set(1);
1130 reset_shell_mode();
1131 termkey_stop(uic->termkey);
1134 static void ui_terminal_restore(Ui *ui) {
1135 UiCurses *uic = (UiCurses*)ui;
1136 termkey_start(uic->termkey);
1137 reset_prog_mode();
1138 wclear(stdscr);
1139 curs_set(0);
1142 Ui *ui_curses_new(void) {
1144 UiCurses *uic = calloc(1, sizeof(UiCurses));
1145 Ui *ui = (Ui*)uic;
1146 if (!uic)
1147 return NULL;
1148 tcgetattr(STDERR_FILENO, &uic->tio);
1149 if (!(uic->termkey = ui_termkey_new(STDIN_FILENO)))
1150 goto err;
1151 setlocale(LC_CTYPE, "");
1152 if (!getenv("ESCDELAY"))
1153 set_escdelay(50);
1154 char *term = getenv("TERM");
1155 if (!term)
1156 term = "xterm";
1157 if (!newterm(term, stderr, stdin)) {
1158 snprintf(uic->info, sizeof(uic->info), "Warning: unknown term `%s'", term);
1159 if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin))
1160 goto err;
1162 start_color();
1163 use_default_colors();
1164 raw();
1165 noecho();
1166 nonl();
1167 keypad(stdscr, TRUE);
1168 meta(stdscr, TRUE);
1169 curs_set(0);
1170 /* needed because we use getch() which implicitly calls refresh() which
1171 would clear the screen (overwrite it with an empty / unused stdscr */
1172 refresh();
1174 *ui = (Ui) {
1175 .init = ui_init,
1176 .start = ui_start,
1177 .free = ui_curses_free,
1178 .suspend = ui_suspend,
1179 .update = ui_update,
1180 .window_new = ui_window_new,
1181 .window_free = ui_window_free,
1182 .window_focus = ui_window_focus,
1183 .window_swap = ui_window_swap,
1184 .draw = ui_draw,
1185 .redraw = ui_redraw,
1186 .arrange = ui_arrange,
1187 .die = ui_die,
1188 .info = ui_info,
1189 .info_hide = ui_info_hide,
1190 .needkey = ui_needkey,
1191 .getkey = ui_getkey,
1192 .terminal_save = ui_terminal_save,
1193 .terminal_restore = ui_terminal_restore,
1196 struct sigaction sa;
1197 sa.sa_flags = 0;
1198 sigemptyset(&sa.sa_mask);
1199 sa.sa_handler = sigwinch_handler;
1200 sigaction(SIGWINCH, &sa, NULL);
1201 sigaction(SIGCONT, &sa, NULL);
1203 ui_resize(ui);
1205 return ui;
1206 err:
1207 ui_curses_free(ui);
1208 return NULL;
1211 void ui_curses_free(Ui *ui) {
1212 UiCurses *uic = (UiCurses*)ui;
1213 if (!uic)
1214 return;
1215 while (uic->windows)
1216 ui_window_free((UiWin*)uic->windows);
1217 endwin();
1218 if (uic->termkey)
1219 termkey_destroy(uic->termkey);
1220 tcsetattr(STDERR_FILENO, TCSANOW, &uic->tio);
1221 free(uic);