man: remove authors section from tool's man pages
[vis.git] / ui-curses.c
blob94734d56e0571f24e12c872bab628443141ccd0e
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <limits.h>
6 #include <ctype.h>
7 #include <locale.h>
8 #include <poll.h>
9 #include <sys/ioctl.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <fcntl.h>
13 #include <termios.h>
14 #include <errno.h>
16 #include "ui-curses.h"
17 #include "vis.h"
18 #include "vis-core.h"
19 #include "text.h"
20 #include "util.h"
21 #include "text-util.h"
23 #ifdef NCURSES_VERSION
24 # ifndef NCURSES_EXT_COLORS
25 # define NCURSES_EXT_COLORS 0
26 # endif
27 # if !NCURSES_EXT_COLORS
28 # define MAX_COLOR_PAIRS 256
29 # endif
30 #endif
31 #ifndef MAX_COLOR_PAIRS
32 # define MAX_COLOR_PAIRS COLOR_PAIRS
33 #endif
35 #ifndef DEBUG_UI
36 #define DEBUG_UI 0
37 #endif
39 #if DEBUG_UI
40 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
41 #else
42 #define debug(...) do { } while (0)
43 #endif
45 #if 0
46 #define wresize(win, y, x) do { \
47 if (wresize(win, y, x) == ERR) { \
48 printf("ERROR resizing: %d x %d\n", x, y); \
49 } else { \
50 printf("OK resizing: %d x %d\n", x, y); \
51 } \
52 fflush(stdout); \
53 } while (0);
55 #define mvwin(win, y, x) do { \
56 if (mvwin(win, y, x) == ERR) { \
57 printf("ERROR moving: %d x %d\n", x, y); \
58 } else { \
59 printf("OK moving: %d x %d\n", x, y); \
60 } \
61 fflush(stdout); \
62 } while (0);
63 #endif
65 #define MAX_COLOR_CLOBBER 240
66 static short color_clobber_idx = 0;
67 static uint32_t clobbering_colors[MAX_COLOR_CLOBBER];
68 static bool change_colors;
70 typedef struct {
71 attr_t attr;
72 short fg, bg;
73 } CellStyle;
75 typedef struct UiCursesWin UiCursesWin;
77 typedef struct {
78 Ui ui; /* generic ui interface, has to be the first struct member */
79 Vis *vis; /* editor instance to which this ui belongs */
80 UiCursesWin *windows; /* all windows managed by this ui */
81 UiCursesWin *selwin; /* the currently selected layout */
82 char info[512]; /* info message displayed at the bottom of the screen */
83 int width, height; /* terminal dimensions available for all windows */
84 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
85 TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */
86 } UiCurses;
88 struct UiCursesWin {
89 UiWin uiwin; /* generic interface, has to be the first struct member */
90 UiCurses *ui; /* ui which manages this window */
91 File *file; /* file being displayed in this window */
92 View *view; /* current viewport */
93 WINDOW *win; /* curses window for the text area */
94 WINDOW *winstatus; /* curses window for the status bar */
95 WINDOW *winside; /* curses window for the side bar (line numbers) */
96 int width, height; /* window dimension including status bar */
97 int x, y; /* window position */
98 int sidebar_width; /* width of the sidebar showing line numbers etc. */
99 UiCursesWin *next, *prev; /* pointers to neighbouring windows */
100 enum UiOption options; /* display settings for this window */
101 CellStyle styles[UI_STYLE_MAX];
104 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
105 UiCurses *uic = (UiCurses*)ui;
106 endwin();
107 if (uic->termkey)
108 termkey_stop(uic->termkey);
109 vfprintf(stderr, msg, ap);
110 exit(EXIT_FAILURE);
113 __attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) {
114 va_list ap;
115 va_start(ap, msg);
116 ui_die(ui, msg, ap);
117 va_end(ap);
120 /* Calculate r,g,b components of one of the standard upper 240 colors */
121 static void get_6cube_rgb(unsigned int n, int *r, int *g, int *b)
123 if (n < 16) {
124 return;
125 } else if (n < 232) {
126 n -= 16;
127 *r = (n / 36) ? (n / 36) * 40 + 55 : 0;
128 *g = ((n / 6) % 6) ? ((n / 6) % 6) * 40 + 55 : 0;
129 *b = (n % 6) ? (n % 6) * 40 + 55 : 0;
130 } else if (n < 256) {
131 n -= 232;
132 *r = n * 10 + 8;
133 *g = n * 10 + 8;
134 *b = n * 10 + 8;
138 /* Reset color palette to default values using OSC 104 */
139 static void undo_palette(void)
141 fputs("\033]104;\a", stderr);
142 fflush(stderr);
145 /* Work out the nearest color from the 256 color set, or perhaps exactly. */
146 static int color_find_rgb(unsigned char r, unsigned char g, unsigned char b)
148 if (change_colors) {
149 uint32_t hexrep = ((r << 16) | (g << 8) | b) + 1;
150 for (short i = 0; i < MAX_COLOR_CLOBBER; ++i) {
151 if (clobbering_colors[i] == hexrep)
152 return i + 16;
153 else if (!clobbering_colors[i])
154 break;
157 short i = color_clobber_idx;
158 clobbering_colors[i] = hexrep;
159 init_color(i + 16, (r * 1000) / 0xff, (g * 1000) / 0xff,
160 (b * 1000) / 0xff);
162 /* in the unlikely case a user requests this many colors, reuse old slots */
163 if (++color_clobber_idx >= MAX_COLOR_CLOBBER)
164 color_clobber_idx = 0;
166 return i + 16;
169 static const unsigned char color_256_to_16[256] = {
170 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
171 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
172 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
173 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
174 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
175 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
176 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
177 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
178 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
179 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
180 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
181 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
182 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
183 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
184 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
185 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
188 int i = 0;
189 if ((!r || (r - 55) % 40 == 0) &&
190 (!g || (g - 55) % 40 == 0) &&
191 (!b || (b - 55) % 40 == 0)) {
192 i = 16;
193 i += r ? ((r - 55) / 40) * 36 : 0;
194 i += g ? ((g - 55) / 40) * 6 : 0;
195 i += g ? ((b - 55) / 40) : 0;
196 } else if (r == g && g == b && (r - 8) % 10 == 0 && r < 239) {
197 i = 232 + ((r - 8) / 10);
198 } else {
199 unsigned lowest = UINT_MAX;
200 for (int j = 16; j < 256; ++j) {
201 int jr = 0, jg = 0, jb = 0;
202 get_6cube_rgb(j, &jr, &jg, &jb);
203 int dr = jr - r;
204 int dg = jg - g;
205 int db = jb - b;
206 unsigned int distance = dr * dr + dg * dg + db * db;
207 if (distance < lowest) {
208 lowest = distance;
209 i = j;
214 if (COLORS <= 16)
215 return color_256_to_16[i];
216 return i;
219 /* Convert color from string. */
220 static int color_fromstring(const char *s)
222 if (!s)
223 return -1;
224 if (*s == '#' && strlen(s) == 7) {
225 const char *cp;
226 unsigned char r, g, b;
227 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
228 if (*cp != '\0')
229 return -1;
230 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
231 if (n != 3)
232 return -1;
233 return color_find_rgb(r, g, b);
234 } else if ('0' <= *s && *s <= '9') {
235 int col = atoi(s);
236 return (col <= 0 || col > 255) ? -1 : col;
239 if (strcasecmp(s, "black") == 0)
240 return 0;
241 if (strcasecmp(s, "red") == 0)
242 return 1;
243 if (strcasecmp(s, "green") == 0)
244 return 2;
245 if (strcasecmp(s, "yellow") == 0)
246 return 3;
247 if (strcasecmp(s, "blue") == 0)
248 return 4;
249 if (strcasecmp(s, "magenta") == 0)
250 return 5;
251 if (strcasecmp(s, "cyan") == 0)
252 return 6;
253 if (strcasecmp(s, "white") == 0)
254 return 7;
255 return -1;
258 static inline unsigned int color_pair_hash(short fg, short bg) {
259 if (fg == -1)
260 fg = COLORS;
261 if (bg == -1)
262 bg = COLORS + 1;
263 return fg * (COLORS + 2) + bg;
266 static short color_pair_get(short fg, short bg) {
267 static bool has_default_colors;
268 static short *color2palette, default_fg, default_bg;
269 static short color_pairs_max, color_pair_current;
271 if (!color2palette) {
272 pair_content(0, &default_fg, &default_bg);
273 if (default_fg == -1)
274 default_fg = COLOR_WHITE;
275 if (default_bg == -1)
276 default_bg = COLOR_BLACK;
277 has_default_colors = (use_default_colors() == OK);
278 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
279 if (COLORS)
280 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
283 if (fg >= COLORS)
284 fg = default_fg;
285 if (bg >= COLORS)
286 bg = default_bg;
288 if (!has_default_colors) {
289 if (fg == -1)
290 fg = default_fg;
291 if (bg == -1)
292 bg = default_bg;
295 if (!color2palette || (fg == -1 && bg == -1))
296 return 0;
298 unsigned int index = color_pair_hash(fg, bg);
299 if (color2palette[index] == 0) {
300 short oldfg, oldbg;
301 if (++color_pair_current >= color_pairs_max)
302 color_pair_current = 1;
303 pair_content(color_pair_current, &oldfg, &oldbg);
304 unsigned int old_index = color_pair_hash(oldfg, oldbg);
305 if (init_pair(color_pair_current, fg, bg) == OK) {
306 color2palette[old_index] = 0;
307 color2palette[index] = color_pair_current;
311 return color2palette[index];
314 static inline attr_t style_to_attr(CellStyle *style) {
315 return style->attr | COLOR_PAIR(color_pair_get(style->fg, style->bg));
318 static bool ui_window_syntax_style(UiWin *w, int id, const char *style) {
319 UiCursesWin *win = (UiCursesWin*)w;
320 if (id >= UI_STYLE_MAX)
321 return false;
322 if (!style)
323 return true;
324 CellStyle cell_style = win->styles[UI_STYLE_DEFAULT];
325 char *style_copy = strdup(style), *option = style_copy, *next, *p;
326 while (option) {
327 if ((next = strchr(option, ',')))
328 *next++ = '\0';
329 if ((p = strchr(option, ':')))
330 *p++ = '\0';
331 if (!strcasecmp(option, "reverse")) {
332 cell_style.attr |= A_REVERSE;
333 } else if (!strcasecmp(option, "bold")) {
334 cell_style.attr |= A_BOLD;
335 } else if (!strcasecmp(option, "notbold")) {
336 cell_style.attr &= ~A_BOLD;
337 #ifdef A_ITALIC
338 } else if (!strcasecmp(option, "italics")) {
339 cell_style.attr |= A_ITALIC;
340 } else if (!strcasecmp(option, "notitalics")) {
341 cell_style.attr &= ~A_ITALIC;
342 #endif
343 } else if (!strcasecmp(option, "underlined")) {
344 cell_style.attr |= A_UNDERLINE;
345 } else if (!strcasecmp(option, "notunderlined")) {
346 cell_style.attr &= ~A_UNDERLINE;
347 } else if (!strcasecmp(option, "blink")) {
348 cell_style.attr |= A_BLINK;
349 } else if (!strcasecmp(option, "notblink")) {
350 cell_style.attr &= ~A_BLINK;
351 } else if (!strcasecmp(option, "fore")) {
352 cell_style.fg = color_fromstring(p);
353 } else if (!strcasecmp(option, "back")) {
354 cell_style.bg = color_fromstring(p);
356 option = next;
358 win->styles[id] = cell_style;
359 free(style_copy);
360 return true;
363 static void ui_window_resize(UiCursesWin *win, int width, int height) {
364 debug("ui-win-resize[%s]: %dx%d\n", win->file->name ? win->file->name : "noname", width, height);
365 win->width = width;
366 win->height = height;
367 if (win->winstatus)
368 wresize(win->winstatus, 1, width);
369 wresize(win->win, win->winstatus ? height - 1 : height, width - win->sidebar_width);
370 if (win->winside)
371 wresize(win->winside, height-1, win->sidebar_width);
372 view_resize(win->view, width - win->sidebar_width, win->winstatus ? height - 1 : height);
375 static void ui_window_move(UiCursesWin *win, int x, int y) {
376 debug("ui-win-move[%s]: (%d, %d)\n", win->file->name ? win->file->name : "noname", x, y);
377 win->x = x;
378 win->y = y;
379 mvwin(win->win, y, x + win->sidebar_width);
380 if (win->winside)
381 mvwin(win->winside, y, x);
382 if (win->winstatus)
383 mvwin(win->winstatus, y + win->height - 1, x);
386 static bool ui_window_draw_sidebar(UiCursesWin *win) {
387 if (!win->winside)
388 return true;
389 const Line *line = view_lines_get(win->view);
390 int sidebar_width = snprintf(NULL, 0, "%zd", line->lineno + win->height - 2) + 1;
391 if (win->sidebar_width != sidebar_width) {
392 win->sidebar_width = sidebar_width;
393 ui_window_resize(win, win->width, win->height);
394 ui_window_move(win, win->x, win->y);
395 return false;
396 } else {
397 int i = 0;
398 size_t prev_lineno = 0;
399 const Line *cursor_line = view_line_get(win->view);
400 size_t cursor_lineno = cursor_line->lineno;
401 werase(win->winside);
402 wbkgd(win->winside, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
403 wattrset(win->winside, style_to_attr(&win->styles[UI_STYLE_LINENUMBER]));
404 for (const Line *l = line; l; l = l->next, i++) {
405 if (l->lineno && l->lineno != prev_lineno) {
406 if (win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE) {
407 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, l->lineno);
408 } else if (win->options & UI_OPTION_LINE_NUMBERS_RELATIVE) {
409 size_t rel = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
410 if (l->lineno > cursor_lineno)
411 rel = l->lineno - cursor_lineno;
412 else if (l->lineno < cursor_lineno)
413 rel = cursor_lineno - l->lineno;
414 mvwprintw(win->winside, i, 0, "%*u", sidebar_width-1, rel);
417 prev_lineno = l->lineno;
419 mvwvline(win->winside, 0, sidebar_width-1, ACS_VLINE, win->height-1);
420 return true;
424 static void ui_window_status(UiWin *w, const char *status) {
425 UiCursesWin *win = (UiCursesWin*)w;
426 if (!win->winstatus)
427 return;
428 UiCurses *uic = win->ui;
429 bool focused = uic->selwin == win;
430 wattrset(win->winstatus, focused ? A_REVERSE|A_BOLD : A_REVERSE);
431 mvwhline(win->winstatus, 0, 0, ' ', win->width);
432 if (status)
433 mvwprintw(win->winstatus, 0, 0, "%s", status);
436 static void ui_window_draw(UiWin *w) {
437 UiCursesWin *win = (UiCursesWin*)w;
438 if (!ui_window_draw_sidebar(win))
439 return;
441 debug("ui-win-draw[%s]\n", win->file->name ? win->file->name : "noname");
442 wbkgd(win->win, style_to_attr(&win->styles[UI_STYLE_DEFAULT]));
443 wmove(win->win, 0, 0);
444 int width = view_width_get(win->view);
445 CellStyle *prev_style = NULL;
446 size_t cursor_lineno = -1;
448 if (win->options & UI_OPTION_CURSOR_LINE && win->ui->selwin == win) {
449 Filerange selection = view_selection_get(win->view);
450 if (!view_cursors_multiple(win->view) && !text_range_valid(&selection)) {
451 const Line *line = view_line_get(win->view);
452 cursor_lineno = line->lineno;
456 short selection_bg = win->styles[UI_STYLE_SELECTION].bg;
457 short cul_bg = win->styles[UI_STYLE_CURSOR_LINE].bg;
458 attr_t cul_attr = win->styles[UI_STYLE_CURSOR_LINE].attr;
459 bool multiple_cursors = view_cursors_multiple(win->view);
460 attr_t attr = A_NORMAL;
462 for (const Line *l = view_lines_get(win->view); l; l = l->next) {
463 bool cursor_line = l->lineno == cursor_lineno;
464 for (int x = 0; x < width; x++) {
465 enum UiStyle style_id = l->cells[x].style;
466 if (style_id == 0)
467 style_id = UI_STYLE_DEFAULT;
468 CellStyle *style = &win->styles[style_id];
470 if (l->cells[x].cursor && win->ui->selwin == win) {
471 if (multiple_cursors && l->cells[x].cursor_primary)
472 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR_PRIMARY]);
473 else
474 attr = style_to_attr(&win->styles[UI_STYLE_CURSOR]);
475 prev_style = NULL;
476 } else if (l->cells[x].selected) {
477 if (style->fg == selection_bg)
478 attr = style->attr | A_REVERSE;
479 else
480 attr = style->attr | COLOR_PAIR(color_pair_get(style->fg, selection_bg));
481 prev_style = NULL;
482 } else if (cursor_line) {
483 attr = cul_attr | (style->attr & ~A_COLOR) | COLOR_PAIR(color_pair_get(style->fg, cul_bg));
484 prev_style = NULL;
485 } else if (style != prev_style) {
486 attr = style_to_attr(style);
487 prev_style = style;
489 wattrset(win->win, attr);
490 waddstr(win->win, l->cells[x].data);
492 /* try to fixup display issues, in theory we should always output a full line */
493 int x, y;
494 getyx(win->win, y, x);
495 (void)y;
496 wattrset(win->win, A_NORMAL);
497 for (; 0 < x && x < width; x++)
498 waddstr(win->win, " ");
501 wclrtobot(win->win);
504 static void ui_window_reload(UiWin *w, File *file) {
505 UiCursesWin *win = (UiCursesWin*)w;
506 win->file = file;
507 win->sidebar_width = 0;
508 view_reload(win->view, file->text);
509 ui_window_draw(w);
512 static void ui_window_update(UiCursesWin *win) {
513 debug("ui-win-update[%s]\n", win->file->name ? win->file->name : "noname");
514 if (win->winstatus)
515 wnoutrefresh(win->winstatus);
516 if (win->winside)
517 wnoutrefresh(win->winside);
518 wnoutrefresh(win->win);
521 static void ui_arrange(Ui *ui, enum UiLayout layout) {
522 debug("ui-arrange\n");
523 UiCurses *uic = (UiCurses*)ui;
524 uic->layout = layout;
525 int n = 0, m = !!uic->info[0], x = 0, y = 0;
526 for (UiCursesWin *win = uic->windows; win; win = win->next) {
527 if (win->options & UI_OPTION_ONELINE)
528 m++;
529 else
530 n++;
532 int max_height = uic->height - m;
533 int width = (uic->width / MAX(1, n)) - 1;
534 int height = max_height / MAX(1, n);
535 for (UiCursesWin *win = uic->windows; win; win = win->next) {
536 if (win->options & UI_OPTION_ONELINE)
537 continue;
538 n--;
539 if (layout == UI_LAYOUT_HORIZONTAL) {
540 int h = n ? height : max_height - y;
541 ui_window_resize(win, uic->width, h);
542 ui_window_move(win, x, y);
543 y += h;
544 } else {
545 int w = n ? width : uic->width - x;
546 ui_window_resize(win, w, max_height);
547 ui_window_move(win, x, y);
548 x += w;
549 if (n)
550 mvvline(0, x++, ACS_VLINE, max_height);
554 if (layout == UI_LAYOUT_VERTICAL)
555 y = max_height;
557 for (UiCursesWin *win = uic->windows; win; win = win->next) {
558 if (!(win->options & UI_OPTION_ONELINE))
559 continue;
560 ui_window_resize(win, uic->width, 1);
561 ui_window_move(win, 0, y++);
565 static void ui_draw(Ui *ui) {
566 debug("ui-draw\n");
567 UiCurses *uic = (UiCurses*)ui;
568 erase();
569 ui_arrange(ui, uic->layout);
571 for (UiCursesWin *win = uic->windows; win; win = win->next)
572 ui_window_draw((UiWin*)win);
574 if (uic->info[0]) {
575 attrset(A_BOLD);
576 mvaddstr(uic->height-1, 0, uic->info);
579 wnoutrefresh(stdscr);
582 static void ui_redraw(Ui *ui) {
583 clear();
584 ui_draw(ui);
587 static void ui_resize_to(Ui *ui, int width, int height) {
588 UiCurses *uic = (UiCurses*)ui;
589 uic->width = width;
590 uic->height = height;
591 ui_draw(ui);
594 static void ui_resize(Ui *ui) {
595 struct winsize ws;
596 int width, height;
598 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1) {
599 getmaxyx(stdscr, height, width);
600 } else {
601 width = ws.ws_col;
602 height = ws.ws_row;
605 resizeterm(height, width);
606 wresize(stdscr, height, width);
607 ui_resize_to(ui, width, height);
610 static void ui_update(Ui *ui) {
611 UiCurses *uic = (UiCurses*)ui;
612 for (UiCursesWin *win = uic->windows; win; win = win->next)
613 ui_window_update(win);
614 debug("ui-doupdate\n");
615 doupdate();
618 static void ui_window_free(UiWin *w) {
619 UiCursesWin *win = (UiCursesWin*)w;
620 if (!win)
621 return;
622 UiCurses *uic = win->ui;
623 if (win->prev)
624 win->prev->next = win->next;
625 if (win->next)
626 win->next->prev = win->prev;
627 if (uic->windows == win)
628 uic->windows = win->next;
629 if (uic->selwin == win)
630 uic->selwin = NULL;
631 win->next = win->prev = NULL;
632 if (win->winstatus)
633 delwin(win->winstatus);
634 if (win->winside)
635 delwin(win->winside);
636 if (win->win)
637 delwin(win->win);
638 free(win);
641 static void ui_window_focus(UiWin *w) {
642 UiCursesWin *win = (UiCursesWin*)w;
643 UiCursesWin *oldsel = win->ui->selwin;
644 win->ui->selwin = win;
645 if (oldsel) {
646 view_draw(oldsel->view);
647 ui_window_draw((UiWin*)oldsel);
649 view_draw(win->view);
650 ui_window_draw(w);
653 static void ui_window_options_set(UiWin *w, enum UiOption options) {
654 UiCursesWin *win = (UiCursesWin*)w;
655 win->options = options;
656 if (options & (UI_OPTION_LINE_NUMBERS_ABSOLUTE|UI_OPTION_LINE_NUMBERS_RELATIVE)) {
657 if (!win->winside)
658 win->winside = newwin(1, 1, 1, 1);
659 } else {
660 if (win->winside) {
661 delwin(win->winside);
662 win->winside = NULL;
663 win->sidebar_width = 0;
666 if (options & UI_OPTION_STATUSBAR) {
667 if (!win->winstatus)
668 win->winstatus = newwin(1, 0, 0, 0);
669 } else {
670 if (win->winstatus)
671 delwin(win->winstatus);
672 win->winstatus = NULL;
675 if (options & UI_OPTION_ONELINE) {
676 /* move the new window to the end of the list */
677 UiCurses *uic = win->ui;
678 UiCursesWin *last = uic->windows;
679 while (last->next)
680 last = last->next;
681 if (last != win) {
682 if (win->prev)
683 win->prev->next = win->next;
684 if (win->next)
685 win->next->prev = win->prev;
686 if (uic->windows == win)
687 uic->windows = win->next;
688 last->next = win;
689 win->prev = last;
690 win->next = NULL;
694 ui_draw((Ui*)win->ui);
697 static enum UiOption ui_window_options_get(UiWin *w) {
698 UiCursesWin *win = (UiCursesWin*)w;
699 return win->options;
702 static int ui_window_width(UiWin *win) {
703 return ((UiCursesWin*)win)->width;
706 static int ui_window_height(UiWin *win) {
707 return ((UiCursesWin*)win)->height;
710 static void ui_window_swap(UiWin *aw, UiWin *bw) {
711 UiCursesWin *a = (UiCursesWin*)aw;
712 UiCursesWin *b = (UiCursesWin*)bw;
713 if (a == b || !a || !b)
714 return;
715 UiCurses *ui = a->ui;
716 UiCursesWin *tmp = a->next;
717 a->next = b->next;
718 b->next = tmp;
719 if (a->next)
720 a->next->prev = a;
721 if (b->next)
722 b->next->prev = b;
723 tmp = a->prev;
724 a->prev = b->prev;
725 b->prev = tmp;
726 if (a->prev)
727 a->prev->next = a;
728 if (b->prev)
729 b->prev->next = b;
730 if (ui->windows == a)
731 ui->windows = b;
732 else if (ui->windows == b)
733 ui->windows = a;
734 if (ui->selwin == a)
735 ui_window_focus(bw);
736 else if (ui->selwin == b)
737 ui_window_focus(aw);
740 static UiWin *ui_window_new(Ui *ui, View *view, File *file, enum UiOption options) {
741 UiCurses *uic = (UiCurses*)ui;
742 UiCursesWin *win = calloc(1, sizeof(UiCursesWin));
743 if (!win)
744 return NULL;
746 win->uiwin = (UiWin) {
747 .draw = ui_window_draw,
748 .status = ui_window_status,
749 .options_set = ui_window_options_set,
750 .options_get = ui_window_options_get,
751 .reload = ui_window_reload,
752 .syntax_style = ui_window_syntax_style,
753 .window_width = ui_window_width,
754 .window_height = ui_window_height,
757 if (!(win->win = newwin(0, 0, 0, 0))) {
758 ui_window_free((UiWin*)win);
759 return NULL;
763 for (int i = 0; i < UI_STYLE_MAX; i++) {
764 win->styles[i] = (CellStyle) {
765 .fg = -1, .bg = -1, .attr = A_NORMAL,
769 win->styles[UI_STYLE_CURSOR].attr |= A_REVERSE;
770 win->styles[UI_STYLE_CURSOR_PRIMARY].attr |= A_REVERSE|A_BLINK;
771 win->styles[UI_STYLE_SELECTION].attr |= A_REVERSE;
772 win->styles[UI_STYLE_COLOR_COLUMN].attr |= A_REVERSE;
774 win->ui = uic;
775 win->view = view;
776 win->file = file;
777 view_ui(view, &win->uiwin);
779 if (uic->windows)
780 uic->windows->prev = win;
781 win->next = uic->windows;
782 uic->windows = win;
784 if (text_size(file->text) > UI_LARGE_FILE_SIZE) {
785 options |= UI_OPTION_LARGE_FILE;
786 options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
789 ui_window_options_set((UiWin*)win, options);
791 return &win->uiwin;
794 static void ui_info(Ui *ui, const char *msg, va_list ap) {
795 UiCurses *uic = (UiCurses*)ui;
796 vsnprintf(uic->info, sizeof(uic->info), msg, ap);
797 ui_draw(ui);
800 static void ui_info_hide(Ui *ui) {
801 UiCurses *uic = (UiCurses*)ui;
802 if (uic->info[0]) {
803 uic->info[0] = '\0';
804 ui_draw(ui);
808 static TermKey *ui_termkey_new(int fd) {
809 TermKey *termkey = termkey_new(fd, TERMKEY_FLAG_UTF8);
810 if (termkey)
811 termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
812 return termkey;
815 static TermKey *ui_termkey_reopen(Ui *ui, int fd) {
816 int tty = open("/dev/tty", O_RDWR);
817 if (tty == -1)
818 return NULL;
819 if (tty != fd && dup2(tty, fd) == -1) {
820 close(tty);
821 return NULL;
823 close(tty);
824 return ui_termkey_new(fd);
827 static TermKey *ui_termkey_get(Ui *ui) {
828 UiCurses *uic = (UiCurses*)ui;
829 return uic->termkey;
832 static void ui_suspend(Ui *ui) {
833 if (change_colors)
834 undo_palette();
835 endwin();
836 kill(0, SIGSTOP);
839 static bool ui_getkey(Ui *ui, TermKeyKey *key) {
840 UiCurses *uic = (UiCurses*)ui;
841 TermKeyResult ret = termkey_getkey(uic->termkey, key);
843 if (ret == TERMKEY_RES_EOF) {
844 termkey_destroy(uic->termkey);
845 errno = 0;
846 if (!(uic->termkey = ui_termkey_reopen(ui, STDIN_FILENO)))
847 ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : "");
848 return false;
851 if (ret == TERMKEY_RES_AGAIN) {
852 struct pollfd fd;
853 fd.fd = STDIN_FILENO;
854 fd.events = POLLIN;
855 if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0)
856 ret = termkey_getkey_force(uic->termkey, key);
859 return ret == TERMKEY_RES_KEY;
862 static void ui_terminal_save(Ui *ui) {
863 UiCurses *uic = (UiCurses*)ui;
864 curs_set(1);
865 reset_shell_mode();
866 termkey_stop(uic->termkey);
869 static void ui_terminal_restore(Ui *ui) {
870 UiCurses *uic = (UiCurses*)ui;
871 termkey_start(uic->termkey);
872 reset_prog_mode();
873 wclear(stdscr);
874 curs_set(0);
877 static int ui_colors(Ui *ui) {
878 return COLORS;
881 static bool ui_init(Ui *ui, Vis *vis) {
882 UiCurses *uic = (UiCurses*)ui;
883 uic->vis = vis;
885 setlocale(LC_CTYPE, "");
887 char *term = getenv("TERM");
888 if (!term)
889 term = "xterm";
891 errno = 0;
892 if (!(uic->termkey = ui_termkey_new(STDIN_FILENO))) {
893 /* work around libtermkey bug which fails if stdin is /dev/null */
894 if (errno == EBADF && !isatty(STDIN_FILENO)) {
895 errno = 0;
896 if (!(uic->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO)
897 uic->termkey = termkey_new_abstract(term, TERMKEY_FLAG_UTF8);
899 if (!uic->termkey)
900 goto err;
903 if (!newterm(term, stderr, stdin)) {
904 snprintf(uic->info, sizeof(uic->info), "Warning: unknown term `%s'", term);
905 if (!newterm(strstr(term, "-256color") ? "xterm-256color" : "xterm", stderr, stdin))
906 goto err;
908 start_color();
909 use_default_colors();
910 raw();
911 noecho();
912 nonl();
913 keypad(stdscr, TRUE);
914 meta(stdscr, TRUE);
915 curs_set(0);
916 change_colors = can_change_color() && COLORS >= 256;
918 ui_resize(ui);
919 return true;
920 err:
921 ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : "");
922 return false;
925 Ui *ui_curses_new(void) {
927 Ui *ui = calloc(1, sizeof(UiCurses));
928 if (!ui)
929 return NULL;
931 *ui = (Ui) {
932 .init = ui_init,
933 .free = ui_curses_free,
934 .termkey_get = ui_termkey_get,
935 .suspend = ui_suspend,
936 .resize = ui_resize,
937 .update = ui_update,
938 .window_new = ui_window_new,
939 .window_free = ui_window_free,
940 .window_focus = ui_window_focus,
941 .window_swap = ui_window_swap,
942 .draw = ui_draw,
943 .redraw = ui_redraw,
944 .arrange = ui_arrange,
945 .die = ui_die,
946 .info = ui_info,
947 .info_hide = ui_info_hide,
948 .getkey = ui_getkey,
949 .terminal_save = ui_terminal_save,
950 .terminal_restore = ui_terminal_restore,
951 .colors = ui_colors,
954 return ui;
957 void ui_curses_free(Ui *ui) {
958 UiCurses *uic = (UiCurses*)ui;
959 if (!uic)
960 return;
961 while (uic->windows)
962 ui_window_free((UiWin*)uic->windows);
963 if (change_colors)
964 undo_palette();
965 endwin();
966 if (uic->termkey)
967 termkey_destroy(uic->termkey);
968 free(uic);