ui: make default selection visible
[vis.git] / ui-curses.c
blobaf5057ea57b97aaafc9d2200f536618238c08866
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);
573 view_update(win->view);
576 static void ui_window_move(UiCursesWin *win, int x, int y) {
577 win->x = x;
578 win->y = y;
579 mvwin(win->win, y, x + win->sidebar_width);
580 if (win->winside)
581 mvwin(win->winside, y, x);
582 if (win->winstatus)
583 mvwin(win->winstatus, y + win->height - 1, x);
586 static bool ui_window_draw_sidebar(UiCursesWin *win) {
587 if (!win->winside)
588 return true;
589 const Line *line = view_lines_get(win->view);
590 int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1;
591 if (win->sidebar_width != sidebar_width) {
592 win->sidebar_width = sidebar_width;
593 ui_window_resize(win, win->width, win->height);
594 ui_window_move(win, win->x, win->y);
595 return false;
596 } else {
597 int i = 0;
598 size_t prev_lineno = 0;
599 size_t cursor_lineno = view_cursor_getpos(win->view).line;
600 werase(win->winside);
601 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
602 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
603 for (const Line *l = line; l; l = l->next, i++) {
604 if (l->lineno && l->lineno != prev_lineno) {
605 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
606 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
607 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
608 size_t rel = l->lineno > cursor_lineno ?
609 l->lineno - cursor_lineno :
610 cursor_lineno - l->lineno;
611 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
614 prev_lineno = l->lineno;
616 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
617 return true;
621 static void ui_window_draw_status(UiWin *w) {
622 UiCursesWin *win = (UiCursesWin*)w;
623 if (!win->winstatus)
624 return;
625 UiCurses *uic = win->ui;
626 Vis *vis = uic->vis;
627 bool focused = uic->selwin == win;
628 const char *filename = vis_file_name(win->file);
629 const char *status = vis_mode_status(vis);
630 CursorPos pos = view_cursor_getpos(win->view);
631 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
632 mvwhline(win->winstatus, 0, 0, ' ', win->width);
633 mvwprintw(win->winstatus, 0, 0, "%s %s %s %s",
634 focused && status ? status : "",
635 filename ? filename : "[No Name]",
636 text_modified(vis_file_text(win->file)) ? "[+]" : "",
637 vis_macro_recording(vis) ? "recording": "");
638 char buf[win->width + 1];
639 int len = snprintf(buf, win->width, "%zd, %zd", pos.line, pos.col);
640 if (len > 0) {
641 buf[len] = '\0';
642 mvwaddstr(win->winstatus, 0, win->width - len - 1, buf);
646 static void ui_window_draw(UiWin *w) {
647 UiCursesWin *win = (UiCursesWin*)w;
648 if (!ui_window_draw_sidebar(win))
649 return;
650 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
651 wmove(win->win, 0, 0);
652 int width = view_width_get(win->view);
653 CellStyle *prev_style = NULL;
654 size_t cursor_lineno = -1;
655 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
656 Cursor *cursor = view_cursors(win->view);
657 Filerange selection = view_cursors_selection_get(cursor);
658 if (!view_cursors_next(cursor) && !text_range_valid(&selection))
659 cursor_lineno = view_cursor_getpos(win->view).line;
661 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
662 short cursor_line_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
663 attr_t attr;
664 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
665 bool cursor_line = l->lineno == cursor_lineno;
666 for (int x = 0; x < width; x++) {
667 CellStyle *style = &win->styles[l->cells[x].attr];
668 if (l->cells[x].cursor && (win->ui->selwin == win || win->ui->prompt_win == win)) {
669 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
670 prev_style = NULL;
671 } else if (l->cells[x].selected) {
672 if (style->fg == selection_bg)
673 attr = style_to_attr(&win->styles[UI_STYLE_SELECTION]);
674 else
675 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
676 prev_style = NULL;
677 } else if (cursor_line) {
678 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, cursor_line_bg));
679 prev_style = NULL;
680 } else if (style != prev_style) {
681 attr = style_to_attr(style);
682 prev_style = style;
684 wattrset(win->win, attr);
685 waddstr(win->win, l->cells[x].data);
687 /* try to fixup display issues, in theory we should always output a full line */
688 int x, y;
689 getyx(win->win, y, x);
690 (void)y;
691 wattrset(win->win, A_NORMAL);
692 for (; 0 < x && x < width; x++)
693 waddstr(win->win, " ");
695 wclrtobot(win->win);
697 if (win->winstatus)
698 ui_window_draw_status(w);
701 static void ui_window_reload(UiWin *w, File *file) {
702 UiCursesWin *win = (UiCursesWin*)w;
703 win->file = file;
704 win->sidebar_width = 0;
705 view_reload(win->view, vis_file_text(file));
706 ui_window_draw(w);
709 static void ui_window_update(UiCursesWin *win) {
710 if (win->winstatus)
711 wnoutrefresh(win->winstatus);
712 if (win->winside)
713 wnoutrefresh(win->winside);
714 wnoutrefresh(win->win);
717 static void ui_arrange(Ui *ui, enum UiLayout layout) {
718 UiCurses *uic = (UiCurses*)ui;
719 uic->layout = layout;
720 int n = 0, x = 0, y = 0;
721 for (UiCursesWin *win = uic->windows; win; win = win->next)
722 n++;
723 int max_height = uic->height - !!(uic->prompt_title[0] || uic->info[0]);
724 int width = (uic->width / MAX(1, n)) - 1;
725 int height = max_height / MAX(1, n);
726 for (UiCursesWin *win = uic->windows; win; win = win->next) {
727 if (layout == UI_LAYOUT_HORIZONTAL) {
728 ui_window_resize(win, uic->width, win->next ? height : max_height - y);
729 ui_window_move(win, x, y);
730 y += height;
731 } else {
732 ui_window_resize(win, win->next ? width : uic->width - x, max_height);
733 ui_window_move(win, x, y);
734 x += width;
735 if (win->next)
736 mvvline(0, x++, ACS_VLINE, max_height);
741 static void ui_draw(Ui *ui) {
742 UiCurses *uic = (UiCurses*)ui;
743 erase();
744 ui_arrange(ui, uic->layout);
746 for (UiCursesWin *win = uic->windows; win; win = win->next)
747 ui_window_draw((UiWin*)win);
749 if (uic->info[0]) {
750 attrset(A_BOLD);
751 mvaddstr(uic->height-1, 0, uic->info);
754 if (uic->prompt_title[0]) {
755 attrset(A_NORMAL);
756 mvaddstr(uic->height-1, 0, uic->prompt_title);
757 ui_window_draw((UiWin*)uic->prompt_win);
760 wnoutrefresh(stdscr);
763 static void ui_resize_to(Ui *ui, int width, int height) {
764 UiCurses *uic = (UiCurses*)ui;
765 uic->width = width;
766 uic->height = height;
767 if (uic->prompt_title[0]) {
768 size_t title_width = strlen(uic->prompt_title);
769 ui_window_resize(uic->prompt_win, width - title_width, 1);
770 ui_window_move(uic->prompt_win, title_width, height-1);
772 ui_draw(ui);
775 static void ui_resize(Ui *ui) {
776 struct winsize ws;
777 int width, height;
779 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
780 getmaxyx(stdscr, height, width);
781 } else {
782 width = ws.ws_col;
783 height = ws.ws_row;
786 resizeterm(height, width);
787 wresize(stdscr, height, width);
788 ui_resize_to(ui, width, height);
791 static void ui_update(Ui *ui) {
792 UiCurses *uic = (UiCurses*)ui;
793 if (need_resize) {
794 ui_resize(ui);
795 need_resize = false;
797 for (UiCursesWin *win = uic->windows; win; win = win->next) {
798 if (win != uic->selwin)
799 ui_window_update(win);
802 if (uic->selwin)
803 ui_window_update(uic->selwin);
804 if (uic->prompt_title[0]) {
805 wnoutrefresh(uic->prompt_win->win);
806 ui_window_update(uic->prompt_win);
808 doupdate();
811 static void ui_window_free(UiWin *w) {
812 UiCursesWin *win = (UiCursesWin*)w;
813 if (!win)
814 return;
815 UiCurses *uic = win->ui;
816 if (win->prev)
817 win->prev->next = win->next;
818 if (win->next)
819 win->next->prev = win->prev;
820 if (uic->windows == win)
821 uic->windows = win->next;
822 if (uic->selwin == win)
823 uic->selwin = NULL;
824 win->next = win->prev = NULL;
825 if (win->winstatus)
826 delwin(win->winstatus);
827 if (win->winside)
828 delwin(win->winside);
829 if (win->win)
830 delwin(win->win);
831 free(win);
834 static void ui_window_focus(UiWin *w) {
835 UiCursesWin *win = (UiCursesWin*)w;
836 UiCursesWin *oldsel = win->ui->selwin;
837 win->ui->selwin = win;
838 if (oldsel)
839 ui_window_draw((UiWin*)oldsel);
840 ui_window_draw(w);
843 static void ui_window_options_set(UiWin *w, enum UiOption options) {
844 UiCursesWin *win = (UiCursesWin*)w;
845 win->options = options;
846 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
847 if (!win->winside)
848 win->winside = newwin(1, 1, 1, 1);
849 } else {
850 if (win->winside) {
851 delwin(win->winside);
852 win->winside = NULL;
853 win->sidebar_width = 0;
856 ui_window_draw(w);
859 static enum UiOption ui_window_options_get(UiWin *w) {
860 UiCursesWin *win = (UiCursesWin*)w;
861 return win->options;
864 static UiWin *ui_window_new(Ui *ui, View *view, File *file) {
865 UiCurses *uic = (UiCurses*)ui;
866 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
867 if (!win)
868 return NULL;
870 win->uiwin = (UiWin) {
871 .draw = ui_window_draw,
872 .draw_status = ui_window_draw_status,
873 .options_set = ui_window_options_set,
874 .options_get = ui_window_options_get,
875 .reload = ui_window_reload,
876 .syntax_style = ui_window_syntax_style,
879 if (!(win->win = newwin(0, 0, 0, 0)) || !(win->winstatus = newwin(1, 0, 0, 0))) {
880 ui_window_free((UiWin*)win);
881 return NULL;
884 CellStyle style = (CellStyle) {
885 .fg = -1, .bg = -1, .attr = A_NORMAL,
888 for (int i = 0; i < UI_STYLE_MAX; i++) {
889 win->styles[i] = style;
892 style.attr |= A_REVERSE;
893 win->styles[UI_STYLE_CURSOR] = style;
894 win->styles[UI_STYLE_SELECTION] = style;
896 win->ui = uic;
897 win->view = view;
898 win->file = file;
899 view_ui(view, &win->uiwin);
901 if (uic->windows)
902 uic->windows->prev = win;
903 win->next = uic->windows;
904 uic->windows = win;
906 return &win->uiwin;
909 static void ui_die(Ui *ui, const char *msg, va_list ap) {
910 UiCurses *uic = (UiCurses*)ui;
911 endwin();
912 if (uic->termkey)
913 termkey_stop(uic->termkey);
914 vfprintf(stderr, msg, ap);
915 exit(EXIT_FAILURE);
918 static void ui_info(Ui *ui, const char *msg, va_list ap) {
919 UiCurses *uic = (UiCurses*)ui;
920 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
921 ui_draw(ui);
924 static void ui_info_hide(Ui *ui) {
925 UiCurses *uic = (UiCurses*)ui;
926 if (uic->info[0]) {
927 uic->info[0] = '\0';
928 ui_draw(ui);
932 static UiWin *ui_prompt_new(Ui *ui, View *view, File *file) {
933 UiCurses *uic = (UiCurses*)ui;
934 if (uic->prompt_win)
935 return (UiWin*)uic->prompt_win;
936 UiWin *uiwin = ui_window_new(ui, view, file);
937 UiCursesWin *win = (UiCursesWin*)uiwin;
938 if (!win)
939 return NULL;
940 uic->windows = win->next;
941 if (uic->windows)
942 uic->windows->prev = NULL;
943 if (win->winstatus)
944 delwin(win->winstatus);
945 if (win->winside)
946 delwin(win->winside);
947 win->winstatus = NULL;
948 win->winside = NULL;
949 uic->prompt_win = win;
950 return uiwin;
953 static void ui_prompt(Ui *ui, const char *title, const char *data) {
954 UiCurses *uic = (UiCurses*)ui;
955 if (uic->prompt_title[0])
956 return;
957 size_t len = strlen(data);
958 Text *text = vis_file_text(uic->prompt_win->file);
959 strncpy(uic->prompt_title, title, sizeof(uic->prompt_title)-1);
960 while (text_undo(text) != EPOS);
961 text_insert(text, 0, data, len);
962 view_cursor_to(uic->prompt_win->view, 0);
963 ui_resize_to(ui, uic->width, uic->height);
964 view_cursor_to(uic->prompt_win->view, len);
967 static char *ui_prompt_input(Ui *ui) {
968 UiCurses *uic = (UiCurses*)ui;
969 if (!uic->prompt_win)
970 return NULL;
971 Text *text = vis_file_text(uic->prompt_win->file);
972 char *buf = malloc(text_size(text) + 1);
973 if (!buf)
974 return NULL;
975 size_t len = text_bytes_get(text, 0, text_size(text), buf);
976 buf[len] = '\0';
977 return buf;
980 static void ui_prompt_hide(Ui *ui) {
981 UiCurses *uic = (UiCurses*)ui;
982 uic->prompt_title[0] = '\0';
983 ui_resize_to(ui, uic->width, uic->height);
986 static bool ui_init(Ui *ui, Vis *vis) {
987 UiCurses *uic = (UiCurses*)ui;
988 uic->vis = vis;
989 return true;
992 static TermKey *ui_termkey_get(Ui *ui) {
993 UiCurses *uic = (UiCurses*)ui;
994 return uic->termkey;
997 static void ui_suspend(Ui *ui) {
998 endwin();
999 raise(SIGSTOP);
1002 static bool ui_haskey(Ui *ui) {
1003 nodelay(stdscr, TRUE);
1004 int c = getch();
1005 if (c != ERR)
1006 ungetch(c);
1007 nodelay(stdscr, FALSE);
1008 return c != ERR;
1011 static const char *ui_getkey(Ui *ui) {
1012 UiCurses *uic = (UiCurses*)ui;
1013 TermKeyKey key;
1014 TermKeyResult ret = termkey_getkey(uic->termkey, &key);
1016 if (ret == TERMKEY_RES_AGAIN) {
1017 struct pollfd fd;
1018 fd.fd = STDIN_FILENO;
1019 fd.events = POLLIN;
1020 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
1021 ret = termkey_getkey_force(uic->termkey, &key);
1024 if (ret != TERMKEY_RES_KEY)
1025 return NULL;
1026 termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM);
1027 return uic->key;
1030 static void ui_terminal_save(Ui *ui) {
1031 UiCurses *uic = (UiCurses*)ui;
1032 curs_set(1);
1033 reset_shell_mode();
1034 termkey_stop(uic->termkey);
1037 static void ui_terminal_restore(Ui *ui) {
1038 UiCurses *uic = (UiCurses*)ui;
1039 termkey_start(uic->termkey);
1040 reset_prog_mode();
1041 wclear(stdscr);
1042 curs_set(0);
1045 Ui *ui_curses_new(void) {
1047 UiCurses *uic = calloc(1, sizeof(UiCurses));
1048 Ui *ui = (Ui*)uic;
1049 if (!uic)
1050 return NULL;
1051 if (!(uic->termkey = termkey_new(STDIN_FILENO, TERMKEY_FLAG_UTF8)))
1052 goto err;
1053 termkey_set_canonflags(uic->termkey, TERMKEY_CANON_DELBS);
1054 setlocale(LC_CTYPE, "");
1055 if (!getenv("ESCDELAY"))
1056 set_escdelay(50);
1057 char *term = getenv("TERM");
1058 if (!term)
1059 term = "xterm";
1060 if (!newterm(term, stderr, stdin))
1061 goto err;
1062 start_color();
1063 use_default_colors();
1064 raw();
1065 noecho();
1066 nonl();
1067 keypad(stdscr, TRUE);
1068 meta(stdscr, TRUE);
1069 curs_set(0);
1070 /* needed because we use getch() which implicitly calls refresh() which
1071 would clear the screen (overwrite it with an empty / unused stdscr */
1072 refresh();
1074 *ui = (Ui) {
1075 .init = ui_init,
1076 .free = ui_curses_free,
1077 .termkey_get = ui_termkey_get,
1078 .suspend = ui_suspend,
1079 .resize = ui_resize,
1080 .update = ui_update,
1081 .window_new = ui_window_new,
1082 .window_free = ui_window_free,
1083 .window_focus = ui_window_focus,
1084 .prompt_new = ui_prompt_new,
1085 .prompt = ui_prompt,
1086 .prompt_input = ui_prompt_input,
1087 .prompt_hide = ui_prompt_hide,
1088 .draw = ui_draw,
1089 .arrange = ui_arrange,
1090 .die = ui_die,
1091 .info = ui_info,
1092 .info_hide = ui_info_hide,
1093 .haskey = ui_haskey,
1094 .getkey = ui_getkey,
1095 .terminal_save = ui_terminal_save,
1096 .terminal_restore = ui_terminal_restore,
1099 struct sigaction sa;
1100 sa.sa_flags = 0;
1101 sigemptyset(&sa.sa_mask);
1102 sa.sa_handler = sigwinch_handler;
1103 sigaction(SIGWINCH, &sa, NULL);
1104 sigaction(SIGCONT, &sa, NULL);
1106 ui_resize(ui);
1108 return ui;
1109 err:
1110 ui_curses_free(ui);
1111 return NULL;
1114 void ui_curses_free(Ui *ui) {
1115 UiCurses *uic = (UiCurses*)ui;
1116 if (!uic)
1117 return;
1118 ui_window_free((UiWin*)uic->prompt_win);
1119 while (uic->windows)
1120 ui_window_free((UiWin*)uic->windows);
1121 endwin();
1122 if (uic->termkey)
1123 termkey_destroy(uic->termkey);
1124 free(uic);