mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 05:05:00 -07:00
ui: Rewrite the builtin terminal UI
Now all terminal-handling code was moved to src/nvim/tui, which implements a new terminal UI based on libtermkey and unibilium
This commit is contained in:
parent
e0e41b30c6
commit
9a2dd7c498
@ -182,6 +182,14 @@ include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS})
|
||||
find_package(LuaJit REQUIRED)
|
||||
include_directories(SYSTEM ${LUAJIT_INCLUDE_DIRS})
|
||||
|
||||
set(LIBUNIBILIUM_USE_STATIC ON)
|
||||
find_package(LibUnibilium REQUIRED)
|
||||
include_directories(SYSTEM ${LIBUNIBILIUM_INCLUDE_DIRS})
|
||||
|
||||
set(LIBTERMKEY_USE_STATIC ON)
|
||||
find_package(LibTermkey REQUIRED)
|
||||
include_directories(SYSTEM ${LIBTERMEY_INCLUDE_DIRS})
|
||||
|
||||
find_package(LibIntl)
|
||||
if(LibIntl_FOUND)
|
||||
include_directories(SYSTEM ${LibIntl_INCLUDE_DIRS})
|
||||
|
@ -26,13 +26,16 @@ file(MAKE_DIRECTORY ${GENERATED_DIR}/os)
|
||||
file(MAKE_DIRECTORY ${GENERATED_DIR}/api)
|
||||
file(MAKE_DIRECTORY ${GENERATED_DIR}/api/private)
|
||||
file(MAKE_DIRECTORY ${GENERATED_DIR}/msgpack_rpc)
|
||||
file(MAKE_DIRECTORY ${GENERATED_DIR}/tui)
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR})
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/os)
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api)
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api/private)
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/msgpack_rpc)
|
||||
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/tui)
|
||||
|
||||
file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c msgpack_rpc/*.c)
|
||||
file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c msgpack_rpc/*.c
|
||||
tui/*.c)
|
||||
file(GLOB_RECURSE NEOVIM_HEADERS *.h)
|
||||
|
||||
foreach(sfile ${NEOVIM_SOURCES})
|
||||
@ -173,8 +176,12 @@ list(APPEND NVIM_LINK_LIBRARIES
|
||||
${LIBUV_LIBRARIES}
|
||||
${MSGPACK_LIBRARIES}
|
||||
${LUAJIT_LIBRARIES}
|
||||
${LIBTICKIT_LIBRARIES}
|
||||
${LIBTERMKEY_LIBRARIES}
|
||||
${LIBUNIBILIUM_LIBRARIES}
|
||||
m
|
||||
${CMAKE_THREAD_LIBS_INIT})
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
add_executable(nvim ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES}
|
||||
${NEOVIM_HEADERS})
|
||||
|
@ -383,6 +383,8 @@ int main(int argc, char **argv)
|
||||
TIME_MSG("waiting for return");
|
||||
input_stop_stdin();
|
||||
}
|
||||
|
||||
ui_builtin_start();
|
||||
}
|
||||
|
||||
setmouse(); // may start using the mouse
|
||||
@ -984,6 +986,8 @@ static void command_line_scan(mparm_T *parmp)
|
||||
}
|
||||
|
||||
mch_exit(0);
|
||||
} else if (STRICMP(argv[0] + argv_idx, "headless") == 0) {
|
||||
parmp->headless = true;
|
||||
} else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
|
||||
embedded_mode = true;
|
||||
parmp->headless = true;
|
||||
@ -2092,6 +2096,7 @@ static void usage(void)
|
||||
mch_msg(_(" -i <nviminfo> Use <nviminfo> instead of .nviminfo\n"));
|
||||
mch_msg(_(" --api-info Dump API metadata serialized to msgpack and exit\n"));
|
||||
mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n"));
|
||||
mch_msg(_(" --headless Don't start a user interface\n"));
|
||||
mch_msg(_(" --version Print version information and exit\n"));
|
||||
mch_msg(_(" -h | --help Print this help message and exit\n"));
|
||||
|
||||
|
@ -409,6 +409,8 @@ void mch_exit(int r)
|
||||
if (swapping_screen() && !newline_on_exit)
|
||||
exit_scroll();
|
||||
|
||||
ui_builtin_stop();
|
||||
|
||||
/*
|
||||
* A newline is only required after a message in the alternate screen.
|
||||
* This is set to TRUE by wait_return().
|
||||
|
246
src/nvim/tui/term_input.inl
Normal file
246
src/nvim/tui/term_input.inl
Normal file
@ -0,0 +1,246 @@
|
||||
#include <termkey.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/rstream.h"
|
||||
|
||||
|
||||
struct term_input {
|
||||
int in_fd;
|
||||
TermKey *tk;
|
||||
uv_tty_t input_handle;
|
||||
uv_timer_t timer_handle;
|
||||
RBuffer *read_buffer;
|
||||
RStream *read_stream;
|
||||
};
|
||||
|
||||
static void forward_simple_utf8(TermKeyKey *key)
|
||||
{
|
||||
size_t len = 0;
|
||||
char buf[64];
|
||||
char *ptr = key->utf8;
|
||||
|
||||
while (*ptr) {
|
||||
if (*ptr == '<') {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>");
|
||||
} else {
|
||||
buf[len++] = *ptr;
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
|
||||
buf[len] = 0;
|
||||
input_enqueue((String){.data = buf, .size = len});
|
||||
}
|
||||
|
||||
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
|
||||
{
|
||||
size_t len;
|
||||
char buf[64];
|
||||
|
||||
if (key->type == TERMKEY_TYPE_KEYSYM
|
||||
&& key->code.sym == TERMKEY_SYM_ESCAPE) {
|
||||
len = (size_t)snprintf(buf, sizeof(buf), "<Esc>");
|
||||
} else {
|
||||
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
|
||||
}
|
||||
|
||||
input_enqueue((String){.data = buf, .size = len});
|
||||
}
|
||||
|
||||
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
|
||||
{
|
||||
char buf[64];
|
||||
size_t len = 0;
|
||||
int button, row, col;
|
||||
TermKeyMouseEvent ev;
|
||||
termkey_interpret_mouse(tk, key, &ev, &button, &row, &col);
|
||||
|
||||
if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG) {
|
||||
return;
|
||||
}
|
||||
|
||||
row--; col--; // Termkey uses 1-based coordinates
|
||||
buf[len++] = '<';
|
||||
|
||||
if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
|
||||
}
|
||||
|
||||
if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
|
||||
}
|
||||
|
||||
if (key->modifiers & TERMKEY_KEYMOD_ALT) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
|
||||
}
|
||||
|
||||
if (button == 1) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left");
|
||||
} else if (button == 2) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Middle");
|
||||
} else if (button == 3) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Right");
|
||||
}
|
||||
|
||||
if (ev == TERMKEY_MOUSE_PRESS) {
|
||||
if (button == 4) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp");
|
||||
} else if (button == 5) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown");
|
||||
} else {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse");
|
||||
}
|
||||
} else if (ev == TERMKEY_MOUSE_DRAG) {
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag");
|
||||
}
|
||||
|
||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
|
||||
input_enqueue((String){.data = buf, .size = len});
|
||||
}
|
||||
|
||||
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
|
||||
{
|
||||
return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key);
|
||||
}
|
||||
|
||||
static void timer_cb(uv_timer_t *handle);
|
||||
|
||||
static int get_key_code_timeout(void)
|
||||
{
|
||||
Integer ms = 0;
|
||||
bool timeout = false;
|
||||
// Check 'timeout' and 'ttimeout' to determine if we should send ESC
|
||||
// after 'ttimeoutlen'. See :help 'ttimeout' for more information
|
||||
Error err;
|
||||
timeout = vim_get_option(cstr_as_string("timeout"), &err).data.boolean;
|
||||
if (!timeout) {
|
||||
timeout = vim_get_option(cstr_as_string("ttimeout"), &err).data.boolean;
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
ms = vim_get_option(cstr_as_string("ttimeoutlen"), &err).data.integer;
|
||||
}
|
||||
|
||||
return (int)ms;
|
||||
}
|
||||
|
||||
static void tk_getkeys(TermInput *input, bool force)
|
||||
{
|
||||
TermKeyKey key;
|
||||
TermKeyResult result;
|
||||
|
||||
while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) {
|
||||
if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) {
|
||||
forward_simple_utf8(&key);
|
||||
} else if (key.type == TERMKEY_TYPE_UNICODE ||
|
||||
key.type == TERMKEY_TYPE_FUNCTION ||
|
||||
key.type == TERMKEY_TYPE_KEYSYM) {
|
||||
forward_modified_utf8(input->tk, &key);
|
||||
} else if (key.type == TERMKEY_TYPE_MOUSE) {
|
||||
forward_mouse_event(input->tk, &key);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != TERMKEY_RES_AGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ms = get_key_code_timeout();
|
||||
|
||||
if (ms > 0) {
|
||||
// Stop the current timer if already running
|
||||
uv_timer_stop(&input->timer_handle);
|
||||
uv_timer_start(&input->timer_handle, timer_cb, (uint32_t)ms, 0);
|
||||
} else {
|
||||
tk_getkeys(input, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void timer_cb(uv_timer_t *handle)
|
||||
{
|
||||
tk_getkeys(handle->data, true);
|
||||
}
|
||||
|
||||
static void read_cb(RStream *rstream, void *rstream_data, bool eof)
|
||||
{
|
||||
if (eof) {
|
||||
input_done();
|
||||
return;
|
||||
}
|
||||
|
||||
TermInput *input = rstream_data;
|
||||
|
||||
do {
|
||||
char *ptr = rbuffer_read_ptr(input->read_buffer);
|
||||
size_t len = rbuffer_pending(input->read_buffer);
|
||||
if (len > 1 && ptr[0] == ESC && ptr[1] == NUL) {
|
||||
// skip the ESC and NUL and push one <esc> to the input buffer
|
||||
termkey_push_bytes(input->tk, ptr, 1);
|
||||
rbuffer_consumed(input->read_buffer, 2);
|
||||
tk_getkeys(input, true);
|
||||
continue;
|
||||
}
|
||||
// Find the next 'esc' and push everything up to it(excluding)
|
||||
size_t i;
|
||||
for (i = ptr[0] == ESC ? 1 : 0; i < len; i++) {
|
||||
if (ptr[i] == '\x1b') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t consumed = termkey_push_bytes(input->tk, ptr, i);
|
||||
rbuffer_consumed(input->read_buffer, consumed);
|
||||
tk_getkeys(input, false);
|
||||
} while (rbuffer_pending(input->read_buffer));
|
||||
}
|
||||
|
||||
static TermInput *term_input_new(void)
|
||||
{
|
||||
TermInput *rv = xmalloc(sizeof(TermInput));
|
||||
// read input from stderr if stdin is not a tty
|
||||
rv->in_fd = os_isatty(0) ? 0 : (os_isatty(2) ? 2 : 0);
|
||||
|
||||
// Set terminal encoding based on environment(taken from libtermkey source
|
||||
// code)
|
||||
const char *e;
|
||||
int flags = 0;
|
||||
if (((e = os_getenv("LANG")) || (e = os_getenv("LC_MESSAGES"))
|
||||
|| (e = os_getenv("LC_ALL"))) && (e = strchr(e, '.')) && e++ &&
|
||||
(strcasecmp(e, "UTF-8") == 0 || strcasecmp(e, "UTF8") == 0)) {
|
||||
flags |= TERMKEY_FLAG_UTF8;
|
||||
} else {
|
||||
flags |= TERMKEY_FLAG_RAW;
|
||||
}
|
||||
|
||||
rv->tk = termkey_new_abstract(os_getenv("TERM"), flags);
|
||||
int curflags = termkey_get_canonflags(rv->tk);
|
||||
termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS);
|
||||
// setup input handle
|
||||
uv_tty_init(uv_default_loop(), &rv->input_handle, rv->in_fd, 1);
|
||||
uv_tty_set_mode(&rv->input_handle, UV_TTY_MODE_RAW);
|
||||
rv->input_handle.data = NULL;
|
||||
rv->read_buffer = rbuffer_new(0xfff);
|
||||
rv->read_stream = rstream_new(read_cb, rv->read_buffer, rv);
|
||||
rstream_set_stream(rv->read_stream, (uv_stream_t *)&rv->input_handle);
|
||||
rstream_start(rv->read_stream);
|
||||
// initialize a timer handle for handling ESC with libtermkey
|
||||
uv_timer_init(uv_default_loop(), &rv->timer_handle);
|
||||
rv->timer_handle.data = rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void term_input_destroy(TermInput *input)
|
||||
{
|
||||
uv_tty_reset_mode();
|
||||
uv_timer_stop(&input->timer_handle);
|
||||
rstream_stop(input->read_stream);
|
||||
rstream_free(input->read_stream);
|
||||
uv_close((uv_handle_t *)&input->input_handle, NULL);
|
||||
uv_close((uv_handle_t *)&input->timer_handle, NULL);
|
||||
termkey_destroy(input->tk);
|
||||
event_poll(0); // Run once to remove references to input/timer handles
|
||||
free(input->input_handle.data);
|
||||
free(input);
|
||||
}
|
752
src/nvim/tui/tui.c
Normal file
752
src/nvim/tui/tui.c
Normal file
@ -0,0 +1,752 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <uv.h>
|
||||
#include <unibilium.h>
|
||||
|
||||
#include "nvim/lib/kvec.h"
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/os/event.h"
|
||||
#include "nvim/tui/tui.h"
|
||||
|
||||
typedef struct term_input TermInput;
|
||||
|
||||
#include "term_input.inl"
|
||||
|
||||
typedef struct {
|
||||
int top, bot, left, right;
|
||||
} Rect;
|
||||
|
||||
typedef struct {
|
||||
char data[7];
|
||||
HlAttrs attrs;
|
||||
} Cell;
|
||||
|
||||
typedef struct {
|
||||
PMap(cstr_t) *option_cache;
|
||||
unibi_var_t params[9];
|
||||
char buf[0xffff];
|
||||
size_t bufpos;
|
||||
TermInput *input;
|
||||
uv_loop_t *write_loop;
|
||||
unibi_term *ut;
|
||||
uv_tty_t output_handle;
|
||||
uv_signal_t winch_handle;
|
||||
Rect scroll_region;
|
||||
kvec_t(Rect) invalid_regions;
|
||||
int row, col;
|
||||
int bg, fg;
|
||||
int out_fd;
|
||||
int old_height;
|
||||
bool can_use_terminal_scroll;
|
||||
HlAttrs attrs, print_attrs;
|
||||
Cell **screen;
|
||||
struct {
|
||||
size_t enable_mouse, disable_mouse;
|
||||
} unibi_ext;
|
||||
} TUIData;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "tui/tui.c.generated.h"
|
||||
#endif
|
||||
|
||||
#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
|
||||
|
||||
#define FOREACH_CELL(ui, top, bot, left, right, go, code) \
|
||||
do { \
|
||||
TUIData *data = ui->data; \
|
||||
for (int row = top; row <= bot; ++row) { \
|
||||
Cell *cells = data->screen[row]; \
|
||||
if (go) { \
|
||||
unibi_goto(ui, row, left); \
|
||||
} \
|
||||
for (int col = left; col <= right; ++col) { \
|
||||
Cell *cell = cells + col; \
|
||||
(void)(cell); \
|
||||
code; \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
void tui_start(void)
|
||||
{
|
||||
TUIData *data = xcalloc(1, sizeof(TUIData));
|
||||
UI *ui = xcalloc(1, sizeof(UI));
|
||||
ui->data = data;
|
||||
data->attrs = data->print_attrs = EMPTY_ATTRS;
|
||||
data->fg = data->bg = -1;
|
||||
data->can_use_terminal_scroll = true;
|
||||
data->bufpos = 0;
|
||||
data->option_cache = pmap_new(cstr_t)();
|
||||
|
||||
// write output to stderr if stdout is not a tty
|
||||
data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
|
||||
kv_init(data->invalid_regions);
|
||||
// setup term input
|
||||
data->input = term_input_new();
|
||||
// setup unibilium
|
||||
data->ut = unibi_from_env();
|
||||
if (!data->ut) {
|
||||
// For some reason could not read terminfo file, use a dummy entry that
|
||||
// will be populated with common values by fix_terminfo below
|
||||
data->ut = unibi_dummy();
|
||||
}
|
||||
fix_terminfo(data);
|
||||
// Enter alternate screen and clear
|
||||
unibi_out(ui, unibi_enter_ca_mode, NULL);
|
||||
unibi_out(ui, unibi_clear_screen, NULL);
|
||||
|
||||
// setup output handle in a separate event loop(we wanna do synchronous
|
||||
// write to the tty)
|
||||
data->write_loop = xmalloc(sizeof(uv_loop_t));
|
||||
uv_loop_init(data->write_loop);
|
||||
uv_tty_init(data->write_loop, &data->output_handle, data->out_fd, 0);
|
||||
|
||||
// Obtain screen dimensions
|
||||
update_size(ui);
|
||||
|
||||
// listen for SIGWINCH
|
||||
uv_signal_init(uv_default_loop(), &data->winch_handle);
|
||||
uv_signal_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
|
||||
data->winch_handle.data = ui;
|
||||
|
||||
ui->stop = tui_stop;
|
||||
ui->rgb = false;
|
||||
ui->data = data;
|
||||
ui->resize = tui_resize;
|
||||
ui->clear = tui_clear;
|
||||
ui->eol_clear = tui_eol_clear;
|
||||
ui->cursor_goto = tui_cursor_goto;
|
||||
ui->cursor_on = tui_cursor_on;
|
||||
ui->cursor_off = tui_cursor_off;
|
||||
ui->mouse_on = tui_mouse_on;
|
||||
ui->mouse_off = tui_mouse_off;
|
||||
ui->insert_mode = tui_insert_mode;
|
||||
ui->normal_mode = tui_normal_mode;
|
||||
ui->set_scroll_region = tui_set_scroll_region;
|
||||
ui->scroll = tui_scroll;
|
||||
ui->highlight_set = tui_highlight_set;
|
||||
ui->put = tui_put;
|
||||
ui->bell = tui_bell;
|
||||
ui->visual_bell = tui_visual_bell;
|
||||
ui->update_fg = tui_update_fg;
|
||||
ui->update_bg = tui_update_bg;
|
||||
ui->flush = tui_flush;
|
||||
ui->suspend = tui_suspend;
|
||||
ui->set_title = tui_set_title;
|
||||
ui->set_icon = tui_set_icon;
|
||||
// Attach
|
||||
ui_attach(ui);
|
||||
}
|
||||
|
||||
static void tui_stop(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
// Destroy common stuff
|
||||
kv_destroy(data->invalid_regions);
|
||||
uv_signal_stop(&data->winch_handle);
|
||||
uv_close((uv_handle_t *)&data->winch_handle, NULL);
|
||||
// Destroy input stuff
|
||||
term_input_destroy(data->input);
|
||||
// Destroy output stuff
|
||||
tui_normal_mode(ui);
|
||||
tui_mouse_off(ui);
|
||||
unibi_out(ui, unibi_exit_attribute_mode, NULL);
|
||||
unibi_out(ui, unibi_cursor_normal, NULL);
|
||||
unibi_out(ui, unibi_exit_ca_mode, NULL);
|
||||
flush_buf(ui);
|
||||
uv_close((uv_handle_t *)&data->output_handle, NULL);
|
||||
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
||||
if (uv_loop_close(data->write_loop)) {
|
||||
abort();
|
||||
}
|
||||
free(data->write_loop);
|
||||
unibi_destroy(data->ut);
|
||||
char *opt_value;
|
||||
map_foreach_value(data->option_cache, opt_value, {
|
||||
free(opt_value);
|
||||
});
|
||||
pmap_free(cstr_t)(data->option_cache);
|
||||
destroy_screen(data);
|
||||
free(data);
|
||||
free(ui);
|
||||
ui_detach(ui);
|
||||
}
|
||||
|
||||
static void try_resize(Event ev)
|
||||
{
|
||||
UI *ui = ev.data;
|
||||
update_size(ui);
|
||||
ui_refresh();
|
||||
}
|
||||
|
||||
static void sigwinch_cb(uv_signal_t *handle, int signum)
|
||||
{
|
||||
// Queue the event because resizing can result in recursive event_poll calls
|
||||
event_push((Event) {
|
||||
.data = handle->data,
|
||||
.handler = try_resize
|
||||
}, false);
|
||||
}
|
||||
|
||||
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
|
||||
{
|
||||
return a1.foreground != a2.foreground || a1.background != a2.background
|
||||
|| a1.bold != a2.bold || a1.italic != a2.italic
|
||||
|| a1.undercurl != a2.undercurl || a1.underline != a2.underline
|
||||
|| a1.reverse != a2.reverse;
|
||||
}
|
||||
|
||||
static void update_attrs(UI *ui, HlAttrs attrs)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
unibi_out(ui, unibi_exit_attribute_mode, NULL);
|
||||
|
||||
data->params[0].i = attrs.foreground != -1 ? attrs.foreground : data->fg;
|
||||
if (data->params[0].i != -1) {
|
||||
unibi_out(ui, unibi_set_a_foreground, NULL);
|
||||
}
|
||||
|
||||
data->params[0].i = attrs.background != -1 ? attrs.background : data->bg;
|
||||
if (data->params[0].i != -1) {
|
||||
unibi_out(ui, unibi_set_a_background, NULL);
|
||||
}
|
||||
|
||||
if (attrs.bold) {
|
||||
unibi_out(ui, unibi_enter_bold_mode, NULL);
|
||||
}
|
||||
if (attrs.italic) {
|
||||
unibi_out(ui, unibi_enter_italics_mode, NULL);
|
||||
}
|
||||
if (attrs.underline) {
|
||||
unibi_out(ui, unibi_enter_underline_mode, NULL);
|
||||
}
|
||||
if (attrs.reverse) {
|
||||
unibi_out(ui, unibi_enter_reverse_mode, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_cell(UI *ui, Cell *ptr)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
if (attrs_differ(ptr->attrs, data->print_attrs)) {
|
||||
update_attrs(ui, ptr->attrs);
|
||||
data->print_attrs = ptr->attrs;
|
||||
}
|
||||
out(ui, ptr->data);
|
||||
}
|
||||
|
||||
static void clear_region(UI *ui, int top, int bot, int left, int right,
|
||||
bool refresh)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
HlAttrs clear_attrs = EMPTY_ATTRS;
|
||||
clear_attrs.foreground = data->fg;
|
||||
clear_attrs.background = data->bg;
|
||||
|
||||
bool cleared = false;
|
||||
if (refresh && data->bg == -1 && right == ui->width -1) {
|
||||
// Background is set to the default color and the right edge matches the
|
||||
// screen end, try to use terminal codes for clearing the requested area.
|
||||
if (left == 0) {
|
||||
if (bot == ui->height - 1) {
|
||||
if (top == 0) {
|
||||
unibi_out(ui, unibi_clear_screen, NULL);
|
||||
} else {
|
||||
unibi_goto(ui, top, 0);
|
||||
unibi_out(ui, unibi_clr_eos, NULL);
|
||||
}
|
||||
cleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cleared) {
|
||||
// iterate through each line and clear with clr_eol
|
||||
for (int row = top; row <= bot; ++row) {
|
||||
unibi_goto(ui, row, left);
|
||||
unibi_out(ui, unibi_clr_eol, NULL);
|
||||
}
|
||||
cleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool clear = refresh && !cleared;
|
||||
FOREACH_CELL(ui, top, bot, left, right, clear, {
|
||||
cell->data[0] = ' ';
|
||||
cell->data[1] = 0;
|
||||
cell->attrs = clear_attrs;
|
||||
if (clear) {
|
||||
print_cell(ui, cell);
|
||||
}
|
||||
});
|
||||
|
||||
// restore cursor
|
||||
unibi_goto(ui, data->row, data->col);
|
||||
}
|
||||
|
||||
static void tui_resize(UI *ui, int width, int height)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
destroy_screen(data);
|
||||
|
||||
data->screen = xmalloc((size_t)height * sizeof(Cell *));
|
||||
for (int i = 0; i < height; i++) {
|
||||
data->screen[i] = xcalloc((size_t)width, sizeof(Cell));
|
||||
}
|
||||
|
||||
data->old_height = height;
|
||||
data->scroll_region.top = 0;
|
||||
data->scroll_region.bot = height - 1;
|
||||
data->scroll_region.left = 0;
|
||||
data->scroll_region.right = width - 1;
|
||||
data->row = data->col = 0;
|
||||
}
|
||||
|
||||
static void tui_clear(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
clear_region(ui, data->scroll_region.top, data->scroll_region.bot,
|
||||
data->scroll_region.left, data->scroll_region.right, true);
|
||||
}
|
||||
|
||||
static void tui_eol_clear(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
clear_region(ui, data->row, data->row, data->col,
|
||||
data->scroll_region.right, true);
|
||||
}
|
||||
|
||||
static void tui_cursor_goto(UI *ui, int row, int col)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
data->row = row;
|
||||
data->col = col;
|
||||
unibi_goto(ui, row, col);
|
||||
}
|
||||
|
||||
static void tui_cursor_on(UI *ui)
|
||||
{
|
||||
unibi_out(ui, unibi_cursor_normal, NULL);
|
||||
}
|
||||
|
||||
static void tui_cursor_off(UI *ui)
|
||||
{
|
||||
unibi_out(ui, unibi_cursor_invisible, NULL);
|
||||
}
|
||||
|
||||
static void tui_mouse_on(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
unibi_out(ui, (int)data->unibi_ext.enable_mouse, NULL);
|
||||
}
|
||||
|
||||
static void tui_mouse_off(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
unibi_out(ui, (int)data->unibi_ext.disable_mouse, NULL);
|
||||
}
|
||||
|
||||
static void tui_insert_mode(UI *ui)
|
||||
{
|
||||
unibi_out(ui, -1, "t_SI");
|
||||
}
|
||||
|
||||
static void tui_normal_mode(UI *ui)
|
||||
{
|
||||
unibi_out(ui, -1, "t_EI");
|
||||
}
|
||||
|
||||
static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
|
||||
int right)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
data->scroll_region.top = top;
|
||||
data->scroll_region.bot = bot;
|
||||
data->scroll_region.left = left;
|
||||
data->scroll_region.right = right;
|
||||
|
||||
data->can_use_terminal_scroll =
|
||||
left == 0 && right == ui->width - 1
|
||||
&& ((top == 0 && bot == ui->height - 1)
|
||||
|| unibi_get_str(data->ut, unibi_change_scroll_region));
|
||||
}
|
||||
|
||||
static void tui_scroll(UI *ui, int count)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
int top = data->scroll_region.top;
|
||||
int bot = data->scroll_region.bot;
|
||||
int left = data->scroll_region.left;
|
||||
int right = data->scroll_region.right;
|
||||
|
||||
if (data->can_use_terminal_scroll) {
|
||||
// Change terminal scroll region and move cursor to the top
|
||||
data->params[0].i = top;
|
||||
data->params[1].i = bot;
|
||||
unibi_out(ui, unibi_change_scroll_region, NULL);
|
||||
unibi_goto(ui, top, left);
|
||||
}
|
||||
|
||||
// Compute start/stop/step for the loop below, also use terminal scroll
|
||||
// if possible
|
||||
int start, stop, step;
|
||||
if (count > 0) {
|
||||
start = top;
|
||||
stop = bot - count + 1;
|
||||
step = 1;
|
||||
if (data->can_use_terminal_scroll) {
|
||||
if (count == 1) {
|
||||
unibi_out(ui, unibi_delete_line, NULL);
|
||||
} else {
|
||||
data->params[0].i = count;
|
||||
unibi_out(ui, unibi_parm_delete_line, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
start = bot;
|
||||
stop = top - count - 1;
|
||||
step = -1;
|
||||
if (data->can_use_terminal_scroll) {
|
||||
if (count == -1) {
|
||||
unibi_out(ui, unibi_insert_line, NULL);
|
||||
} else {
|
||||
data->params[0].i = -count;
|
||||
unibi_out(ui, unibi_parm_insert_line, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data->can_use_terminal_scroll) {
|
||||
// Restore terminal scroll region and cursor
|
||||
data->params[0].i = 0;
|
||||
data->params[1].i = ui->height - 1;
|
||||
unibi_out(ui, unibi_change_scroll_region, NULL);
|
||||
unibi_goto(ui, data->row, data->col);
|
||||
}
|
||||
|
||||
int i;
|
||||
// Scroll internal screen
|
||||
for (i = start; i != stop; i += step) {
|
||||
Cell *target_row = data->screen[i] + left;
|
||||
Cell *source_row = data->screen[i + count] + left;
|
||||
memcpy(target_row, source_row, sizeof(Cell) * (size_t)(right - left + 1));
|
||||
}
|
||||
|
||||
// clear emptied region, updating the terminal if its builtin scrolling
|
||||
// facility was used. This is done when the background color is not the
|
||||
// default, since scrolling may leave wrong background in the cleared area.
|
||||
bool update_clear = data->bg != -1 && data->can_use_terminal_scroll;
|
||||
if (count > 0) {
|
||||
clear_region(ui, stop, stop + count - 1, left, right, update_clear);
|
||||
} else {
|
||||
clear_region(ui, stop + count + 1, stop, left, right, update_clear);
|
||||
}
|
||||
|
||||
if (!data->can_use_terminal_scroll) {
|
||||
// Mark the entire scroll region as invalid for redrawing later
|
||||
invalidate(ui, data->scroll_region.top, data->scroll_region.bot,
|
||||
data->scroll_region.left, data->scroll_region.right);
|
||||
}
|
||||
}
|
||||
|
||||
static void tui_highlight_set(UI *ui, HlAttrs attrs)
|
||||
{
|
||||
((TUIData *)ui->data)->attrs = attrs;
|
||||
}
|
||||
|
||||
static void tui_put(UI *ui, uint8_t *text, size_t size)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
Cell *cell = data->screen[data->row] + data->col;
|
||||
cell->data[size] = 0;
|
||||
cell->attrs = data->attrs;
|
||||
|
||||
if (text) {
|
||||
memcpy(cell->data, text, size);
|
||||
}
|
||||
|
||||
print_cell(ui, cell);
|
||||
data->col += 1;
|
||||
}
|
||||
|
||||
static void tui_bell(UI *ui)
|
||||
{
|
||||
unibi_out(ui, unibi_bell, NULL);
|
||||
}
|
||||
|
||||
static void tui_visual_bell(UI *ui)
|
||||
{
|
||||
unibi_out(ui, unibi_flash_screen, NULL);
|
||||
}
|
||||
|
||||
static void tui_update_fg(UI *ui, int fg)
|
||||
{
|
||||
((TUIData *)ui->data)->fg = fg;
|
||||
}
|
||||
|
||||
static void tui_update_bg(UI *ui, int bg)
|
||||
{
|
||||
((TUIData *)ui->data)->bg = bg;
|
||||
}
|
||||
|
||||
static void tui_flush(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
|
||||
while (kv_size(data->invalid_regions)) {
|
||||
Rect r = kv_pop(data->invalid_regions);
|
||||
FOREACH_CELL(ui, r.top, r.bot, r.left, r.right, true, {
|
||||
print_cell(ui, cell);
|
||||
});
|
||||
}
|
||||
|
||||
unibi_goto(ui, data->row, data->col);
|
||||
flush_buf(ui);
|
||||
}
|
||||
|
||||
static void tui_suspend(UI *ui)
|
||||
{
|
||||
tui_stop(ui);
|
||||
kill(0, SIGTSTP);
|
||||
tui_start();
|
||||
}
|
||||
|
||||
static void tui_set_title(UI *ui, char *title)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
if (!(unibi_get_str(data->ut, unibi_to_status_line)
|
||||
&& unibi_get_str(data->ut, unibi_from_status_line))) {
|
||||
return;
|
||||
}
|
||||
unibi_out(ui, unibi_to_status_line, NULL);
|
||||
out(ui, title);
|
||||
unibi_out(ui, unibi_from_status_line, NULL);
|
||||
}
|
||||
|
||||
static void tui_set_icon(UI *ui, char *icon)
|
||||
{
|
||||
}
|
||||
|
||||
static void invalidate(UI *ui, int top, int bot, int left, int right)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
Rect *intersects = NULL;
|
||||
// Increase dimensions before comparing to ensure adjacent regions are
|
||||
// treated as intersecting
|
||||
--top;
|
||||
++bot;
|
||||
--left;
|
||||
++right;
|
||||
|
||||
for (size_t i = 0; i < kv_size(data->invalid_regions); i++) {
|
||||
Rect *r = &kv_A(data->invalid_regions, i);
|
||||
if (!(top > r->bot || bot < r->top
|
||||
|| left > r->right || right < r->left)) {
|
||||
intersects = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++top;
|
||||
--bot;
|
||||
++left;
|
||||
--right;
|
||||
|
||||
if (intersects) {
|
||||
// If top/bot/left/right intersects with a invalid rect, we replace it
|
||||
// by the union
|
||||
intersects->top = MIN(top, intersects->top);
|
||||
intersects->bot = MAX(bot, intersects->bot);
|
||||
intersects->left = MIN(left, intersects->left);
|
||||
intersects->right = MAX(right, intersects->right);
|
||||
} else {
|
||||
// Else just add a new entry;
|
||||
kv_push(Rect, data->invalid_regions, ((Rect){top, bot, left, right}));
|
||||
}
|
||||
}
|
||||
|
||||
static void update_size(UI *ui)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
int width = 0, height = 0;
|
||||
// 1 - try from a system call(ioctl/TIOCGWINSZ on unix)
|
||||
if (!uv_tty_get_winsize(&data->output_handle, &width, &height)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// 2 - use $LINES/$COLUMNS if available
|
||||
const char *val;
|
||||
int advance;
|
||||
if ((val = os_getenv("LINES"))
|
||||
&& sscanf(val, "%d%n", &height, &advance) != EOF && advance
|
||||
&& (val = os_getenv("COLUMNS"))
|
||||
&& sscanf(val, "%d%n", &width, &advance) != EOF && advance) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// 3- read from terminfo if available
|
||||
height = unibi_get_num(data->ut, unibi_lines);
|
||||
width = unibi_get_num(data->ut, unibi_columns);
|
||||
|
||||
end:
|
||||
if (width <= 0 || height <= 0) {
|
||||
// use a default of 80x24
|
||||
width = 80;
|
||||
height = 24;
|
||||
}
|
||||
|
||||
ui->width = width;
|
||||
ui->height = height;
|
||||
}
|
||||
|
||||
static void unibi_goto(UI *ui, int row, int col)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
data->params[0].i = row;
|
||||
data->params[1].i = col;
|
||||
unibi_out(ui, unibi_cursor_address, NULL);
|
||||
}
|
||||
|
||||
static void unibi_out(UI *ui, int unibi_index, char *nvim_override)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
|
||||
const char *str = NULL;
|
||||
|
||||
if (nvim_override) {
|
||||
str = get_term_option(ui, nvim_override);
|
||||
} else if (unibi_index >= 0) {
|
||||
if (unibi_index < unibi_string_begin_) {
|
||||
str = unibi_get_ext_str(data->ut, (unsigned)unibi_index);
|
||||
} else {
|
||||
str = unibi_get_str(data->ut, (unsigned)unibi_index);
|
||||
}
|
||||
}
|
||||
|
||||
if (str) {
|
||||
data->bufpos += unibi_run(str, data->params, data->buf + data->bufpos,
|
||||
sizeof(data->buf) - data->bufpos);
|
||||
}
|
||||
}
|
||||
|
||||
static void out(UI *ui, const char *str)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
data->bufpos += (size_t)snprintf(data->buf + data->bufpos,
|
||||
sizeof(data->buf) - data->bufpos, "%s", str);
|
||||
}
|
||||
|
||||
static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str,
|
||||
const char *val)
|
||||
{
|
||||
if (!unibi_get_str(ut, str)) {
|
||||
unibi_set_str(ut, str, val);
|
||||
}
|
||||
}
|
||||
|
||||
static void fix_terminfo(TUIData *data)
|
||||
{
|
||||
unibi_term *ut = data->ut;
|
||||
|
||||
const char *term = os_getenv("TERM");
|
||||
if (!term) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
#define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1))
|
||||
|
||||
if (STARTS_WITH(term, "rxvt")) {
|
||||
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b[m\x1b(B");
|
||||
unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<20/>\x1b[?5l");
|
||||
unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
|
||||
} else if (STARTS_WITH(term, "screen")) {
|
||||
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
|
||||
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
|
||||
}
|
||||
|
||||
if (STARTS_WITH(term, "xterm") || STARTS_WITH(term, "rxvt")) {
|
||||
unibi_set_if_empty(ut, unibi_cursor_normal, "\x1b[?12l\x1b[?25h");
|
||||
unibi_set_if_empty(ut, unibi_cursor_invisible, "\x1b[?25l");
|
||||
unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<100/>\x1b[?5l");
|
||||
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b(B\x1b[m");
|
||||
unibi_set_if_empty(ut, unibi_change_scroll_region, "\x1b[%i%p1%d;%p2%dr");
|
||||
unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[2J");
|
||||
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]2");
|
||||
unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
|
||||
}
|
||||
|
||||
end:
|
||||
// Fill some empty slots with common terminal strings
|
||||
data->unibi_ext.enable_mouse = unibi_add_ext_str(ut, NULL,
|
||||
"\x1b[?1002h\x1b[?1006h");
|
||||
data->unibi_ext.disable_mouse = unibi_add_ext_str(ut, NULL,
|
||||
"\x1b[?1002l\x1b[?1006l");
|
||||
|
||||
unibi_set_if_empty(ut, unibi_cursor_address, "\x1b[%i%p1%d;%p2%dH");
|
||||
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b[0;10m");
|
||||
unibi_set_if_empty(ut, unibi_set_a_foreground,
|
||||
"\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m");
|
||||
unibi_set_if_empty(ut, unibi_set_a_background,
|
||||
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m");
|
||||
unibi_set_if_empty(ut, unibi_enter_bold_mode, "\x1b[1m");
|
||||
unibi_set_if_empty(ut, unibi_enter_underline_mode, "\x1b[4m");
|
||||
unibi_set_if_empty(ut, unibi_enter_reverse_mode, "\x1b[7m");
|
||||
unibi_set_if_empty(ut, unibi_bell, "\x07");
|
||||
unibi_set_if_empty(data->ut, unibi_enter_ca_mode, "\x1b[?1049h");
|
||||
unibi_set_if_empty(data->ut, unibi_exit_ca_mode, "\x1b[?1049l");
|
||||
unibi_set_if_empty(ut, unibi_delete_line, "\x1b[M");
|
||||
unibi_set_if_empty(ut, unibi_parm_delete_line, "\x1b[%p1%dM");
|
||||
unibi_set_if_empty(ut, unibi_insert_line, "\x1b[L");
|
||||
unibi_set_if_empty(ut, unibi_parm_insert_line, "\x1b[%p1%dL");
|
||||
unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[J");
|
||||
unibi_set_if_empty(ut, unibi_clr_eol, "\x1b[K");
|
||||
unibi_set_if_empty(ut, unibi_clr_eos, "\x1b[J");
|
||||
}
|
||||
|
||||
static void flush_buf(UI *ui)
|
||||
{
|
||||
static uv_write_t req;
|
||||
static uv_buf_t buf;
|
||||
TUIData *data = ui->data;
|
||||
buf.base = data->buf;
|
||||
buf.len = data->bufpos;
|
||||
uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL);
|
||||
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
||||
data->bufpos = 0;
|
||||
}
|
||||
|
||||
static char *get_term_option(UI *ui, char *option)
|
||||
{
|
||||
TUIData *data = ui->data;
|
||||
|
||||
char *rv = pmap_get(cstr_t)(data->option_cache, option);
|
||||
if (!rv) {
|
||||
Error err;
|
||||
Object val = vim_get_option(cstr_as_string(option), &err);
|
||||
if (val.type == kObjectTypeString) {
|
||||
rv = val.data.string.data;
|
||||
pmap_put(cstr_t)(data->option_cache, option, rv);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void destroy_screen(TUIData *data)
|
||||
{
|
||||
if (data->screen) {
|
||||
for (int i = 0; i < data->old_height; i++) {
|
||||
free(data->screen[i]);
|
||||
}
|
||||
free(data->screen);
|
||||
}
|
||||
}
|
8
src/nvim/tui/tui.h
Normal file
8
src/nvim/tui/tui.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef NVIM_TUI_TUI_H
|
||||
#define NVIM_TUI_TUI_H
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "tui/tui.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_TUI_TUI_H
|
@ -46,6 +46,7 @@
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/term.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/tui/tui.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ui.c.generated.h"
|
||||
@ -81,8 +82,18 @@ static int height, width;
|
||||
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6
|
||||
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
|
||||
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
|
||||
#define UI_CALL_MORE(method, ...) ui->method(ui, __VA_ARGS__)
|
||||
#define UI_CALL_ZERO(method) ui->method(ui)
|
||||
#define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__)
|
||||
#define UI_CALL_ZERO(method) if (ui->method) ui->method(ui)
|
||||
|
||||
void ui_builtin_start(void)
|
||||
{
|
||||
tui_start();
|
||||
}
|
||||
|
||||
void ui_builtin_stop(void)
|
||||
{
|
||||
UI_CALL(stop);
|
||||
}
|
||||
|
||||
void ui_write(uint8_t *s, int len)
|
||||
{
|
||||
|
@ -38,6 +38,7 @@ struct ui_t {
|
||||
void (*suspend)(UI *ui);
|
||||
void (*set_title)(UI *ui, char *title);
|
||||
void (*set_icon)(UI *ui, char *icon);
|
||||
void (*stop)(UI *ui);
|
||||
};
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
Loading…
Reference in New Issue
Block a user