10 #include <sys/types.h>
16 #include "ui-terminal.h"
21 #include "text-util.h"
28 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
30 #define debug(...) do { } while (0)
33 #define MAX_WIDTH 1024
34 #define MAX_HEIGHT 1024
35 typedef struct UiTermWin UiTermWin
;
38 Ui ui
; /* generic ui interface, has to be the first struct member */
39 Vis
*vis
; /* editor instance to which this ui belongs */
40 UiTermWin
*windows
; /* all windows managed by this ui */
41 UiTermWin
*selwin
; /* the currently selected layout */
42 char info
[MAX_WIDTH
]; /* info message displayed at the bottom of the screen */
43 int width
, height
; /* terminal dimensions available for all windows */
44 enum UiLayout layout
; /* whether windows are displayed horizontally or vertically */
45 TermKey
*termkey
; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */
46 size_t ids
; /* bit mask of in use window ids */
47 size_t styles_size
; /* #bytes allocated for styles array */
48 CellStyle
*styles
; /* each window has UI_STYLE_MAX different style definitions */
49 size_t cells_size
; /* #bytes allocated for 2D grid (grows only) */
50 Cell
*cells
; /* 2D grid of cells, at least as large as current terminal size */
54 UiWin uiwin
; /* generic interface, has to be the first struct member */
55 UiTerm
*ui
; /* ui which manages this window */
56 Win
*win
; /* editor window being displayed */
57 int id
; /* unique identifier for this window */
58 int width
, height
; /* window dimension including status bar */
59 int x
, y
; /* window position */
60 int sidebar_width
; /* width of the sidebar showing line numbers etc. */
61 UiTermWin
*next
, *prev
; /* pointers to neighbouring windows */
62 enum UiOption options
; /* display settings for this window */
66 #include "ui-terminal-curses.c"
68 #include "ui-terminal-vt100.c"
71 __attribute__((noreturn
)) static void ui_die(Ui
*ui
, const char *msg
, va_list ap
) {
72 UiTerm
*tui
= (UiTerm
*)ui
;
73 ui_term_backend_free(tui
);
75 termkey_stop(tui
->termkey
);
76 vfprintf(stderr
, msg
, ap
);
80 __attribute__((noreturn
)) static void ui_die_msg(Ui
*ui
, const char *msg
, ...) {
87 static void ui_window_resize(UiTermWin
*win
, int width
, int height
) {
88 debug("ui-win-resize[%s]: %dx%d\n", win
->win
->file
->name
? win
->win
->file
->name
: "noname", width
, height
);
89 bool status
= win
->options
& UI_OPTION_STATUSBAR
;
92 view_resize(win
->win
->view
, width
- win
->sidebar_width
, status
? height
- 1 : height
);
95 static void ui_window_move(UiTermWin
*win
, int x
, int y
) {
96 debug("ui-win-move[%s]: (%d, %d)\n", win
->win
->file
->name
? win
->win
->file
->name
: "noname", x
, y
);
101 static bool color_fromstring(UiTerm
*ui
, CellColor
*color
, const char *s
)
105 if (*s
== '#' && strlen(s
) == 7) {
107 unsigned char r
, g
, b
;
108 for (cp
= s
+ 1; isxdigit((unsigned char)*cp
); cp
++);
111 int n
= sscanf(s
+ 1, "%2hhx%2hhx%2hhx", &r
, &g
, &b
);
114 *color
= color_rgb(ui
, r
, g
, b
);
116 } else if ('0' <= *s
&& *s
<= '9') {
118 if (index
<= 0 || index
> 255)
120 *color
= color_terminal(ui
, index
);
124 static const struct {
128 { "black", CELL_COLOR_BLACK
},
129 { "red", CELL_COLOR_RED
},
130 { "green", CELL_COLOR_GREEN
},
131 { "yellow", CELL_COLOR_YELLOW
},
132 { "blue", CELL_COLOR_BLUE
},
133 { "magenta", CELL_COLOR_MAGENTA
},
134 { "cyan", CELL_COLOR_CYAN
},
135 { "white", CELL_COLOR_WHITE
},
136 { "default", CELL_COLOR_DEFAULT
},
139 for (size_t i
= 0; i
< LENGTH(color_names
); i
++) {
140 if (strcasecmp(color_names
[i
].name
, s
) == 0) {
141 *color
= color_names
[i
].color
;
149 static bool ui_style_define(UiWin
*w
, int id
, const char *style
) {
150 UiTermWin
*win
= (UiTermWin
*)w
;
151 UiTerm
*tui
= win
->ui
;
152 if (id
>= UI_STYLE_MAX
)
156 CellStyle cell_style
= tui
->styles
[win
->id
* UI_STYLE_MAX
+ UI_STYLE_DEFAULT
];
157 char *style_copy
= strdup(style
), *option
= style_copy
;
159 while (*option
== ' ')
161 char *next
= strchr(option
, ',');
164 char *value
= strchr(option
, ':');
166 for (*value
++ = '\0'; *value
== ' '; value
++);
167 if (!strcasecmp(option
, "reverse")) {
168 cell_style
.attr
|= CELL_ATTR_REVERSE
;
169 } else if (!strcasecmp(option
, "bold")) {
170 cell_style
.attr
|= CELL_ATTR_BOLD
;
171 } else if (!strcasecmp(option
, "notbold")) {
172 cell_style
.attr
&= ~CELL_ATTR_BOLD
;
173 } else if (!strcasecmp(option
, "italics")) {
174 cell_style
.attr
|= CELL_ATTR_ITALIC
;
175 } else if (!strcasecmp(option
, "notitalics")) {
176 cell_style
.attr
&= ~CELL_ATTR_ITALIC
;
177 } else if (!strcasecmp(option
, "underlined")) {
178 cell_style
.attr
|= CELL_ATTR_UNDERLINE
;
179 } else if (!strcasecmp(option
, "notunderlined")) {
180 cell_style
.attr
&= ~CELL_ATTR_UNDERLINE
;
181 } else if (!strcasecmp(option
, "blink")) {
182 cell_style
.attr
|= CELL_ATTR_BLINK
;
183 } else if (!strcasecmp(option
, "notblink")) {
184 cell_style
.attr
&= ~CELL_ATTR_BLINK
;
185 } else if (!strcasecmp(option
, "fore")) {
186 color_fromstring(win
->ui
, &cell_style
.fg
, value
);
187 } else if (!strcasecmp(option
, "back")) {
188 color_fromstring(win
->ui
, &cell_style
.bg
, value
);
192 tui
->styles
[win
->id
* UI_STYLE_MAX
+ id
] = cell_style
;
197 static void ui_draw_line(UiTerm
*tui
, int x
, int y
, char c
, enum UiStyle style_id
) {
198 if (x
< 0 || x
>= tui
->width
|| y
< 0 || y
>= tui
->height
)
200 CellStyle style
= tui
->styles
[style_id
];
201 Cell (*cells
)[tui
->width
] = (void*)tui
->cells
;
202 while (x
< tui
->width
) {
203 cells
[y
][x
].data
[0] = c
;
204 cells
[y
][x
].data
[1] = '\0';
205 cells
[y
][x
].style
= style
;
210 static void ui_draw_string(UiTerm
*tui
, int x
, int y
, const char *str
, UiTermWin
*win
, enum UiStyle style_id
) {
211 debug("draw-string: [%d][%d]\n", y
, x
);
212 if (x
< 0 || x
>= tui
->width
|| y
< 0 || y
>= tui
->height
)
214 CellStyle style
= tui
->styles
[(win
? win
->id
: 0)*UI_STYLE_MAX
+ style_id
];
215 // FIXME: does not handle double width characters etc, share code with view.c?
216 Cell (*cells
)[tui
->width
] = (void*)tui
->cells
;
217 const size_t cell_size
= sizeof(cells
[0][0].data
)-1;
218 for (const char *next
= str
; *str
&& x
< tui
->width
; str
= next
) {
219 do next
++; while (!ISUTF8(*next
));
220 size_t len
= next
- str
;
223 len
= MIN(len
, cell_size
);
224 strncpy(cells
[y
][x
].data
, str
, len
);
225 cells
[y
][x
].data
[len
] = '\0';
226 cells
[y
][x
].style
= style
;
231 static void ui_window_draw(UiWin
*w
) {
232 UiTermWin
*win
= (UiTermWin
*)w
;
233 UiTerm
*ui
= win
->ui
;
234 View
*view
= win
->win
->view
;
235 Cell (*cells
)[ui
->width
] = (void*)ui
->cells
;
236 int width
= win
->width
, height
= win
->height
;
237 const Line
*line
= view_lines_first(view
);
238 bool status
= win
->options
& UI_OPTION_STATUSBAR
;
239 bool nu
= win
->options
& UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
240 bool rnu
= win
->options
& UI_OPTION_LINE_NUMBERS_RELATIVE
;
241 bool sidebar
= nu
|| rnu
;
242 int sidebar_width
= sidebar
? snprintf(NULL
, 0, "%zd ", line
->lineno
+ height
- 2) : 0;
243 if (sidebar_width
!= win
->sidebar_width
) {
244 view_resize(view
, width
- sidebar_width
, status
? height
- 1 : height
);
245 win
->sidebar_width
= sidebar_width
;
247 vis_window_draw(win
->win
);
248 line
= view_lines_first(view
);
249 size_t prev_lineno
= 0;
250 Selection
*sel
= view_selections_primary_get(view
);
251 const Line
*cursor_line
= view_cursors_line_get(sel
);
252 size_t cursor_lineno
= cursor_line
->lineno
;
253 char buf
[sidebar_width
+1];
254 int x
= win
->x
, y
= win
->y
;
255 int view_width
= view_width_get(view
);
256 if (x
+ sidebar_width
+ view_width
> ui
->width
)
257 view_width
= ui
->width
- x
- sidebar_width
;
258 for (const Line
*l
= line
; l
; l
= l
->next
) {
260 if (!l
->lineno
|| !l
->len
|| l
->lineno
== prev_lineno
) {
261 memset(buf
, ' ', sizeof(buf
));
262 buf
[sidebar_width
] = '\0';
264 size_t number
= l
->lineno
;
266 number
= (win
->options
& UI_OPTION_LARGE_FILE
) ? 0 : l
->lineno
;
267 if (l
->lineno
> cursor_lineno
)
268 number
= l
->lineno
- cursor_lineno
;
269 else if (l
->lineno
< cursor_lineno
)
270 number
= cursor_lineno
- l
->lineno
;
272 snprintf(buf
, sizeof buf
, "%*zu ", sidebar_width
-1, number
);
274 ui_draw_string(ui
, x
, y
, buf
, win
,
275 (l
->lineno
== cursor_lineno
) ? UI_STYLE_LINENUMBER_CURSOR
: UI_STYLE_LINENUMBER
);
276 prev_lineno
= l
->lineno
;
278 debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y
, x
+sidebar_width
, y
, view_width
);
279 memcpy(&cells
[y
++][x
+sidebar_width
], l
->cells
, sizeof(Cell
) * view_width
);
283 static CellStyle
ui_window_style_get(UiWin
*w
, enum UiStyle style
) {
284 UiTermWin
*win
= (UiTermWin
*)w
;
285 UiTerm
*tui
= win
->ui
;
286 return tui
->styles
[win
->id
* UI_STYLE_MAX
+ style
];
289 static void ui_window_status(UiWin
*w
, const char *status
) {
290 UiTermWin
*win
= (UiTermWin
*)w
;
291 if (!(win
->options
& UI_OPTION_STATUSBAR
))
293 UiTerm
*ui
= win
->ui
;
294 enum UiStyle style
= ui
->selwin
== win
? UI_STYLE_STATUS_FOCUSED
: UI_STYLE_STATUS
;
295 ui_draw_string(ui
, win
->x
, win
->y
+ win
->height
- 1, status
, win
, style
);
298 static void ui_arrange(Ui
*ui
, enum UiLayout layout
) {
299 debug("ui-arrange\n");
300 UiTerm
*tui
= (UiTerm
*)ui
;
301 tui
->layout
= layout
;
302 Cell (*cells
)[tui
->width
] = (void*)tui
->cells
;
303 int n
= 0, m
= !!tui
->info
[0], x
= 0, y
= 0;
304 for (UiTermWin
*win
= tui
->windows
; win
; win
= win
->next
) {
305 if (win
->options
& UI_OPTION_ONELINE
)
310 int max_height
= tui
->height
- m
;
311 int width
= (tui
->width
/ MAX(1, n
)) - 1;
312 int height
= max_height
/ MAX(1, n
);
313 for (UiTermWin
*win
= tui
->windows
; win
; win
= win
->next
) {
314 if (win
->options
& UI_OPTION_ONELINE
)
317 if (layout
== UI_LAYOUT_HORIZONTAL
) {
318 int h
= n
? height
: max_height
- y
;
319 ui_window_resize(win
, tui
->width
, h
);
320 ui_window_move(win
, x
, y
);
323 int w
= n
? width
: tui
->width
- x
;
324 ui_window_resize(win
, w
, max_height
);
325 ui_window_move(win
, x
, y
);
328 for (int i
= 0; i
< max_height
; i
++) {
329 strcpy(cells
[i
][x
].data
,"│");
330 cells
[i
][x
].style
= tui
->styles
[UI_STYLE_SEPARATOR
];
337 if (layout
== UI_LAYOUT_VERTICAL
)
340 for (UiTermWin
*win
= tui
->windows
; win
; win
= win
->next
) {
341 if (!(win
->options
& UI_OPTION_ONELINE
))
343 ui_window_resize(win
, tui
->width
, 1);
344 ui_window_move(win
, 0, y
++);
348 static void ui_draw(Ui
*ui
) {
350 UiTerm
*tui
= (UiTerm
*)ui
;
351 ui_arrange(ui
, tui
->layout
);
352 for (UiTermWin
*win
= tui
->windows
; win
; win
= win
->next
)
353 ui_window_draw((UiWin
*)win
);
355 ui_draw_string(tui
, 0, tui
->height
-1, tui
->info
, NULL
, UI_STYLE_INFO
);
356 ui_term_backend_blit(tui
);
359 static void ui_redraw(Ui
*ui
) {
360 UiTerm
*tui
= (UiTerm
*)ui
;
361 ui_term_backend_clear(tui
);
362 for (UiTermWin
*win
= tui
->windows
; win
; win
= win
->next
)
363 view_invalidate(win
->win
->view
);
366 static void ui_resize(Ui
*ui
) {
367 UiTerm
*tui
= (UiTerm
*)ui
;
369 int width
= 80, height
= 24;
371 if (ioctl(STDERR_FILENO
, TIOCGWINSZ
, &ws
) != -1) {
376 width
= MAX(width
, 1);
377 width
= MIN(width
, MAX_WIDTH
);
378 height
= MAX(height
, 1);
379 height
= MIN(height
, MAX_HEIGHT
);
380 if (!ui_term_backend_resize(tui
, width
, height
))
383 size_t size
= width
*height
*sizeof(Cell
);
384 if (size
> tui
->cells_size
) {
385 Cell
*cells
= realloc(tui
->cells
, size
);
388 memset((char*)cells
+tui
->cells_size
, 0, size
- tui
->cells_size
);
389 tui
->cells_size
= size
;
393 tui
->height
= height
;
396 static void ui_window_free(UiWin
*w
) {
397 UiTermWin
*win
= (UiTermWin
*)w
;
400 UiTerm
*tui
= win
->ui
;
402 win
->prev
->next
= win
->next
;
404 win
->next
->prev
= win
->prev
;
405 if (tui
->windows
== win
)
406 tui
->windows
= win
->next
;
407 if (tui
->selwin
== win
)
409 win
->next
= win
->prev
= NULL
;
410 tui
->ids
&= ~(1UL << win
->id
);
414 static void ui_window_focus(UiWin
*w
) {
415 UiTermWin
*new = (UiTermWin
*)w
;
416 UiTermWin
*old
= new->ui
->selwin
;
417 if (new->options
& UI_OPTION_STATUSBAR
)
418 new->ui
->selwin
= new;
420 view_invalidate(old
->win
->view
);
421 view_invalidate(new->win
->view
);
424 static void ui_window_options_set(UiWin
*w
, enum UiOption options
) {
425 UiTermWin
*win
= (UiTermWin
*)w
;
426 win
->options
= options
;
427 if (options
& UI_OPTION_ONELINE
) {
428 /* move the new window to the end of the list */
429 UiTerm
*tui
= win
->ui
;
430 UiTermWin
*last
= tui
->windows
;
435 win
->prev
->next
= win
->next
;
437 win
->next
->prev
= win
->prev
;
438 if (tui
->windows
== win
)
439 tui
->windows
= win
->next
;
445 ui_draw((Ui
*)win
->ui
);
448 static enum UiOption
ui_window_options_get(UiWin
*win
) {
449 return ((UiTermWin
*)win
)->options
;
452 static int ui_window_width(UiWin
*win
) {
453 return ((UiTermWin
*)win
)->width
;
456 static int ui_window_height(UiWin
*win
) {
457 return ((UiTermWin
*)win
)->height
;
460 static void ui_window_swap(UiWin
*aw
, UiWin
*bw
) {
461 UiTermWin
*a
= (UiTermWin
*)aw
;
462 UiTermWin
*b
= (UiTermWin
*)bw
;
463 if (a
== b
|| !a
|| !b
)
466 UiTermWin
*tmp
= a
->next
;
480 if (tui
->windows
== a
)
482 else if (tui
->windows
== b
)
484 if (tui
->selwin
== a
)
486 else if (tui
->selwin
== b
)
490 static UiWin
*ui_window_new(Ui
*ui
, Win
*w
, enum UiOption options
) {
491 UiTerm
*tui
= (UiTerm
*)ui
;
492 /* get rightmost zero bit, i.e. highest available id */
493 size_t bit
= ~tui
->ids
& (tui
->ids
+ 1);
495 for (size_t tmp
= bit
; tmp
>>= 1; id
++);
496 if (id
>= sizeof(size_t) * 8)
498 size_t styles_size
= (id
+ 1) * UI_STYLE_MAX
* sizeof(CellStyle
);
499 if (styles_size
> tui
->styles_size
) {
500 CellStyle
*styles
= realloc(tui
->styles
, styles_size
);
503 tui
->styles
= styles
;
504 tui
->styles_size
= styles_size
;
506 UiTermWin
*win
= calloc(1, sizeof(UiTermWin
));
510 win
->uiwin
= (UiWin
) {
511 .style_get
= ui_window_style_get
,
512 .status
= ui_window_status
,
513 .options_set
= ui_window_options_set
,
514 .options_get
= ui_window_options_get
,
515 .style_define
= ui_style_define
,
516 .window_width
= ui_window_width
,
517 .window_height
= ui_window_height
,
525 CellStyle
*styles
= &tui
->styles
[win
->id
* UI_STYLE_MAX
];
526 for (int i
= 0; i
< UI_STYLE_MAX
; i
++) {
527 styles
[i
] = (CellStyle
) {
528 .fg
= CELL_COLOR_DEFAULT
,
529 .bg
= CELL_COLOR_DEFAULT
,
530 .attr
= CELL_ATTR_NORMAL
,
534 styles
[UI_STYLE_CURSOR
].attr
|= CELL_ATTR_REVERSE
;
535 styles
[UI_STYLE_CURSOR_PRIMARY
].attr
|= CELL_ATTR_REVERSE
|CELL_ATTR_BLINK
;
536 styles
[UI_STYLE_SELECTION
].attr
|= CELL_ATTR_REVERSE
;
537 styles
[UI_STYLE_COLOR_COLUMN
].attr
|= CELL_ATTR_REVERSE
;
538 styles
[UI_STYLE_STATUS
].attr
|= CELL_ATTR_REVERSE
;
539 styles
[UI_STYLE_STATUS_FOCUSED
].attr
|= CELL_ATTR_REVERSE
|CELL_ATTR_BOLD
;
540 styles
[UI_STYLE_INFO
].attr
|= CELL_ATTR_BOLD
;
541 view_ui(w
->view
, &win
->uiwin
);
544 tui
->windows
->prev
= win
;
545 win
->next
= tui
->windows
;
548 if (text_size(w
->file
->text
) > UI_LARGE_FILE_SIZE
) {
549 options
|= UI_OPTION_LARGE_FILE
;
550 options
&= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
553 ui_window_options_set((UiWin
*)win
, options
);
558 static void ui_info(Ui
*ui
, const char *msg
, va_list ap
) {
559 UiTerm
*tui
= (UiTerm
*)ui
;
560 ui_draw_line(tui
, 0, tui
->height
-1, ' ', UI_STYLE_INFO
);
561 vsnprintf(tui
->info
, sizeof(tui
->info
), msg
, ap
);
564 static void ui_info_hide(Ui
*ui
) {
565 UiTerm
*tui
= (UiTerm
*)ui
;
570 static TermKey
*ui_termkey_new(int fd
) {
571 TermKey
*termkey
= termkey_new(fd
, UI_TERMKEY_FLAGS
);
573 termkey_set_canonflags(termkey
, TERMKEY_CANON_DELBS
);
577 static TermKey
*ui_termkey_reopen(Ui
*ui
, int fd
) {
578 int tty
= open("/dev/tty", O_RDWR
);
581 if (tty
!= fd
&& dup2(tty
, fd
) == -1) {
586 return ui_termkey_new(fd
);
589 static TermKey
*ui_termkey_get(Ui
*ui
) {
590 UiTerm
*tui
= (UiTerm
*)ui
;
594 static void ui_suspend(Ui
*ui
) {
595 UiTerm
*tui
= (UiTerm
*)ui
;
596 ui_term_backend_suspend(tui
);
600 static void ui_resume(Ui
*ui
) {
601 UiTerm
*tui
= (UiTerm
*)ui
;
602 ui_term_backend_resume(tui
);
605 static bool ui_getkey(Ui
*ui
, TermKeyKey
*key
) {
606 UiTerm
*tui
= (UiTerm
*)ui
;
607 TermKeyResult ret
= termkey_getkey(tui
->termkey
, key
);
609 if (ret
== TERMKEY_RES_EOF
) {
610 termkey_destroy(tui
->termkey
);
612 if (!(tui
->termkey
= ui_termkey_reopen(ui
, STDIN_FILENO
)))
613 ui_die_msg(ui
, "Failed to re-open stdin as /dev/tty: %s\n", errno
!= 0 ? strerror(errno
) : "");
617 if (ret
== TERMKEY_RES_AGAIN
) {
619 fd
.fd
= STDIN_FILENO
;
621 if (poll(&fd
, 1, termkey_get_waittime(tui
->termkey
)) == 0)
622 ret
= termkey_getkey_force(tui
->termkey
, key
);
625 return ret
== TERMKEY_RES_KEY
;
628 static void ui_terminal_save(Ui
*ui
) {
629 UiTerm
*tui
= (UiTerm
*)ui
;
630 ui_term_backend_save(tui
);
631 termkey_stop(tui
->termkey
);
634 static void ui_terminal_restore(Ui
*ui
) {
635 UiTerm
*tui
= (UiTerm
*)ui
;
636 termkey_start(tui
->termkey
);
637 ui_term_backend_restore(tui
);
640 static bool ui_init(Ui
*ui
, Vis
*vis
) {
641 UiTerm
*tui
= (UiTerm
*)ui
;
644 setlocale(LC_CTYPE
, "");
646 char *term
= getenv("TERM");
651 if (!(tui
->termkey
= ui_termkey_new(STDIN_FILENO
))) {
652 /* work around libtermkey bug which fails if stdin is /dev/null */
653 if (errno
== EBADF
) {
655 if (!(tui
->termkey
= ui_termkey_reopen(ui
, STDIN_FILENO
)) && errno
== ENXIO
)
656 tui
->termkey
= termkey_new_abstract(term
, UI_TERMKEY_FLAGS
);
662 if (!ui_term_backend_init(tui
, term
))
667 ui_die_msg(ui
, "Failed to start curses interface: %s\n", errno
!= 0 ? strerror(errno
) : "");
671 Ui
*ui_term_new(void) {
672 size_t styles_size
= UI_STYLE_MAX
* sizeof(CellStyle
);
673 CellStyle
*styles
= calloc(1, styles_size
);
676 UiTerm
*tui
= ui_term_backend_new();
681 tui
->styles_size
= styles_size
;
682 tui
->styles
= styles
;
686 .free
= ui_term_free
,
687 .termkey_get
= ui_termkey_get
,
688 .suspend
= ui_suspend
,
691 .window_new
= ui_window_new
,
692 .window_free
= ui_window_free
,
693 .window_focus
= ui_window_focus
,
694 .window_swap
= ui_window_swap
,
697 .arrange
= ui_arrange
,
700 .info_hide
= ui_info_hide
,
702 .terminal_save
= ui_terminal_save
,
703 .terminal_restore
= ui_terminal_restore
,
704 .colors
= ui_term_backend_colors
,
710 void ui_term_free(Ui
*ui
) {
711 UiTerm
*tui
= (UiTerm
*)ui
;
715 ui_window_free((UiWin
*)tui
->windows
);
716 ui_term_backend_free(tui
);
718 termkey_destroy(tui
->termkey
);