10 #include <sys/types.h>
16 #include "ui-curses.h"
21 #include "text-util.h"
23 #ifdef NCURSES_VERSION
24 # ifndef NCURSES_EXT_COLORS
25 # define NCURSES_EXT_COLORS 0
27 # if !NCURSES_EXT_COLORS
28 # define MAX_COLOR_PAIRS 256
31 #ifndef MAX_COLOR_PAIRS
32 # define MAX_COLOR_PAIRS COLOR_PAIRS
40 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
42 #define debug(...) do { } while (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); \
50 printf("OK resizing: %d x %d\n", x, y); \
55 #define mvwin(win, y, x) do { \
56 if (mvwin(win, y, x) == ERR) { \
57 printf("ERROR moving: %d x %d\n", x, y); \
59 printf("OK moving: %d x %d\n", x, y); \
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
;
75 typedef struct UiCursesWin UiCursesWin
;
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) */
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
;
108 termkey_stop(uic
->termkey
);
109 vfprintf(stderr
, msg
, ap
);
113 __attribute__((noreturn
)) static void ui_die_msg(Ui
*ui
, const char *msg
, ...) {
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
)
125 } else if (n
< 232) {
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) {
138 /* Reset color palette to default values using OSC 104 */
139 static void undo_palette(void)
141 fputs("\033]104;\a", 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
)
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
)
153 else if (!clobbering_colors
[i
])
157 short i
= color_clobber_idx
;
158 clobbering_colors
[i
] = hexrep
;
159 init_color(i
+ 16, (r
* 1000) / 0xff, (g
* 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;
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
189 if ((!r
|| (r
- 55) % 40 == 0) &&
190 (!g
|| (g
- 55) % 40 == 0) &&
191 (!b
|| (b
- 55) % 40 == 0)) {
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);
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
);
206 unsigned int distance
= dr
* dr
+ dg
* dg
+ db
* db
;
207 if (distance
< lowest
) {
215 return color_256_to_16
[i
];
219 /* Convert color from string. */
220 static int color_fromstring(const char *s
)
224 if (*s
== '#' && strlen(s
) == 7) {
226 unsigned char r
, g
, b
;
227 for (cp
= s
+ 1; isxdigit((unsigned char)*cp
); cp
++);
230 int n
= sscanf(s
+ 1, "%2hhx%2hhx%2hhx", &r
, &g
, &b
);
233 return color_find_rgb(r
, g
, b
);
234 } else if ('0' <= *s
&& *s
<= '9') {
236 return (col
<= 0 || col
> 255) ? -1 : col
;
239 if (strcasecmp(s
, "black") == 0)
241 if (strcasecmp(s
, "red") == 0)
243 if (strcasecmp(s
, "green") == 0)
245 if (strcasecmp(s
, "yellow") == 0)
247 if (strcasecmp(s
, "blue") == 0)
249 if (strcasecmp(s
, "magenta") == 0)
251 if (strcasecmp(s
, "cyan") == 0)
253 if (strcasecmp(s
, "white") == 0)
258 static inline unsigned int color_pair_hash(short fg
, short bg
) {
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
);
280 color2palette
= calloc((COLORS
+ 2) * (COLORS
+ 2), sizeof(short));
288 if (!has_default_colors
) {
295 if (!color2palette
|| (fg
== -1 && bg
== -1))
298 unsigned int index
= color_pair_hash(fg
, bg
);
299 if (color2palette
[index
] == 0) {
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
)
324 CellStyle cell_style
= win
->styles
[UI_STYLE_DEFAULT
];
325 char *style_copy
= strdup(style
), *option
= style_copy
, *next
, *p
;
327 if ((next
= strchr(option
, ',')))
329 if ((p
= strchr(option
, ':')))
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
;
338 } else if (!strcasecmp(option
, "italics")) {
339 cell_style
.attr
|= A_ITALIC
;
340 } else if (!strcasecmp(option
, "notitalics")) {
341 cell_style
.attr
&= ~A_ITALIC
;
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
);
358 win
->styles
[id
] = cell_style
;
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
);
366 win
->height
= height
;
368 wresize(win
->winstatus
, 1, width
);
369 wresize(win
->win
, win
->winstatus
? height
- 1 : height
, width
- win
->sidebar_width
);
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
);
379 mvwin(win
->win
, y
, x
+ win
->sidebar_width
);
381 mvwin(win
->winside
, y
, x
);
383 mvwin(win
->winstatus
, y
+ win
->height
- 1, x
);
386 static bool ui_window_draw_sidebar(UiCursesWin
*win
) {
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
);
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);
424 static void ui_window_status(UiWin
*w
, const char *status
) {
425 UiCursesWin
*win
= (UiCursesWin
*)w
;
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
);
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
))
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
;
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
]);
474 attr
= style_to_attr(&win
->styles
[UI_STYLE_CURSOR
]);
476 } else if (l
->cells
[x
].selected
) {
477 if (style
->fg
== selection_bg
)
478 attr
= style
->attr
| A_REVERSE
;
480 attr
= style
->attr
| COLOR_PAIR(color_pair_get(style
->fg
, selection_bg
));
482 } else if (cursor_line
) {
483 attr
= cul_attr
| (style
->attr
& ~A_COLOR
) | COLOR_PAIR(color_pair_get(style
->fg
, cul_bg
));
485 } else if (style
!= prev_style
) {
486 attr
= style_to_attr(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 */
494 getyx(win
->win
, y
, x
);
496 wattrset(win
->win
, A_NORMAL
);
497 for (; 0 < x
&& x
< width
; x
++)
498 waddstr(win
->win
, " ");
504 static void ui_window_reload(UiWin
*w
, File
*file
) {
505 UiCursesWin
*win
= (UiCursesWin
*)w
;
507 win
->sidebar_width
= 0;
508 view_reload(win
->view
, file
->text
);
512 static void ui_window_update(UiCursesWin
*win
) {
513 debug("ui-win-update[%s]\n", win
->file
->name
? win
->file
->name
: "noname");
515 wnoutrefresh(win
->winstatus
);
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
)
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
)
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
);
545 int w
= n
? width
: uic
->width
- x
;
546 ui_window_resize(win
, w
, max_height
);
547 ui_window_move(win
, x
, y
);
550 mvvline(0, x
++, ACS_VLINE
, max_height
);
554 if (layout
== UI_LAYOUT_VERTICAL
)
557 for (UiCursesWin
*win
= uic
->windows
; win
; win
= win
->next
) {
558 if (!(win
->options
& UI_OPTION_ONELINE
))
560 ui_window_resize(win
, uic
->width
, 1);
561 ui_window_move(win
, 0, y
++);
565 static void ui_draw(Ui
*ui
) {
567 UiCurses
*uic
= (UiCurses
*)ui
;
569 ui_arrange(ui
, uic
->layout
);
571 for (UiCursesWin
*win
= uic
->windows
; win
; win
= win
->next
)
572 ui_window_draw((UiWin
*)win
);
576 mvaddstr(uic
->height
-1, 0, uic
->info
);
579 wnoutrefresh(stdscr
);
582 static void ui_redraw(Ui
*ui
) {
587 static void ui_resize_to(Ui
*ui
, int width
, int height
) {
588 UiCurses
*uic
= (UiCurses
*)ui
;
590 uic
->height
= height
;
594 static void ui_resize(Ui
*ui
) {
598 if (ioctl(STDERR_FILENO
, TIOCGWINSZ
, &ws
) == -1) {
599 getmaxyx(stdscr
, height
, width
);
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");
618 static void ui_window_free(UiWin
*w
) {
619 UiCursesWin
*win
= (UiCursesWin
*)w
;
622 UiCurses
*uic
= win
->ui
;
624 win
->prev
->next
= win
->next
;
626 win
->next
->prev
= win
->prev
;
627 if (uic
->windows
== win
)
628 uic
->windows
= win
->next
;
629 if (uic
->selwin
== win
)
631 win
->next
= win
->prev
= NULL
;
633 delwin(win
->winstatus
);
635 delwin(win
->winside
);
641 static void ui_window_focus(UiWin
*w
) {
642 UiCursesWin
*win
= (UiCursesWin
*)w
;
643 UiCursesWin
*oldsel
= win
->ui
->selwin
;
644 win
->ui
->selwin
= win
;
646 view_draw(oldsel
->view
);
647 ui_window_draw((UiWin
*)oldsel
);
649 view_draw(win
->view
);
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
)) {
658 win
->winside
= newwin(1, 1, 1, 1);
661 delwin(win
->winside
);
663 win
->sidebar_width
= 0;
666 if (options
& UI_OPTION_STATUSBAR
) {
668 win
->winstatus
= newwin(1, 0, 0, 0);
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
;
683 win
->prev
->next
= win
->next
;
685 win
->next
->prev
= win
->prev
;
686 if (uic
->windows
== win
)
687 uic
->windows
= win
->next
;
694 ui_draw((Ui
*)win
->ui
);
697 static enum UiOption
ui_window_options_get(UiWin
*w
) {
698 UiCursesWin
*win
= (UiCursesWin
*)w
;
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
)
715 UiCurses
*ui
= a
->ui
;
716 UiCursesWin
*tmp
= a
->next
;
730 if (ui
->windows
== a
)
732 else if (ui
->windows
== b
)
736 else if (ui
->selwin
== b
)
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
));
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
);
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
;
777 view_ui(view
, &win
->uiwin
);
780 uic
->windows
->prev
= win
;
781 win
->next
= uic
->windows
;
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
);
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
);
800 static void ui_info_hide(Ui
*ui
) {
801 UiCurses
*uic
= (UiCurses
*)ui
;
808 static TermKey
*ui_termkey_new(int fd
) {
809 TermKey
*termkey
= termkey_new(fd
, TERMKEY_FLAG_UTF8
);
811 termkey_set_canonflags(termkey
, TERMKEY_CANON_DELBS
);
815 static TermKey
*ui_termkey_reopen(Ui
*ui
, int fd
) {
816 int tty
= open("/dev/tty", O_RDWR
);
819 if (tty
!= fd
&& dup2(tty
, fd
) == -1) {
824 return ui_termkey_new(fd
);
827 static TermKey
*ui_termkey_get(Ui
*ui
) {
828 UiCurses
*uic
= (UiCurses
*)ui
;
832 static void ui_suspend(Ui
*ui
) {
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
);
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
) : "");
851 if (ret
== TERMKEY_RES_AGAIN
) {
853 fd
.fd
= STDIN_FILENO
;
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
;
866 termkey_stop(uic
->termkey
);
869 static void ui_terminal_restore(Ui
*ui
) {
870 UiCurses
*uic
= (UiCurses
*)ui
;
871 termkey_start(uic
->termkey
);
877 static int ui_colors(Ui
*ui
) {
881 static bool ui_init(Ui
*ui
, Vis
*vis
) {
882 UiCurses
*uic
= (UiCurses
*)ui
;
885 setlocale(LC_CTYPE
, "");
887 char *term
= getenv("TERM");
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
)) {
896 if (!(uic
->termkey
= ui_termkey_reopen(ui
, STDIN_FILENO
)) && errno
== ENXIO
)
897 uic
->termkey
= termkey_new_abstract(term
, TERMKEY_FLAG_UTF8
);
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
))
909 use_default_colors();
913 keypad(stdscr
, TRUE
);
916 change_colors
= can_change_color() && COLORS
>= 256;
921 ui_die_msg(ui
, "Failed to start curses interface: %s\n", errno
!= 0 ? strerror(errno
) : "");
925 Ui
*ui_curses_new(void) {
927 Ui
*ui
= calloc(1, sizeof(UiCurses
));
933 .free
= ui_curses_free
,
934 .termkey_get
= ui_termkey_get
,
935 .suspend
= ui_suspend
,
938 .window_new
= ui_window_new
,
939 .window_free
= ui_window_free
,
940 .window_focus
= ui_window_focus
,
941 .window_swap
= ui_window_swap
,
944 .arrange
= ui_arrange
,
947 .info_hide
= ui_info_hide
,
949 .terminal_save
= ui_terminal_save
,
950 .terminal_restore
= ui_terminal_restore
,
957 void ui_curses_free(Ui
*ui
) {
958 UiCurses
*uic
= (UiCurses
*)ui
;
962 ui_window_free((UiWin
*)uic
->windows
);
967 termkey_destroy(uic
->termkey
);