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