vis: implement :set cursorline
[vis.git] / ui-curses.c
blobfd557059b39d70967618816d695e11e3af6aeb95
1 /* parts of the color handling code originates from tmux/colour.c and is
3 * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
5 */
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <strings.h>
10 #include <limits.h>
11 #include <ctype.h>
12 #include <signal.h>
13 #include <locale.h>
14 #include <poll.h>
15 #include <sys/ioctl.h>
17 #include "ui.h"
18 #include "ui-curses.h"
19 #include "util.h"
20 #include "text-util.h"
22 #ifdef NCURSES_VERSION
23 # ifndef NCURSES_EXT_COLORS
24 # define NCURSES_EXT_COLORS 0
25 # endif
26 # if !NCURSES_EXT_COLORS
27 # define MAX_COLOR_PAIRS 256
28 # endif
29 #endif
30 #ifndef MAX_COLOR_PAIRS
31 # define MAX_COLOR_PAIRS COLOR_PAIRS
32 #endif
34 #ifdef PDCURSES
35 int ESCDELAY;
36 #endif
37 #ifndef NCURSES_REENTRANT
38 # define set_escdelay(d) (ESCDELAY = (d))
39 #endif
41 #define CONTROL(k) ((k)&0x1F)
43 #if 0
44 #define wresize(win, y, x) do { \
45 if (wresize(win, y, x) == ERR) { \
46 printf("ERROR resizing: %d x %d\n", x, y); \
47 } else { \
48 printf("OK resizing: %d x %d\n", x, y); \
49 } \
50 fflush(stdout); \
51 } while (0);
53 #define mvwin(win, y, x) do { \
54 if (mvwin(win, y, x) == ERR) { \
55 printf("ERROR moving: %d x %d\n", x, y); \
56 } else { \
57 printf("OK moving: %d x %d\n", x, y); \
58 } \
59 fflush(stdout); \
60 } while (0);
61 #endif
63 typedef struct {
64 attr_t attr;
65 short fg, bg;
66 } CellStyle;
68 typedef struct UiCursesWin UiCursesWin;
70 typedef struct {
71 Ui ui; /* generic ui interface, has to be the first struct member */
72 Vis *vis; /* editor instance to which this ui belongs */
73 UiCursesWin *windows; /* all windows managed by this ui */
74 UiCursesWin *selwin; /* the currently selected layout */
75 char prompt_title[255]; /* prompt_title[0] == '\0' if prompt isn't shown */
76 UiCursesWin *prompt_win; /* like a normal window but without a status bar */
77 char info[255]; /* info message displayed at the bottom of the screen */
78 int width, height; /* terminal dimensions available for all windows */
79 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
80 TermKey *termkey; /* libtermkey instance to handle keyboard input */
81 char key[64]; /* string representation of last pressed key */
82 } UiCurses;
84 struct UiCursesWin {
85 UiWin uiwin; /* generic interface, has to be the first struct member */
86 UiCurses *ui; /* ui which manages this window */
87 File *file; /* file being displayed in this window */
88 View *view; /* current viewport */
89 WINDOW *win; /* curses window for the text area */
90 WINDOW *winstatus; /* curses window for the status bar */
91 WINDOW *winside; /* curses window for the side bar (line numbers) */
92 int width, height; /* window dimension including status bar */
93 int x, y; /* window position */
94 int sidebar_width; /* width of the sidebar showing line numbers etc. */
95 UiCursesWin *next, *prev; /* pointers to neighbouring windows */
96 enum UiOption options; /* display settings for this window */
97 CellStyle styles[UI_STYLE_MAX];
100 static volatile sig_atomic_t need_resize; /* TODO */
102 static void sigwinch_handler(int sig) {
103 need_resize = true;
106 typedef struct {
107 unsigned char i;
108 unsigned char r;
109 unsigned char g;
110 unsigned char b;
111 } Color;
113 static int color_compare(const void *lhs0, const void *rhs0) {
114 const Color *lhs = lhs0, *rhs = rhs0;
116 if (lhs->r < rhs->r)
117 return -1;
118 if (lhs->r > rhs->r)
119 return 1;
121 if (lhs->g < rhs->g)
122 return -1;
123 if (lhs->g > rhs->g)
124 return 1;
126 if (lhs->b < rhs->b)
127 return -1;
128 if (lhs->b > rhs->b)
129 return 1;
131 return 0;
134 /* Work out the nearest color from the 256 color set. */
135 static int color_find_rgb(unsigned char r, unsigned char g, unsigned char b)
137 static const Color color_from_256[] = {
138 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
139 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
140 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
141 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
142 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
143 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
144 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
145 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
146 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
147 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
148 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
149 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
150 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
151 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
152 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
153 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
154 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
155 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
156 { 36, 0x5f, 0x00, 0x00 }, { 37, 0x5f, 0x00, 0x5f },
157 { 38, 0x5f, 0x00, 0x87 }, { 39, 0x5f, 0x00, 0xaf },
158 { 40, 0x5f, 0x00, 0xd7 }, { 41, 0x5f, 0x00, 0xff },
159 { 42, 0x5f, 0x5f, 0x00 }, { 43, 0x5f, 0x5f, 0x5f },
160 { 44, 0x5f, 0x5f, 0x87 }, { 45, 0x5f, 0x5f, 0xaf },
161 { 46, 0x5f, 0x5f, 0xd7 }, { 47, 0x5f, 0x5f, 0xff },
162 { 48, 0x5f, 0x87, 0x00 }, { 49, 0x5f, 0x87, 0x5f },
163 { 50, 0x5f, 0x87, 0x87 }, { 51, 0x5f, 0x87, 0xaf },
164 { 52, 0x5f, 0x87, 0xd7 }, { 53, 0x5f, 0x87, 0xff },
165 { 54, 0x5f, 0xaf, 0x00 }, { 55, 0x5f, 0xaf, 0x5f },
166 { 56, 0x5f, 0xaf, 0x87 }, { 57, 0x5f, 0xaf, 0xaf },
167 { 58, 0x5f, 0xaf, 0xd7 }, { 59, 0x5f, 0xaf, 0xff },
168 { 60, 0x5f, 0xd7, 0x00 }, { 61, 0x5f, 0xd7, 0x5f },
169 { 62, 0x5f, 0xd7, 0x87 }, { 63, 0x5f, 0xd7, 0xaf },
170 { 64, 0x5f, 0xd7, 0xd7 }, { 65, 0x5f, 0xd7, 0xff },
171 { 66, 0x5f, 0xff, 0x00 }, { 67, 0x5f, 0xff, 0x5f },
172 { 68, 0x5f, 0xff, 0x87 }, { 69, 0x5f, 0xff, 0xaf },
173 { 70, 0x5f, 0xff, 0xd7 }, { 71, 0x5f, 0xff, 0xff },
174 { 72, 0x87, 0x00, 0x00 }, { 73, 0x87, 0x00, 0x5f },
175 { 74, 0x87, 0x00, 0x87 }, { 75, 0x87, 0x00, 0xaf },
176 { 76, 0x87, 0x00, 0xd7 }, { 77, 0x87, 0x00, 0xff },
177 { 78, 0x87, 0x5f, 0x00 }, { 79, 0x87, 0x5f, 0x5f },
178 { 80, 0x87, 0x5f, 0x87 }, { 81, 0x87, 0x5f, 0xaf },
179 { 82, 0x87, 0x5f, 0xd7 }, { 83, 0x87, 0x5f, 0xff },
180 { 84, 0x87, 0x87, 0x00 }, { 85, 0x87, 0x87, 0x5f },
181 { 86, 0x87, 0x87, 0x87 }, { 87, 0x87, 0x87, 0xaf },
182 { 88, 0x87, 0x87, 0xd7 }, { 89, 0x87, 0x87, 0xff },
183 { 90, 0x87, 0xaf, 0x00 }, { 91, 0x87, 0xaf, 0x5f },
184 { 92, 0x87, 0xaf, 0x87 }, { 93, 0x87, 0xaf, 0xaf },
185 { 94, 0x87, 0xaf, 0xd7 }, { 95, 0x87, 0xaf, 0xff },
186 { 96, 0x87, 0xd7, 0x00 }, { 97, 0x87, 0xd7, 0x5f },
187 { 98, 0x87, 0xd7, 0x87 }, { 99, 0x87, 0xd7, 0xaf },
188 { 100, 0x87, 0xd7, 0xd7 }, { 101, 0x87, 0xd7, 0xff },
189 { 102, 0x87, 0xff, 0x00 }, { 103, 0x87, 0xff, 0x5f },
190 { 104, 0x87, 0xff, 0x87 }, { 105, 0x87, 0xff, 0xaf },
191 { 106, 0x87, 0xff, 0xd7 }, { 107, 0x87, 0xff, 0xff },
192 { 108, 0xaf, 0x00, 0x00 }, { 109, 0xaf, 0x00, 0x5f },
193 { 110, 0xaf, 0x00, 0x87 }, { 111, 0xaf, 0x00, 0xaf },
194 { 112, 0xaf, 0x00, 0xd7 }, { 113, 0xaf, 0x00, 0xff },
195 { 114, 0xaf, 0x5f, 0x00 }, { 115, 0xaf, 0x5f, 0x5f },
196 { 116, 0xaf, 0x5f, 0x87 }, { 117, 0xaf, 0x5f, 0xaf },
197 { 118, 0xaf, 0x5f, 0xd7 }, { 119, 0xaf, 0x5f, 0xff },
198 { 120, 0xaf, 0x87, 0x00 }, { 121, 0xaf, 0x87, 0x5f },
199 { 122, 0xaf, 0x87, 0x87 }, { 123, 0xaf, 0x87, 0xaf },
200 { 124, 0xaf, 0x87, 0xd7 }, { 125, 0xaf, 0x87, 0xff },
201 { 126, 0xaf, 0xaf, 0x00 }, { 127, 0xaf, 0xaf, 0x5f },
202 { 128, 0xaf, 0xaf, 0x87 }, { 129, 0xaf, 0xaf, 0xaf },
203 { 130, 0xaf, 0xaf, 0xd7 }, { 131, 0xaf, 0xaf, 0xff },
204 { 132, 0xaf, 0xd7, 0x00 }, { 133, 0xaf, 0xd7, 0x5f },
205 { 134, 0xaf, 0xd7, 0x87 }, { 135, 0xaf, 0xd7, 0xaf },
206 { 136, 0xaf, 0xd7, 0xd7 }, { 137, 0xaf, 0xd7, 0xff },
207 { 138, 0xaf, 0xff, 0x00 }, { 139, 0xaf, 0xff, 0x5f },
208 { 140, 0xaf, 0xff, 0x87 }, { 141, 0xaf, 0xff, 0xaf },
209 { 142, 0xaf, 0xff, 0xd7 }, { 143, 0xaf, 0xff, 0xff },
210 { 144, 0xd7, 0x00, 0x00 }, { 145, 0xd7, 0x00, 0x5f },
211 { 146, 0xd7, 0x00, 0x87 }, { 147, 0xd7, 0x00, 0xaf },
212 { 148, 0xd7, 0x00, 0xd7 }, { 149, 0xd7, 0x00, 0xff },
213 { 150, 0xd7, 0x5f, 0x00 }, { 151, 0xd7, 0x5f, 0x5f },
214 { 152, 0xd7, 0x5f, 0x87 }, { 153, 0xd7, 0x5f, 0xaf },
215 { 154, 0xd7, 0x5f, 0xd7 }, { 155, 0xd7, 0x5f, 0xff },
216 { 156, 0xd7, 0x87, 0x00 }, { 157, 0xd7, 0x87, 0x5f },
217 { 158, 0xd7, 0x87, 0x87 }, { 159, 0xd7, 0x87, 0xaf },
218 { 160, 0xd7, 0x87, 0xd7 }, { 161, 0xd7, 0x87, 0xff },
219 { 162, 0xd7, 0xaf, 0x00 }, { 163, 0xd7, 0xaf, 0x5f },
220 { 164, 0xd7, 0xaf, 0x87 }, { 165, 0xd7, 0xaf, 0xaf },
221 { 166, 0xd7, 0xaf, 0xd7 }, { 167, 0xd7, 0xaf, 0xff },
222 { 168, 0xd7, 0xd7, 0x00 }, { 169, 0xd7, 0xd7, 0x5f },
223 { 170, 0xd7, 0xd7, 0x87 }, { 171, 0xd7, 0xd7, 0xaf },
224 { 172, 0xd7, 0xd7, 0xd7 }, { 173, 0xd7, 0xd7, 0xff },
225 { 174, 0xd7, 0xff, 0x00 }, { 175, 0xd7, 0xff, 0x5f },
226 { 176, 0xd7, 0xff, 0x87 }, { 177, 0xd7, 0xff, 0xaf },
227 { 178, 0xd7, 0xff, 0xd7 }, { 179, 0xd7, 0xff, 0xff },
228 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
229 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
230 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
231 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
232 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
233 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
234 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
235 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
236 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
237 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
238 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
239 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
240 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
241 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
242 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
243 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
244 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
245 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
246 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
247 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
248 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
249 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
250 { 224, 0x58, 0x58, 0x58 }, { 225, 0x62, 0x62, 0x62 },
251 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
252 { 228, 0x80, 0x80, 0x80 }, { 229, 0x8a, 0x8a, 0x8a },
253 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
254 { 232, 0xa8, 0xa8, 0xa8 }, { 233, 0xb2, 0xb2, 0xb2 },
255 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
256 { 236, 0xd0, 0xd0, 0xd0 }, { 237, 0xda, 0xda, 0xda },
257 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
260 static const Color color_to_256[] = {
261 { 0, 0x00, 0x00, 0x00 }, { 1, 0x00, 0x00, 0x5f },
262 { 2, 0x00, 0x00, 0x87 }, { 3, 0x00, 0x00, 0xaf },
263 { 4, 0x00, 0x00, 0xd7 }, { 5, 0x00, 0x00, 0xff },
264 { 6, 0x00, 0x5f, 0x00 }, { 7, 0x00, 0x5f, 0x5f },
265 { 8, 0x00, 0x5f, 0x87 }, { 9, 0x00, 0x5f, 0xaf },
266 { 10, 0x00, 0x5f, 0xd7 }, { 11, 0x00, 0x5f, 0xff },
267 { 12, 0x00, 0x87, 0x00 }, { 13, 0x00, 0x87, 0x5f },
268 { 14, 0x00, 0x87, 0x87 }, { 15, 0x00, 0x87, 0xaf },
269 { 16, 0x00, 0x87, 0xd7 }, { 17, 0x00, 0x87, 0xff },
270 { 18, 0x00, 0xaf, 0x00 }, { 19, 0x00, 0xaf, 0x5f },
271 { 20, 0x00, 0xaf, 0x87 }, { 21, 0x00, 0xaf, 0xaf },
272 { 22, 0x00, 0xaf, 0xd7 }, { 23, 0x00, 0xaf, 0xff },
273 { 24, 0x00, 0xd7, 0x00 }, { 25, 0x00, 0xd7, 0x5f },
274 { 26, 0x00, 0xd7, 0x87 }, { 27, 0x00, 0xd7, 0xaf },
275 { 28, 0x00, 0xd7, 0xd7 }, { 29, 0x00, 0xd7, 0xff },
276 { 30, 0x00, 0xff, 0x00 }, { 31, 0x00, 0xff, 0x5f },
277 { 32, 0x00, 0xff, 0x87 }, { 33, 0x00, 0xff, 0xaf },
278 { 34, 0x00, 0xff, 0xd7 }, { 35, 0x00, 0xff, 0xff },
279 { 216, 0x08, 0x08, 0x08 }, { 217, 0x12, 0x12, 0x12 },
280 { 218, 0x1c, 0x1c, 0x1c }, { 219, 0x26, 0x26, 0x26 },
281 { 220, 0x30, 0x30, 0x30 }, { 221, 0x3a, 0x3a, 0x3a },
282 { 222, 0x44, 0x44, 0x44 }, { 223, 0x4e, 0x4e, 0x4e },
283 { 224, 0x58, 0x58, 0x58 }, { 36, 0x5f, 0x00, 0x00 },
284 { 37, 0x5f, 0x00, 0x5f }, { 38, 0x5f, 0x00, 0x87 },
285 { 39, 0x5f, 0x00, 0xaf }, { 40, 0x5f, 0x00, 0xd7 },
286 { 41, 0x5f, 0x00, 0xff }, { 42, 0x5f, 0x5f, 0x00 },
287 { 43, 0x5f, 0x5f, 0x5f }, { 44, 0x5f, 0x5f, 0x87 },
288 { 45, 0x5f, 0x5f, 0xaf }, { 46, 0x5f, 0x5f, 0xd7 },
289 { 47, 0x5f, 0x5f, 0xff }, { 48, 0x5f, 0x87, 0x00 },
290 { 49, 0x5f, 0x87, 0x5f }, { 50, 0x5f, 0x87, 0x87 },
291 { 51, 0x5f, 0x87, 0xaf }, { 52, 0x5f, 0x87, 0xd7 },
292 { 53, 0x5f, 0x87, 0xff }, { 54, 0x5f, 0xaf, 0x00 },
293 { 55, 0x5f, 0xaf, 0x5f }, { 56, 0x5f, 0xaf, 0x87 },
294 { 57, 0x5f, 0xaf, 0xaf }, { 58, 0x5f, 0xaf, 0xd7 },
295 { 59, 0x5f, 0xaf, 0xff }, { 60, 0x5f, 0xd7, 0x00 },
296 { 61, 0x5f, 0xd7, 0x5f }, { 62, 0x5f, 0xd7, 0x87 },
297 { 63, 0x5f, 0xd7, 0xaf }, { 64, 0x5f, 0xd7, 0xd7 },
298 { 65, 0x5f, 0xd7, 0xff }, { 66, 0x5f, 0xff, 0x00 },
299 { 67, 0x5f, 0xff, 0x5f }, { 68, 0x5f, 0xff, 0x87 },
300 { 69, 0x5f, 0xff, 0xaf }, { 70, 0x5f, 0xff, 0xd7 },
301 { 71, 0x5f, 0xff, 0xff }, { 225, 0x62, 0x62, 0x62 },
302 { 226, 0x6c, 0x6c, 0x6c }, { 227, 0x76, 0x76, 0x76 },
303 { 228, 0x80, 0x80, 0x80 }, { 72, 0x87, 0x00, 0x00 },
304 { 73, 0x87, 0x00, 0x5f }, { 74, 0x87, 0x00, 0x87 },
305 { 75, 0x87, 0x00, 0xaf }, { 76, 0x87, 0x00, 0xd7 },
306 { 77, 0x87, 0x00, 0xff }, { 78, 0x87, 0x5f, 0x00 },
307 { 79, 0x87, 0x5f, 0x5f }, { 80, 0x87, 0x5f, 0x87 },
308 { 81, 0x87, 0x5f, 0xaf }, { 82, 0x87, 0x5f, 0xd7 },
309 { 83, 0x87, 0x5f, 0xff }, { 84, 0x87, 0x87, 0x00 },
310 { 85, 0x87, 0x87, 0x5f }, { 86, 0x87, 0x87, 0x87 },
311 { 87, 0x87, 0x87, 0xaf }, { 88, 0x87, 0x87, 0xd7 },
312 { 89, 0x87, 0x87, 0xff }, { 90, 0x87, 0xaf, 0x00 },
313 { 91, 0x87, 0xaf, 0x5f }, { 92, 0x87, 0xaf, 0x87 },
314 { 93, 0x87, 0xaf, 0xaf }, { 94, 0x87, 0xaf, 0xd7 },
315 { 95, 0x87, 0xaf, 0xff }, { 96, 0x87, 0xd7, 0x00 },
316 { 97, 0x87, 0xd7, 0x5f }, { 98, 0x87, 0xd7, 0x87 },
317 { 99, 0x87, 0xd7, 0xaf }, { 100, 0x87, 0xd7, 0xd7 },
318 { 101, 0x87, 0xd7, 0xff }, { 102, 0x87, 0xff, 0x00 },
319 { 103, 0x87, 0xff, 0x5f }, { 104, 0x87, 0xff, 0x87 },
320 { 105, 0x87, 0xff, 0xaf }, { 106, 0x87, 0xff, 0xd7 },
321 { 107, 0x87, 0xff, 0xff }, { 229, 0x8a, 0x8a, 0x8a },
322 { 230, 0x94, 0x94, 0x94 }, { 231, 0x9e, 0x9e, 0x9e },
323 { 232, 0xa8, 0xa8, 0xa8 }, { 108, 0xaf, 0x00, 0x00 },
324 { 109, 0xaf, 0x00, 0x5f }, { 110, 0xaf, 0x00, 0x87 },
325 { 111, 0xaf, 0x00, 0xaf }, { 112, 0xaf, 0x00, 0xd7 },
326 { 113, 0xaf, 0x00, 0xff }, { 114, 0xaf, 0x5f, 0x00 },
327 { 115, 0xaf, 0x5f, 0x5f }, { 116, 0xaf, 0x5f, 0x87 },
328 { 117, 0xaf, 0x5f, 0xaf }, { 118, 0xaf, 0x5f, 0xd7 },
329 { 119, 0xaf, 0x5f, 0xff }, { 120, 0xaf, 0x87, 0x00 },
330 { 121, 0xaf, 0x87, 0x5f }, { 122, 0xaf, 0x87, 0x87 },
331 { 123, 0xaf, 0x87, 0xaf }, { 124, 0xaf, 0x87, 0xd7 },
332 { 125, 0xaf, 0x87, 0xff }, { 126, 0xaf, 0xaf, 0x00 },
333 { 127, 0xaf, 0xaf, 0x5f }, { 128, 0xaf, 0xaf, 0x87 },
334 { 129, 0xaf, 0xaf, 0xaf }, { 130, 0xaf, 0xaf, 0xd7 },
335 { 131, 0xaf, 0xaf, 0xff }, { 132, 0xaf, 0xd7, 0x00 },
336 { 133, 0xaf, 0xd7, 0x5f }, { 134, 0xaf, 0xd7, 0x87 },
337 { 135, 0xaf, 0xd7, 0xaf }, { 136, 0xaf, 0xd7, 0xd7 },
338 { 137, 0xaf, 0xd7, 0xff }, { 138, 0xaf, 0xff, 0x00 },
339 { 139, 0xaf, 0xff, 0x5f }, { 140, 0xaf, 0xff, 0x87 },
340 { 141, 0xaf, 0xff, 0xaf }, { 142, 0xaf, 0xff, 0xd7 },
341 { 143, 0xaf, 0xff, 0xff }, { 233, 0xb2, 0xb2, 0xb2 },
342 { 234, 0xbc, 0xbc, 0xbc }, { 235, 0xc6, 0xc6, 0xc6 },
343 { 236, 0xd0, 0xd0, 0xd0 }, { 144, 0xd7, 0x00, 0x00 },
344 { 145, 0xd7, 0x00, 0x5f }, { 146, 0xd7, 0x00, 0x87 },
345 { 147, 0xd7, 0x00, 0xaf }, { 148, 0xd7, 0x00, 0xd7 },
346 { 149, 0xd7, 0x00, 0xff }, { 150, 0xd7, 0x5f, 0x00 },
347 { 151, 0xd7, 0x5f, 0x5f }, { 152, 0xd7, 0x5f, 0x87 },
348 { 153, 0xd7, 0x5f, 0xaf }, { 154, 0xd7, 0x5f, 0xd7 },
349 { 155, 0xd7, 0x5f, 0xff }, { 156, 0xd7, 0x87, 0x00 },
350 { 157, 0xd7, 0x87, 0x5f }, { 158, 0xd7, 0x87, 0x87 },
351 { 159, 0xd7, 0x87, 0xaf }, { 160, 0xd7, 0x87, 0xd7 },
352 { 161, 0xd7, 0x87, 0xff }, { 162, 0xd7, 0xaf, 0x00 },
353 { 163, 0xd7, 0xaf, 0x5f }, { 164, 0xd7, 0xaf, 0x87 },
354 { 165, 0xd7, 0xaf, 0xaf }, { 166, 0xd7, 0xaf, 0xd7 },
355 { 167, 0xd7, 0xaf, 0xff }, { 168, 0xd7, 0xd7, 0x00 },
356 { 169, 0xd7, 0xd7, 0x5f }, { 170, 0xd7, 0xd7, 0x87 },
357 { 171, 0xd7, 0xd7, 0xaf }, { 172, 0xd7, 0xd7, 0xd7 },
358 { 173, 0xd7, 0xd7, 0xff }, { 174, 0xd7, 0xff, 0x00 },
359 { 175, 0xd7, 0xff, 0x5f }, { 176, 0xd7, 0xff, 0x87 },
360 { 177, 0xd7, 0xff, 0xaf }, { 178, 0xd7, 0xff, 0xd7 },
361 { 179, 0xd7, 0xff, 0xff }, { 237, 0xda, 0xda, 0xda },
362 { 238, 0xe4, 0xe4, 0xe4 }, { 239, 0xee, 0xee, 0xee },
363 { 180, 0xff, 0x00, 0x00 }, { 181, 0xff, 0x00, 0x5f },
364 { 182, 0xff, 0x00, 0x87 }, { 183, 0xff, 0x00, 0xaf },
365 { 184, 0xff, 0x00, 0xd7 }, { 185, 0xff, 0x00, 0xff },
366 { 186, 0xff, 0x5f, 0x00 }, { 187, 0xff, 0x5f, 0x5f },
367 { 188, 0xff, 0x5f, 0x87 }, { 189, 0xff, 0x5f, 0xaf },
368 { 190, 0xff, 0x5f, 0xd7 }, { 191, 0xff, 0x5f, 0xff },
369 { 192, 0xff, 0x87, 0x00 }, { 193, 0xff, 0x87, 0x5f },
370 { 194, 0xff, 0x87, 0x87 }, { 195, 0xff, 0x87, 0xaf },
371 { 196, 0xff, 0x87, 0xd7 }, { 197, 0xff, 0x87, 0xff },
372 { 198, 0xff, 0xaf, 0x00 }, { 199, 0xff, 0xaf, 0x5f },
373 { 200, 0xff, 0xaf, 0x87 }, { 201, 0xff, 0xaf, 0xaf },
374 { 202, 0xff, 0xaf, 0xd7 }, { 203, 0xff, 0xaf, 0xff },
375 { 204, 0xff, 0xd7, 0x00 }, { 205, 0xff, 0xd7, 0x5f },
376 { 206, 0xff, 0xd7, 0x87 }, { 207, 0xff, 0xd7, 0xaf },
377 { 208, 0xff, 0xd7, 0xd7 }, { 209, 0xff, 0xd7, 0xff },
378 { 210, 0xff, 0xff, 0x00 }, { 211, 0xff, 0xff, 0x5f },
379 { 212, 0xff, 0xff, 0x87 }, { 213, 0xff, 0xff, 0xaf },
380 { 214, 0xff, 0xff, 0xd7 }, { 215, 0xff, 0xff, 0xff },
383 static const unsigned char color_256_to_16[256] = {
384 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
385 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
386 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
387 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
388 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
389 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
390 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
391 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
392 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
393 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
394 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
395 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
396 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
397 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
398 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
399 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
402 Color rgb = { .r = r, .g = g, .b = b };
403 const Color *found = bsearch(&rgb, color_to_256, LENGTH(color_to_256),
404 sizeof color_to_256[0], color_compare);
406 if (!found) {
407 unsigned lowest = UINT_MAX;
408 found = color_from_256;
409 for (int i = 0; i < 240; i++) {
410 int dr = (int)color_from_256[i].r - r;
411 int dg = (int)color_from_256[i].g - g;
412 int db = (int)color_from_256[i].b - b;
414 unsigned int distance = dr * dr + dg * dg + db * db;
415 if (distance < lowest) {
416 lowest = distance;
417 found = &color_from_256[i];
422 if (COLORS <= 16)
423 return color_256_to_16[found->i + 16];
424 return found->i + 16;
427 /* Convert color from string. */
428 static int color_fromstring(const char *s)
430 if (!s)
431 return -1;
432 if (*s == '#' && strlen(s) == 7) {
433 const char *cp;
434 unsigned char r, g, b;
435 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
436 if (*cp != '\0')
437 return -1;
438 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
439 if (n != 3)
440 return -1;
441 return color_find_rgb(r, g, b);
444 if (strcasecmp(s, "black") == 0)
445 return 0;
446 if (strcasecmp(s, "red") == 0)
447 return 1;
448 if (strcasecmp(s, "green") == 0)
449 return 2;
450 if (strcasecmp(s, "yellow") == 0)
451 return 3;
452 if (strcasecmp(s, "blue") == 0)
453 return 4;
454 if (strcasecmp(s, "magenta") == 0)
455 return 5;
456 if (strcasecmp(s, "cyan") == 0)
457 return 6;
458 if (strcasecmp(s, "white") == 0)
459 return 7;
460 return -1;
463 static inline unsigned int color_pair_hash(short fg, short bg) {
464 if (fg == -1)
465 fg = COLORS;
466 if (bg == -1)
467 bg = COLORS + 1;
468 return fg * (COLORS + 2) + bg;
471 static short color_pair_get(short fg, short bg) {
472 static bool has_default_colors;
473 static short *color2palette, default_fg, default_bg;
474 static short color_pairs_max, color_pair_current;
476 if (!color2palette) {
477 pair_content(0, &default_fg, &default_bg);
478 if (default_fg == -1)
479 default_fg = COLOR_WHITE;
480 if (default_bg == -1)
481 default_bg = COLOR_BLACK;
482 has_default_colors = (use_default_colors() == OK);
483 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
484 if (COLORS)
485 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
488 if (fg >= COLORS)
489 fg = default_fg;
490 if (bg >= COLORS)
491 bg = default_bg;
493 if (!has_default_colors) {
494 if (fg == -1)
495 fg = default_fg;
496 if (bg == -1)
497 bg = default_bg;
500 if (!color2palette || (fg == -1 && bg == -1))
501 return 0;
503 unsigned int index = color_pair_hash(fg, bg);
504 if (color2palette[index] == 0) {
505 short oldfg, oldbg;
506 if (++color_pair_current >= color_pairs_max)
507 color_pair_current = 1;
508 pair_content(color_pair_current, &oldfg, &oldbg);
509 unsigned int old_index = color_pair_hash(oldfg, oldbg);
510 if (init_pair(color_pair_current, fg, bg) == OK) {
511 color2palette[old_index] = 0;
512 color2palette[index] = color_pair_current;
516 return color2palette[index];
519 static inline attr_t style_to_attr(CellStyle *style) {
520 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
523 static bool ui_window_syntax_style(UiWin *w, int id, const char *style) {
524 UiCursesWin *win = (UiCursesWin*)w;
525 if (id >= UI_STYLE_MAX)
526 return false;
527 if (!style)
528 return true;
529 CellStyle cell_style = win->styles[UI_STYLE_DEFAULT];
530 char *style_copy = strdup(style), *option = style_copy, *next, *p;
531 while (option) {
532 if ((next = strchr(option, ',')))
533 *next++ = '\0';
534 if ((p = strchr(option, ':')))
535 *p++ = '\0';
536 if (!strcasecmp(option, "reverse")) {
537 cell_style.attr |= A_REVERSE;
538 } else if (!strcasecmp(option, "bold")) {
539 cell_style.attr |= A_BOLD;
540 } else if (!strcasecmp(option, "notbold")) {
541 cell_style.attr &= ~A_BOLD;
542 #ifdef A_ITALIC
543 } else if (!strcasecmp(option, "italics")) {
544 cell_style.attr |= A_ITALIC;
545 } else if (!strcasecmp(option, "notitalics")) {
546 cell_style.attr &= ~A_ITALIC;
547 #endif
548 } else if (!strcasecmp(option, "underlined")) {
549 cell_style.attr |= A_UNDERLINE;
550 } else if (!strcasecmp(option, "notunderlined")) {
551 cell_style.attr &= ~A_UNDERLINE;
552 } else if (!strcasecmp(option, "fore")) {
553 cell_style.fg = color_fromstring(p);
554 } else if (!strcasecmp(option, "back")) {
555 cell_style.bg = color_fromstring(p);
557 option = next;
559 win->styles[id] = cell_style;
560 free(style_copy);
561 return true;
564 static void ui_window_resize(UiCursesWin *win, int width, int height) {
565 win->width = width;
566 win->height = height;
567 if (win->winstatus)
568 wresize(win->winstatus, 1, width);
569 wresize(win->win, win->winstatus ? height - 1 : height, width - win->sidebar_width);
570 if (win->winside)
571 wresize(win->winside, height-1, win->sidebar_width);
572 view_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height);
575 static void ui_window_move(UiCursesWin *win, int x, int y) {
576 win->x = x;
577 win->y = y;
578 mvwin(win->win, y, x + win->sidebar_width);
579 if (win->winside)
580 mvwin(win->winside, y, x);
581 if (win->winstatus)
582 mvwin(win->winstatus, y + win->height - 1, x);
585 static bool ui_window_draw_sidebar(UiCursesWin *win) {
586 if (!win->winside)
587 return true;
588 const Line *line = view_lines_get(win->view);
589 int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1;
590 if (win->sidebar_width != sidebar_width) {
591 win->sidebar_width = sidebar_width;
592 ui_window_resize(win, win->width, win->height);
593 ui_window_move(win, win->x, win->y);
594 return false;
595 } else {
596 int i = 0;
597 size_t prev_lineno = 0;
598 size_t cursor_lineno = view_cursor_getpos(win->view).line;
599 werase(win->winside);
600 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
601 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
602 for (const Line *l = line; l; l = l->next, i++) {
603 if (l->lineno && l->lineno != prev_lineno) {
604 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
605 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
606 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
607 size_t rel = l->lineno > cursor_lineno ?
608 l->lineno - cursor_lineno :
609 cursor_lineno - l->lineno;
610 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
613 prev_lineno = l->lineno;
615 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
616 return true;
620 static void ui_window_draw_status(UiWin *w) {
621 UiCursesWin *win = (UiCursesWin*)w;
622 if (!win->winstatus)
623 return;
624 UiCurses *uic = win->ui;
625 Vis *vis = uic->vis;
626 bool focused = uic->selwin == win;
627 const char *filename = vis_file_name(win->file);
628 const char *status = vis_mode_status(vis);
629 CursorPos pos = view_cursor_getpos(win->view);
630 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
631 mvwhline(win->winstatus, 0, 0, ' ', win->width);
632 mvwprintw(win->winstatus, 0, 0, "%s %s %s %s",
633 focused && status ? status : "",
634 filename ? filename : "[No Name]",
635 text_modified(vis_file_text(win->file)) ? "[+]" : "",
636 vis_macro_recording(vis) ? "recording": "");
637 char buf[win->width + 1];
638 int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col);
639 if (len > 0) {
640 buf[len] = '\0';
641 mvwaddstr(win->winstatus, 0, win->width - len - 1, buf);
645 static void ui_window_draw(UiWin *w) {
646 UiCursesWin *win = (UiCursesWin*)w;
647 if (!ui_window_draw_sidebar(win))
648 return;
649 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
650 wmove(win->win, 0, 0);
651 int width = view_width_get(win->view);
652 CellStyle *prev_style = NULL;
653 size_t cursor_lineno = -1;
654 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
655 Cursor *cursor = view_cursors(win->view);
656 Filerange selection = view_cursors_selection_get(cursor);
657 if (!view_cursors_next(cursor) && !text_range_valid(&selection))
658 cursor_lineno = view_cursor_getpos(win->view).line;
660 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
661 short cursor_line_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
662 attr_t attr;
663 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
664 bool cursor_line = l->lineno == cursor_lineno;
665 for (int x = 0; x < width; x++) {
666 CellStyle *style = &win->styles[l->cells[x].attr];
667 if (l->cells[x].cursor && (win->ui->selwin == win || win->ui->prompt_win == win)) {
668 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
669 prev_style = NULL;
670 } else if (l->cells[x].selected) {
671 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
672 prev_style = NULL;
673 } else if (cursor_line) {
674 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, cursor_line_bg));
675 prev_style = NULL;
676 } else if (style != prev_style) {
677 attr = style_to_attr(style);
678 prev_style = style;
680 wattrset(win->win, attr);
681 waddstr(win->win, l->cells[x].data);
683 /* try to fixup display issues, in theory we should always output a full line */
684 int x, y;
685 getyx(win->win, y, x);
686 (void)y;
687 wattrset(win->win, A_NORMAL);
688 for (; 0 < x && x < width; x++)
689 waddstr(win->win, " ");
691 wclrtobot(win->win);
693 if (win->winstatus)
694 ui_window_draw_status(w);
697 static void ui_window_reload(UiWin *w, File *file) {
698 UiCursesWin *win = (UiCursesWin*)w;
699 win->file = file;
700 win->sidebar_width = 0;
701 view_reload(win->view, vis_file_text(file));
702 ui_window_draw(w);
705 static void ui_window_update(UiCursesWin *win) {
706 if (win->winstatus)
707 wnoutrefresh(win->winstatus);
708 if (win->winside)
709 wnoutrefresh(win->winside);
710 wnoutrefresh(win->win);
713 static void ui_arrange(Ui *ui, enum UiLayout layout) {
714 UiCurses *uic = (UiCurses*)ui;
715 uic->layout = layout;
716 int n = 0, x = 0, y = 0;
717 for (UiCursesWin *win = uic->windows; win; win = win->next)
718 n++;
719 int max_height = uic->height - !!(uic->prompt_title[0] || uic->info[0]);
720 int width = (uic->width / MAX(1, n)) - 1;
721 int height = max_height / MAX(1, n);
722 for (UiCursesWin *win = uic->windows; win; win = win->next) {
723 if (layout == UI_LAYOUT_HORIZONTAL) {
724 ui_window_resize(win, uic->width, win->next ? height : max_height - y);
725 ui_window_move(win, x, y);
726 y += height;
727 } else {
728 ui_window_resize(win, win->next ? width : uic->width - x, max_height);
729 ui_window_move(win, x, y);
730 x += width;
731 if (win->next)
732 mvvline(0, x++, ACS_VLINE, max_height);
737 static void ui_draw(Ui *ui) {
738 UiCurses *uic = (UiCurses*)ui;
739 erase();
740 ui_arrange(ui, uic->layout);
742 for (UiCursesWin *win = uic->windows; win; win = win->next)
743 ui_window_draw((UiWin*)win);
745 if (uic->info[0]) {
746 attrset(A_BOLD);
747 mvaddstr(uic->height-1, 0, uic->info);
750 if (uic->prompt_title[0]) {
751 attrset(A_NORMAL);
752 mvaddstr(uic->height-1, 0, uic->prompt_title);
753 ui_window_draw((UiWin*)uic->prompt_win);
756 wnoutrefresh(stdscr);
759 static void ui_resize_to(Ui *ui, int width, int height) {
760 UiCurses *uic = (UiCurses*)ui;
761 uic->width = width;
762 uic->height = height;
763 if (uic->prompt_title[0]) {
764 size_t title_width = strlen(uic->prompt_title);
765 ui_window_resize(uic->prompt_win, width - title_width, 1);
766 ui_window_move(uic->prompt_win, title_width, height-1);
768 ui_draw(ui);
771 static void ui_resize(Ui *ui) {
772 struct winsize ws;
773 int width, height;
775 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
776 getmaxyx(stdscr, height, width);
777 } else {
778 width = ws.ws_col;
779 height = ws.ws_row;
782 resizeterm(height, width);
783 wresize(stdscr, height, width);
784 ui_resize_to(ui, width, height);
787 static void ui_update(Ui *ui) {
788 UiCurses *uic = (UiCurses*)ui;
789 if (need_resize) {
790 ui_resize(ui);
791 need_resize = false;
793 for (UiCursesWin *win = uic->windows; win; win = win->next) {
794 if (win != uic->selwin)
795 ui_window_update(win);
798 if (uic->selwin)
799 ui_window_update(uic->selwin);
800 if (uic->prompt_title[0]) {
801 wnoutrefresh(uic->prompt_win->win);
802 ui_window_update(uic->prompt_win);
804 doupdate();
807 static void ui_window_free(UiWin *w) {
808 UiCursesWin *win = (UiCursesWin*)w;
809 if (!win)
810 return;
811 UiCurses *uic = win->ui;
812 if (win->prev)
813 win->prev->next = win->next;
814 if (win->next)
815 win->next->prev = win->prev;
816 if (uic->windows == win)
817 uic->windows = win->next;
818 if (uic->selwin == win)
819 uic->selwin = NULL;
820 win->next = win->prev = NULL;
821 if (win->winstatus)
822 delwin(win->winstatus);
823 if (win->winside)
824 delwin(win->winside);
825 if (win->win)
826 delwin(win->win);
827 free(win);
830 static void ui_window_focus(UiWin *w) {
831 UiCursesWin *win = (UiCursesWin*)w;
832 UiCursesWin *oldsel = win->ui->selwin;
833 win->ui->selwin = win;
834 if (oldsel)
835 ui_window_draw((UiWin*)oldsel);
836 ui_window_draw(w);
839 static void ui_window_options_set(UiWin *w, enum UiOption options) {
840 UiCursesWin *win = (UiCursesWin*)w;
841 win->options = options;
842 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
843 if (!win->winside)
844 win->winside = newwin(1, 1, 1, 1);
845 } else {
846 if (win->winside) {
847 delwin(win->winside);
848 win->winside = NULL;
849 win->sidebar_width = 0;
852 ui_window_draw(w);
855 static enum UiOption ui_window_options_get(UiWin *w) {
856 UiCursesWin *win = (UiCursesWin*)w;
857 return win->options;
860 static UiWin *ui_window_new(Ui *ui, View *view, File *file) {
861 UiCurses *uic = (UiCurses*)ui;
862 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
863 if (!win)
864 return NULL;
866 win->uiwin = (UiWin) {
867 .draw = ui_window_draw,
868 .draw_status = ui_window_draw_status,
869 .options_set = ui_window_options_set,
870 .options_get = ui_window_options_get,
871 .reload = ui_window_reload,
872 .syntax_style = ui_window_syntax_style,
875 if (!(win->win = newwin(0, 0, 0, 0)) || !(win->winstatus = newwin(1, 0, 0, 0))) {
876 ui_window_free((UiWin*)win);
877 return NULL;
880 CellStyle style = (CellStyle) {
881 .fg = -1, .bg = -1, .attr = A_NORMAL,
884 for (int i = 0; i < UI_STYLE_MAX; i++) {
885 win->styles[i] = style;
888 style.attr |= A_REVERSE;
889 win->styles[UI_STYLE_CURSOR] = style;
890 win->styles[UI_STYLE_SELECTION] = style;
892 win->ui = uic;
893 win->view = view;
894 win->file = file;
895 view_ui(view, &win->uiwin);
897 if (uic->windows)
898 uic->windows->prev = win;
899 win->next = uic->windows;
900 uic->windows = win;
902 return &win->uiwin;
905 static void ui_die(Ui *ui, const char *msg, va_list ap) {
906 UiCurses *uic = (UiCurses*)ui;
907 endwin();
908 if (uic->termkey)
909 termkey_stop(uic->termkey);
910 vfprintf(stderr, msg, ap);
911 exit(EXIT_FAILURE);
914 static void ui_info(Ui *ui, const char *msg, va_list ap) {
915 UiCurses *uic = (UiCurses*)ui;
916 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
917 ui_draw(ui);
920 static void ui_info_hide(Ui *ui) {
921 UiCurses *uic = (UiCurses*)ui;
922 if (uic->info[0]) {
923 uic->info[0] = '\0';
924 ui_draw(ui);
928 static UiWin *ui_prompt_new(Ui *ui, View *view, File *file) {
929 UiCurses *uic = (UiCurses*)ui;
930 if (uic->prompt_win)
931 return (UiWin*)uic->prompt_win;
932 UiWin *uiwin = ui_window_new(ui, view, file);
933 UiCursesWin *win = (UiCursesWin*)uiwin;
934 if (!win)
935 return NULL;
936 uic->windows = win->next;
937 if (uic->windows)
938 uic->windows->prev = NULL;
939 if (win->winstatus)
940 delwin(win->winstatus);
941 if (win->winside)
942 delwin(win->winside);
943 win->winstatus = NULL;
944 win->winside = NULL;
945 uic->prompt_win = win;
946 return uiwin;
949 static void ui_prompt(Ui *ui, const char *title, const char *data) {
950 UiCurses *uic = (UiCurses*)ui;
951 if (uic->prompt_title[0])
952 return;
953 size_t len = strlen(data);
954 Text *text = vis_file_text(uic->prompt_win->file);
955 strncpy(uic->prompt_title, title, sizeof(uic->prompt_title)-1);
956 while (text_undo(text) != EPOS);
957 text_insert(text, 0, data, len);
958 view_cursor_to(uic->prompt_win->view, 0);
959 ui_resize_to(ui, uic->width, uic->height);
960 view_cursor_to(uic->prompt_win->view, len);
963 static char *ui_prompt_input(Ui *ui) {
964 UiCurses *uic = (UiCurses*)ui;
965 if (!uic->prompt_win)
966 return NULL;
967 Text *text = vis_file_text(uic->prompt_win->file);
968 char *buf = malloc(text_size(text) + 1);
969 if (!buf)
970 return NULL;
971 size_t len = text_bytes_get(text, 0, text_size(text), buf);
972 buf[len] = '\0';
973 return buf;
976 static void ui_prompt_hide(Ui *ui) {
977 UiCurses *uic = (UiCurses*)ui;
978 uic->prompt_title[0] = '\0';
979 ui_resize_to(ui, uic->width, uic->height);
982 static bool ui_init(Ui *ui, Vis *vis) {
983 UiCurses *uic = (UiCurses*)ui;
984 uic->vis = vis;
985 return true;
988 static TermKey *ui_termkey_get(Ui *ui) {
989 UiCurses *uic = (UiCurses*)ui;
990 return uic->termkey;
993 static void ui_suspend(Ui *ui) {
994 endwin();
995 raise(SIGSTOP);
998 static bool ui_haskey(Ui *ui) {
999 nodelay(stdscr, TRUE);
1000 int c = getch();
1001 if (c != ERR)
1002 ungetch(c);
1003 nodelay(stdscr, FALSE);
1004 return c != ERR;
1007 static const char *ui_getkey(Ui *ui) {
1008 UiCurses *uic = (UiCurses*)ui;
1009 TermKeyKey key;
1010 TermKeyResult ret = termkey_getkey(uic->termkey, &key);
1012 if (ret == TERMKEY_RES_AGAIN) {
1013 struct pollfd fd;
1014 fd.fd = STDIN_FILENO;
1015 fd.events = POLLIN;
1016 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
1017 ret = termkey_getkey_force(uic->termkey, &key);
1020 if (ret != TERMKEY_RES_KEY)
1021 return NULL;
1022 termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM);
1023 return uic->key;
1026 static void ui_terminal_save(Ui *ui) {
1027 UiCurses *uic = (UiCurses*)ui;
1028 curs_set(1);
1029 reset_shell_mode();
1030 termkey_stop(uic->termkey);
1033 static void ui_terminal_restore(Ui *ui) {
1034 UiCurses *uic = (UiCurses*)ui;
1035 termkey_start(uic->termkey);
1036 reset_prog_mode();
1037 wclear(stdscr);
1038 curs_set(0);
1041 Ui *ui_curses_new(void) {
1043 UiCurses *uic = calloc(1, sizeof(UiCurses));
1044 Ui *ui = (Ui*)uic;
1045 if (!uic)
1046 return NULL;
1047 if (!(uic->termkey = termkey_new(STDIN_FILENO, TERMKEY_FLAG_UTF8)))
1048 goto err;
1049 termkey_set_canonflags(uic->termkey, TERMKEY_CANON_DELBS);
1050 setlocale(LC_CTYPE, "");
1051 if (!getenv("ESCDELAY"))
1052 set_escdelay(50);
1053 char *term = getenv("TERM");
1054 if (!term)
1055 term = "xterm";
1056 if (!newterm(term, stderr, stdin))
1057 goto err;
1058 start_color();
1059 use_default_colors();
1060 raw();
1061 noecho();
1062 nonl();
1063 keypad(stdscr, TRUE);
1064 meta(stdscr, TRUE);
1065 curs_set(0);
1066 /* needed because we use getch() which implicitly calls refresh() which
1067 would clear the screen (overwrite it with an empty / unused stdscr */
1068 refresh();
1070 *ui = (Ui) {
1071 .init = ui_init,
1072 .free = ui_curses_free,
1073 .termkey_get = ui_termkey_get,
1074 .suspend = ui_suspend,
1075 .resize = ui_resize,
1076 .update = ui_update,
1077 .window_new = ui_window_new,
1078 .window_free = ui_window_free,
1079 .window_focus = ui_window_focus,
1080 .prompt_new = ui_prompt_new,
1081 .prompt = ui_prompt,
1082 .prompt_input = ui_prompt_input,
1083 .prompt_hide = ui_prompt_hide,
1084 .draw = ui_draw,
1085 .arrange = ui_arrange,
1086 .die = ui_die,
1087 .info = ui_info,
1088 .info_hide = ui_info_hide,
1089 .haskey = ui_haskey,
1090 .getkey = ui_getkey,
1091 .terminal_save = ui_terminal_save,
1092 .terminal_restore = ui_terminal_restore,
1095 struct sigaction sa;
1096 sa.sa_flags = 0;
1097 sigemptyset(&sa.sa_mask);
1098 sa.sa_handler = sigwinch_handler;
1099 sigaction(SIGWINCH, &sa, NULL);
1100 sigaction(SIGCONT, &sa, NULL);
1102 ui_resize(ui);
1104 return ui;
1105 err:
1106 ui_curses_free(ui);
1107 return NULL;
1110 void ui_curses_free(Ui *ui) {
1111 UiCurses *uic = (UiCurses*)ui;
1112 if (!uic)
1113 return;
1114 ui_window_free((UiWin*)uic->prompt_win);
1115 while (uic->windows)
1116 ui_window_free((UiWin*)uic->windows);
1117 endwin();
1118 if (uic->termkey)
1119 termkey_destroy(uic->termkey);
1120 free(uic);