mirror of
https://github.com/neovim/neovim.git
synced 2024-12-31 17:13:26 -07:00
Merge PR #1605 'Abstract UI termcap'
This commit is contained in:
commit
c5b9e5d1d3
@ -252,6 +252,11 @@ end
|
||||
output:write([[
|
||||
static Map(String, MsgpackRpcRequestHandler) *methods = NULL;
|
||||
|
||||
void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandler handler)
|
||||
{
|
||||
map_put(String, MsgpackRpcRequestHandler)(methods, method, handler);
|
||||
}
|
||||
|
||||
void msgpack_rpc_init_method_table(void)
|
||||
{
|
||||
methods = map_new(String, MsgpackRpcRequestHandler)();
|
||||
@ -263,7 +268,7 @@ void msgpack_rpc_init_method_table(void)
|
||||
local max_fname_len = 0
|
||||
for i = 1, #functions do
|
||||
local fn = functions[i]
|
||||
output:write(' map_put(String, MsgpackRpcRequestHandler)(methods, '..
|
||||
output:write(' msgpack_rpc_add_method_handler('..
|
||||
'(String) {.data = "'..fn.name..'", '..
|
||||
'.size = sizeof("'..fn.name..'") - 1}, '..
|
||||
'(MsgpackRpcRequestHandler) {.fn = handle_'.. fn.name..
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/misc2.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/term.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/os/input.h"
|
||||
@ -546,6 +547,11 @@ void vim_unsubscribe(uint64_t channel_id, String event)
|
||||
channel_unsubscribe(channel_id, e);
|
||||
}
|
||||
|
||||
Integer vim_name_to_color(String name)
|
||||
{
|
||||
return name_to_color((uint8_t *)name.data);
|
||||
}
|
||||
|
||||
Array vim_get_api_info(uint64_t channel_id)
|
||||
{
|
||||
Array rv = ARRAY_DICT_INIT;
|
||||
|
@ -1984,10 +1984,6 @@ void free_cmdline_buf(void)
|
||||
*/
|
||||
static void draw_cmdline(int start, int len)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
|
||||
if (cmdline_star > 0)
|
||||
|
@ -2513,6 +2513,13 @@ fix_input_buffer (
|
||||
int script /* TRUE when reading from a script */
|
||||
)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
// Should not escape K_SPECIAL/CSI while in embedded mode because vim key
|
||||
// codes keys are processed in input.c/input_enqueue.
|
||||
buf[len] = NUL;
|
||||
return len;
|
||||
}
|
||||
|
||||
int i;
|
||||
char_u *p = buf;
|
||||
|
||||
|
@ -465,6 +465,8 @@ EXTERN int highlight_stlnc[9]; /* On top of user */
|
||||
EXTERN int cterm_normal_fg_color INIT(= 0);
|
||||
EXTERN int cterm_normal_fg_bold INIT(= 0);
|
||||
EXTERN int cterm_normal_bg_color INIT(= 0);
|
||||
EXTERN RgbValue normal_fg INIT(= -1);
|
||||
EXTERN RgbValue normal_bg INIT(= -1);
|
||||
|
||||
EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */
|
||||
EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */
|
||||
@ -1251,6 +1253,8 @@ EXTERN int curr_tmode INIT(= TMODE_COOK); /* contains current terminal mode */
|
||||
|
||||
// If a msgpack-rpc channel should be started over stdin/stdout
|
||||
EXTERN bool embedded_mode INIT(= false);
|
||||
// Using the "abstract_ui" termcap
|
||||
EXTERN bool abstract_ui INIT(= false);
|
||||
|
||||
/// Used to track the status of external functions.
|
||||
/// Currently only used for iconv().
|
||||
|
@ -265,13 +265,6 @@ int main(int argc, char **argv)
|
||||
term_init();
|
||||
TIME_MSG("shell init");
|
||||
|
||||
event_init();
|
||||
|
||||
if (!embedded_mode) {
|
||||
// Print a warning if stdout is not a terminal.
|
||||
check_tty(¶ms);
|
||||
}
|
||||
|
||||
/* This message comes before term inits, but after setting "silent_mode"
|
||||
* when the input is not a tty. */
|
||||
if (GARGCOUNT > 1 && !silent_mode)
|
||||
@ -283,6 +276,7 @@ int main(int argc, char **argv)
|
||||
// initial screen size of 80x20
|
||||
full_screen = true;
|
||||
screen_resize(80, 20, false);
|
||||
termcapinit((uint8_t *)"abstract_ui");
|
||||
} else {
|
||||
// set terminal name and get terminal capabilities (will set full_screen)
|
||||
// Do some initialization of the screen
|
||||
@ -292,6 +286,16 @@ int main(int argc, char **argv)
|
||||
TIME_MSG("Termcap init");
|
||||
}
|
||||
|
||||
event_init();
|
||||
|
||||
if (abstract_ui) {
|
||||
t_colors = 256;
|
||||
} else {
|
||||
// Print a warning if stdout is not a terminal TODO(tarruda): Remove this
|
||||
// check once the new terminal UI is implemented
|
||||
check_tty(¶ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the default values for the options that use Rows and Columns.
|
||||
*/
|
||||
@ -424,7 +428,6 @@ int main(int argc, char **argv)
|
||||
TIME_MSG("waiting for return");
|
||||
}
|
||||
|
||||
if (!embedded_mode) {
|
||||
starttermcap(); // start termcap if not done by wait_return()
|
||||
TIME_MSG("start termcap");
|
||||
may_req_ambiguous_char_width();
|
||||
@ -435,7 +438,6 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
scroll_start(); // may scroll the screen to the right position
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't clear the screen when starting in Ex mode, unless using the GUI.
|
||||
|
@ -452,7 +452,7 @@ void setmouse(void)
|
||||
return;
|
||||
|
||||
/* don't switch mouse on when not in raw mode (Ex mode) */
|
||||
if (cur_tmode != TMODE_RAW) {
|
||||
if (!abstract_ui && cur_tmode != TMODE_RAW) {
|
||||
mch_setmouse(false);
|
||||
return;
|
||||
}
|
||||
@ -470,10 +470,11 @@ void setmouse(void)
|
||||
else
|
||||
checkfor = MOUSE_NORMAL; /* assume normal mode */
|
||||
|
||||
if (mouse_has(checkfor))
|
||||
mch_setmouse(true);
|
||||
else
|
||||
mch_setmouse(false);
|
||||
if (mouse_has(checkfor)) {
|
||||
ui_mouse_on();
|
||||
} else {
|
||||
ui_mouse_off();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/msgpack_rpc/channel.h"
|
||||
#include "nvim/msgpack_rpc/remote_ui.h"
|
||||
#include "nvim/os/event.h"
|
||||
#include "nvim/os/rstream.h"
|
||||
#include "nvim/os/rstream_defs.h"
|
||||
@ -100,6 +101,17 @@ void channel_init(void)
|
||||
if (embedded_mode) {
|
||||
channel_from_stdio();
|
||||
}
|
||||
|
||||
if (abstract_ui) {
|
||||
// Add handler for "attach_ui"
|
||||
remote_ui_init();
|
||||
String method = cstr_as_string("attach_ui");
|
||||
MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = true};
|
||||
msgpack_rpc_add_method_handler(method, handler);
|
||||
method = cstr_as_string("detach_ui");
|
||||
handler.fn = remote_ui_detach;
|
||||
msgpack_rpc_add_method_handler(method, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// Teardown the module
|
||||
@ -645,6 +657,10 @@ static void on_stdio_close(Event e)
|
||||
|
||||
static void free_channel(Channel *channel)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
remote_ui_disconnect(channel->id);
|
||||
}
|
||||
|
||||
pmap_del(uint64_t)(channels, channel->id);
|
||||
msgpack_unpacker_free(channel->unpacker);
|
||||
|
||||
|
@ -19,6 +19,10 @@ typedef struct {
|
||||
/// Initializes the msgpack-rpc method table
|
||||
void msgpack_rpc_init_method_table(void);
|
||||
|
||||
// Add a handler to the method table
|
||||
void msgpack_rpc_add_method_handler(String method,
|
||||
MsgpackRpcRequestHandler handler);
|
||||
|
||||
void msgpack_rpc_init_function_metadata(Dictionary *metadata);
|
||||
|
||||
/// Dispatches to the actual API function after basic payload validation by
|
||||
|
280
src/nvim/msgpack_rpc/remote_ui.c
Normal file
280
src/nvim/msgpack_rpc/remote_ui.c
Normal file
@ -0,0 +1,280 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/msgpack_rpc/remote_ui.h"
|
||||
#include "nvim/msgpack_rpc/channel.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "msgpack_rpc/remote_ui.c.generated.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint64_t channel_id;
|
||||
Array buffer;
|
||||
} UIData;
|
||||
|
||||
static PMap(uint64_t) *connected_uis = NULL;
|
||||
|
||||
void remote_ui_init(void)
|
||||
{
|
||||
connected_uis = pmap_new(uint64_t)();
|
||||
}
|
||||
|
||||
Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args,
|
||||
Error *error)
|
||||
{
|
||||
if (pmap_has(uint64_t)(connected_uis, channel_id)) {
|
||||
api_set_error(error, Exception, _("UI already attached for channel"));
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (args.size != 2 || args.items[0].type != kObjectTypeInteger
|
||||
|| args.items[1].type != kObjectTypeInteger
|
||||
|| args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) {
|
||||
api_set_error(error, Validation,
|
||||
_("Arguments must be a pair of positive integers "
|
||||
"representing the remote screen width/height"));
|
||||
return NIL;
|
||||
}
|
||||
UIData *data = xmalloc(sizeof(UIData));
|
||||
data->channel_id = channel_id;
|
||||
data->buffer = (Array)ARRAY_DICT_INIT;
|
||||
UI *ui = xcalloc(1, sizeof(UI));
|
||||
ui->width = (int)args.items[0].data.integer;
|
||||
ui->height = (int)args.items[1].data.integer;
|
||||
ui->data = data;
|
||||
ui->resize = remote_ui_resize;
|
||||
ui->clear = remote_ui_clear;
|
||||
ui->eol_clear = remote_ui_eol_clear;
|
||||
ui->cursor_goto = remote_ui_cursor_goto;
|
||||
ui->cursor_on = remote_ui_cursor_on;
|
||||
ui->cursor_off = remote_ui_cursor_off;
|
||||
ui->mouse_on = remote_ui_mouse_on;
|
||||
ui->mouse_off = remote_ui_mouse_off;
|
||||
ui->insert_mode = remote_ui_insert_mode;
|
||||
ui->normal_mode = remote_ui_normal_mode;
|
||||
ui->set_scroll_region = remote_ui_set_scroll_region;
|
||||
ui->scroll = remote_ui_scroll;
|
||||
ui->highlight_set = remote_ui_highlight_set;
|
||||
ui->put = remote_ui_put;
|
||||
ui->bell = remote_ui_bell;
|
||||
ui->visual_bell = remote_ui_visual_bell;
|
||||
ui->flush = remote_ui_flush;
|
||||
ui->suspend = remote_ui_suspend;
|
||||
pmap_put(uint64_t)(connected_uis, channel_id, ui);
|
||||
ui_attach(ui);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Object remote_ui_detach(uint64_t channel_id, uint64_t request_id, Array args,
|
||||
Error *error)
|
||||
{
|
||||
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
|
||||
api_set_error(error, Exception, _("UI is not attached for channel"));
|
||||
}
|
||||
remote_ui_disconnect(channel_id);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
void remote_ui_disconnect(uint64_t channel_id)
|
||||
{
|
||||
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
|
||||
if (!ui) {
|
||||
return;
|
||||
}
|
||||
UIData *data = ui->data;
|
||||
// destroy pending screen updates
|
||||
api_free_array(data->buffer);
|
||||
pmap_del(uint64_t)(connected_uis, channel_id);
|
||||
free(ui->data);
|
||||
ui_detach(ui);
|
||||
free(ui);
|
||||
}
|
||||
|
||||
static void push_call(UI *ui, char *name, Array args)
|
||||
{
|
||||
Array call = ARRAY_DICT_INIT;
|
||||
UIData *data = ui->data;
|
||||
|
||||
// To optimize data transfer(especially for "put"), we bundle adjacent
|
||||
// calls to same method together, so only add a new call entry if the last
|
||||
// method call is different from "name"
|
||||
if (kv_size(data->buffer)) {
|
||||
call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array;
|
||||
}
|
||||
|
||||
if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) {
|
||||
call = (Array)ARRAY_DICT_INIT;
|
||||
ADD(data->buffer, ARRAY_OBJ(call));
|
||||
ADD(call, STRING_OBJ(cstr_to_string(name)));
|
||||
}
|
||||
|
||||
ADD(call, ARRAY_OBJ(args));
|
||||
kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
|
||||
}
|
||||
|
||||
static void remote_ui_resize(UI *ui, int width, int height)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
ADD(args, INTEGER_OBJ(width));
|
||||
ADD(args, INTEGER_OBJ(height));
|
||||
push_call(ui, "resize", args);
|
||||
}
|
||||
|
||||
static void remote_ui_clear(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "clear", args);
|
||||
}
|
||||
|
||||
static void remote_ui_eol_clear(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "eol_clear", args);
|
||||
}
|
||||
|
||||
static void remote_ui_cursor_goto(UI *ui, int row, int col)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
ADD(args, INTEGER_OBJ(row));
|
||||
ADD(args, INTEGER_OBJ(col));
|
||||
push_call(ui, "cursor_goto", args);
|
||||
}
|
||||
|
||||
static void remote_ui_cursor_on(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "cursor_on", args);
|
||||
}
|
||||
|
||||
static void remote_ui_cursor_off(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "cursor_off", args);
|
||||
}
|
||||
|
||||
static void remote_ui_mouse_on(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "mouse_on", args);
|
||||
}
|
||||
|
||||
static void remote_ui_mouse_off(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "mouse_off", args);
|
||||
}
|
||||
|
||||
static void remote_ui_insert_mode(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "insert_mode", args);
|
||||
}
|
||||
|
||||
static void remote_ui_normal_mode(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "normal_mode", args);
|
||||
}
|
||||
|
||||
static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left,
|
||||
int right)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
ADD(args, INTEGER_OBJ(top));
|
||||
ADD(args, INTEGER_OBJ(bot));
|
||||
ADD(args, INTEGER_OBJ(left));
|
||||
ADD(args, INTEGER_OBJ(right));
|
||||
push_call(ui, "set_scroll_region", args);
|
||||
}
|
||||
|
||||
static void remote_ui_scroll(UI *ui, int count)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
ADD(args, INTEGER_OBJ(count));
|
||||
push_call(ui, "scroll", args);
|
||||
}
|
||||
|
||||
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
Dictionary hl = ARRAY_DICT_INIT;
|
||||
|
||||
if (attrs.bold) {
|
||||
PUT(hl, "bold", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.standout) {
|
||||
PUT(hl, "standout", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.underline) {
|
||||
PUT(hl, "underline", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.undercurl) {
|
||||
PUT(hl, "undercurl", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.italic) {
|
||||
PUT(hl, "italic", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.reverse) {
|
||||
PUT(hl, "reverse", BOOLEAN_OBJ(true));
|
||||
}
|
||||
|
||||
if (attrs.foreground != -1) {
|
||||
PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground));
|
||||
}
|
||||
|
||||
if (attrs.background != -1) {
|
||||
PUT(hl, "background", INTEGER_OBJ(attrs.background));
|
||||
}
|
||||
|
||||
ADD(args, DICTIONARY_OBJ(hl));
|
||||
push_call(ui, "highlight_set", args);
|
||||
}
|
||||
|
||||
static void remote_ui_put(UI *ui, uint8_t *data, size_t size)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
String str = {.data = xmemdupz(data, size), .size = size};
|
||||
ADD(args, STRING_OBJ(str));
|
||||
push_call(ui, "put", args);
|
||||
}
|
||||
|
||||
static void remote_ui_bell(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "bell", args);
|
||||
}
|
||||
|
||||
static void remote_ui_visual_bell(UI *ui)
|
||||
{
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
push_call(ui, "visual_bell", args);
|
||||
}
|
||||
|
||||
static void remote_ui_flush(UI *ui)
|
||||
{
|
||||
UIData *data = ui->data;
|
||||
channel_send_event(data->channel_id, "redraw", data->buffer);
|
||||
data->buffer = (Array)ARRAY_DICT_INIT;
|
||||
}
|
||||
|
||||
static void remote_ui_suspend(UI *ui)
|
||||
{
|
||||
UIData *data = ui->data;
|
||||
remote_ui_disconnect(data->channel_id);
|
||||
}
|
9
src/nvim/msgpack_rpc/remote_ui.h
Normal file
9
src/nvim/msgpack_rpc/remote_ui.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef NVIM_MSGPACK_RPC_REMOTE_UI_H
|
||||
#define NVIM_MSGPACK_RPC_REMOTE_UI_H
|
||||
|
||||
#include "nvim/ui.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "msgpack_rpc/remote_ui.h.generated.h"
|
||||
#endif
|
||||
#endif // NVIM_MSGPACK_RPC_REMOTE_UI_H
|
@ -46,7 +46,7 @@ void input_init(void)
|
||||
{
|
||||
input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN);
|
||||
|
||||
if (embedded_mode) {
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ void input_init(void)
|
||||
|
||||
void input_teardown(void)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ void input_teardown(void)
|
||||
// Listen for input
|
||||
void input_start(void)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ void input_start(void)
|
||||
// Stop listening for input
|
||||
void input_stop(void)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -180,7 +180,23 @@ void input_buffer_restore(String str)
|
||||
|
||||
size_t input_enqueue(String keys)
|
||||
{
|
||||
size_t rv = rbuffer_write(input_buffer, keys.data, keys.size);
|
||||
char *ptr = keys.data, *end = ptr + keys.size;
|
||||
|
||||
while (rbuffer_available(input_buffer) >= 6 && ptr < end) {
|
||||
int new_size = trans_special((char_u **)&ptr,
|
||||
(char_u *)rbuffer_write_ptr(input_buffer),
|
||||
false);
|
||||
if (!new_size) {
|
||||
// copy the character unmodified
|
||||
*rbuffer_write_ptr(input_buffer) = *ptr++;
|
||||
new_size = 1;
|
||||
}
|
||||
// TODO(tarruda): Don't produce past unclosed '<' characters, except if
|
||||
// there's a lot of characters after the '<'
|
||||
rbuffer_produced(input_buffer, (size_t)new_size);
|
||||
}
|
||||
|
||||
size_t rv = (size_t)(ptr - keys.data);
|
||||
process_interrupts();
|
||||
return rv;
|
||||
}
|
||||
@ -255,7 +271,7 @@ static void read_cb(RStream *rstream, void *data, bool at_eof)
|
||||
|
||||
static void convert_input(void)
|
||||
{
|
||||
if (embedded_mode || !rbuffer_available(input_buffer)) {
|
||||
if (abstract_ui || !rbuffer_available(input_buffer)) {
|
||||
// No input buffer space
|
||||
return;
|
||||
}
|
||||
@ -335,7 +351,7 @@ static bool input_ready(void)
|
||||
return typebuf_was_filled || // API call filled typeahead
|
||||
rbuffer_pending(input_buffer) > 0 || // Stdin input
|
||||
event_has_deferred() || // Events must be processed
|
||||
(!embedded_mode && eof); // Stdin closed
|
||||
(!abstract_ui && eof); // Stdin closed
|
||||
}
|
||||
|
||||
// Exit because of an input read error.
|
||||
|
@ -45,7 +45,7 @@ void signal_init(void)
|
||||
uv_signal_start(&shup, signal_cb, SIGHUP);
|
||||
uv_signal_start(&squit, signal_cb, SIGQUIT);
|
||||
uv_signal_start(&sterm, signal_cb, SIGTERM);
|
||||
if (!embedded_mode) {
|
||||
if (!abstract_ui) {
|
||||
// TODO(tarruda): There must be an API function for resizing window
|
||||
uv_signal_start(&swinch, signal_cb, SIGWINCH);
|
||||
}
|
||||
|
@ -5824,9 +5824,12 @@ static void screen_start_highlight(int attr)
|
||||
attrentry_T *aep = NULL;
|
||||
|
||||
screen_attr = attr;
|
||||
if (full_screen
|
||||
) {
|
||||
{
|
||||
if (full_screen) {
|
||||
if (abstract_ui) {
|
||||
char buf[20];
|
||||
sprintf(buf, "\033|%dh", attr);
|
||||
OUT_STR(buf);
|
||||
} else {
|
||||
if (attr > HL_ALL) { /* special HL attr. */
|
||||
if (t_colors > 1)
|
||||
aep = syn_cterm_attr2entry(attr);
|
||||
@ -5877,9 +5880,13 @@ void screen_stop_highlight(void)
|
||||
{
|
||||
int do_ME = FALSE; /* output T_ME code */
|
||||
|
||||
if (screen_attr != 0
|
||||
) {
|
||||
{
|
||||
if (screen_attr != 0) {
|
||||
if (abstract_ui) {
|
||||
// Handled in ui.c
|
||||
char buf[20];
|
||||
sprintf(buf, "\033|%dH", screen_attr);
|
||||
OUT_STR(buf);
|
||||
} else {
|
||||
if (screen_attr > HL_ALL) { /* special HL attr. */
|
||||
attrentry_T *aep;
|
||||
|
||||
@ -6558,11 +6565,14 @@ static void screenclear2(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (starting == NO_SCREEN || ScreenLines == NULL
|
||||
)
|
||||
if (starting == NO_SCREEN || ScreenLines == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!abstract_ui) {
|
||||
screen_attr = -1; /* force setting the Normal colors */
|
||||
}
|
||||
|
||||
screen_stop_highlight(); /* don't want highlighting here */
|
||||
|
||||
|
||||
@ -8156,14 +8166,19 @@ void screen_resize(int width, int height, int mustset)
|
||||
|
||||
++busy;
|
||||
|
||||
|
||||
if (mustset || (ui_get_shellsize() == FAIL && height != 0)) {
|
||||
// TODO(tarruda): "mustset" is still used in the old tests, which don't use
|
||||
// "abstract_ui" yet. This will change when a new TUI is merged.
|
||||
if (abstract_ui || mustset || (ui_get_shellsize() == FAIL && height != 0)) {
|
||||
Rows = height;
|
||||
Columns = width;
|
||||
}
|
||||
check_shellsize();
|
||||
|
||||
if (abstract_ui) {
|
||||
ui_resize(width, height);
|
||||
} else {
|
||||
mch_set_shellsize();
|
||||
} else
|
||||
check_shellsize();
|
||||
}
|
||||
|
||||
/* The window layout used to be adjusted here, but it now happens in
|
||||
* screenalloc() (also invoked from screenclear()). That is because the
|
||||
|
@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
@ -68,9 +69,10 @@ struct hl_group {
|
||||
int sg_cterm_attr; /* Screen attr for color term mode */
|
||||
/* Store the sp color name for the GUI or synIDattr() */
|
||||
int sg_gui; /* "gui=" highlighting attributes */
|
||||
char_u *sg_gui_fg_name; /* GUI foreground color name */
|
||||
char_u *sg_gui_bg_name; /* GUI background color name */
|
||||
char_u *sg_gui_sp_name; /* GUI special color name */
|
||||
RgbValue sg_rgb_fg; // RGB foreground color
|
||||
RgbValue sg_rgb_bg; // RGB background color
|
||||
uint8_t *sg_rgb_fg_name; // RGB foreground color name
|
||||
uint8_t *sg_rgb_bg_name; // RGB background color name
|
||||
int sg_link; /* link to this highlight group ID */
|
||||
int sg_set; /* combination of SG_* flags */
|
||||
scid_T sg_scriptID; /* script in which the group was last set */
|
||||
@ -6518,34 +6520,39 @@ do_highlight (
|
||||
if (!init)
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
|
||||
free(HL_TABLE()[idx].sg_gui_fg_name);
|
||||
if (STRCMP(arg, "NONE"))
|
||||
HL_TABLE()[idx].sg_gui_fg_name = vim_strsave(arg);
|
||||
else
|
||||
HL_TABLE()[idx].sg_gui_fg_name = NULL;
|
||||
free(HL_TABLE()[idx].sg_rgb_fg_name);
|
||||
if (STRCMP(arg, "NONE")) {
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg);
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_fg = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_normal_group) {
|
||||
normal_fg = HL_TABLE()[idx].sg_rgb_fg;
|
||||
}
|
||||
} else if (STRCMP(key, "GUIBG") == 0) {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
|
||||
if (!init)
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
|
||||
free(HL_TABLE()[idx].sg_gui_bg_name);
|
||||
if (STRCMP(arg, "NONE") != 0)
|
||||
HL_TABLE()[idx].sg_gui_bg_name = vim_strsave(arg);
|
||||
else
|
||||
HL_TABLE()[idx].sg_gui_bg_name = NULL;
|
||||
free(HL_TABLE()[idx].sg_rgb_bg_name);
|
||||
if (STRCMP(arg, "NONE") != 0) {
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg);
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_bg = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_normal_group) {
|
||||
normal_bg = HL_TABLE()[idx].sg_rgb_bg;
|
||||
}
|
||||
} else if (STRCMP(key, "GUISP") == 0) {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
|
||||
if (!init)
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
|
||||
free(HL_TABLE()[idx].sg_gui_sp_name);
|
||||
if (STRCMP(arg, "NONE") != 0)
|
||||
HL_TABLE()[idx].sg_gui_sp_name = vim_strsave(arg);
|
||||
else
|
||||
HL_TABLE()[idx].sg_gui_sp_name = NULL;
|
||||
}
|
||||
// Ignored
|
||||
} else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) {
|
||||
char_u buf[100];
|
||||
char_u *tname;
|
||||
@ -6670,6 +6677,8 @@ void free_highlight(void)
|
||||
*/
|
||||
void restore_cterm_colors(void)
|
||||
{
|
||||
normal_fg = -1;
|
||||
normal_bg = -1;
|
||||
cterm_normal_fg_color = 0;
|
||||
cterm_normal_fg_bold = 0;
|
||||
cterm_normal_bg_color = 0;
|
||||
@ -6705,12 +6714,12 @@ static void highlight_clear(int idx)
|
||||
HL_TABLE()[idx].sg_cterm_bg = 0;
|
||||
HL_TABLE()[idx].sg_cterm_attr = 0;
|
||||
HL_TABLE()[idx].sg_gui = 0;
|
||||
free(HL_TABLE()[idx].sg_gui_fg_name);
|
||||
HL_TABLE()[idx].sg_gui_fg_name = NULL;
|
||||
free(HL_TABLE()[idx].sg_gui_bg_name);
|
||||
HL_TABLE()[idx].sg_gui_bg_name = NULL;
|
||||
free(HL_TABLE()[idx].sg_gui_sp_name);
|
||||
HL_TABLE()[idx].sg_gui_sp_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_fg = -1;
|
||||
HL_TABLE()[idx].sg_rgb_bg = -1;
|
||||
free(HL_TABLE()[idx].sg_rgb_fg_name);
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = NULL;
|
||||
free(HL_TABLE()[idx].sg_rgb_bg_name);
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = NULL;
|
||||
/* Clear the script ID only when there is no link, since that is not
|
||||
* cleared. */
|
||||
if (HL_TABLE()[idx].sg_link == 0)
|
||||
@ -6771,7 +6780,11 @@ static int get_attr_entry(garray_T *table, attrentry_T *aep)
|
||||
&& aep->ae_u.cterm.fg_color
|
||||
== taep->ae_u.cterm.fg_color
|
||||
&& aep->ae_u.cterm.bg_color
|
||||
== taep->ae_u.cterm.bg_color)
|
||||
== taep->ae_u.cterm.bg_color
|
||||
&& aep->fg_color
|
||||
== taep->fg_color
|
||||
&& aep->bg_color
|
||||
== taep->bg_color)
|
||||
))
|
||||
|
||||
return i + ATTR_OFF;
|
||||
@ -6818,6 +6831,8 @@ static int get_attr_entry(garray_T *table, attrentry_T *aep)
|
||||
} else if (table == &cterm_attr_table) {
|
||||
taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
|
||||
taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
|
||||
taep->fg_color = aep->fg_color;
|
||||
taep->bg_color = aep->bg_color;
|
||||
}
|
||||
|
||||
return table->ga_len - 1 + ATTR_OFF;
|
||||
@ -6880,6 +6895,10 @@ int hl_combine_attr(int char_attr, int prim_attr)
|
||||
new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
|
||||
if (spell_aep->ae_u.cterm.bg_color > 0)
|
||||
new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
|
||||
if (spell_aep->fg_color >= 0)
|
||||
new_en.fg_color = spell_aep->fg_color;
|
||||
if (spell_aep->bg_color >= 0)
|
||||
new_en.bg_color = spell_aep->bg_color;
|
||||
}
|
||||
}
|
||||
return get_attr_entry(&cterm_attr_table, &new_en);
|
||||
@ -6974,11 +6993,11 @@ static void highlight_list_one(int id)
|
||||
didh = highlight_list_arg(id, didh, LIST_ATTR,
|
||||
sgp->sg_gui, NULL, "gui");
|
||||
didh = highlight_list_arg(id, didh, LIST_STRING,
|
||||
0, sgp->sg_gui_fg_name, "guifg");
|
||||
0, sgp->sg_rgb_fg_name, "guifg");
|
||||
didh = highlight_list_arg(id, didh, LIST_STRING,
|
||||
0, sgp->sg_gui_bg_name, "guibg");
|
||||
0, sgp->sg_rgb_bg_name, "guibg");
|
||||
didh = highlight_list_arg(id, didh, LIST_STRING,
|
||||
0, sgp->sg_gui_sp_name, "guisp");
|
||||
0, NULL, "guisp");
|
||||
|
||||
if (sgp->sg_link && !got_int) {
|
||||
(void)syn_list_header(didh, 9999, id);
|
||||
@ -7092,10 +7111,10 @@ highlight_color (
|
||||
return NULL;
|
||||
if (modec == 'g') {
|
||||
if (fg)
|
||||
return HL_TABLE()[id - 1].sg_gui_fg_name;
|
||||
return HL_TABLE()[id - 1].sg_rgb_fg_name;
|
||||
if (sp)
|
||||
return HL_TABLE()[id - 1].sg_gui_sp_name;
|
||||
return HL_TABLE()[id - 1].sg_gui_bg_name;
|
||||
return NULL;
|
||||
return HL_TABLE()[id - 1].sg_rgb_bg_name;
|
||||
}
|
||||
if (font || sp)
|
||||
return NULL;
|
||||
@ -7192,9 +7211,14 @@ set_hl_attr (
|
||||
if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0)
|
||||
sgp->sg_cterm_attr = sgp->sg_cterm;
|
||||
else {
|
||||
at_en.ae_attr = sgp->sg_cterm;
|
||||
at_en.ae_attr = abstract_ui ? sgp->sg_gui : sgp->sg_cterm;
|
||||
at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
|
||||
at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
|
||||
// FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is
|
||||
// initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name
|
||||
// before setting attr_entry->{f,g}g_color to a other than -1
|
||||
at_en.fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1;
|
||||
at_en.bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1;
|
||||
sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
|
||||
}
|
||||
}
|
||||
@ -7633,6 +7657,200 @@ char_u *get_highlight_name(expand_T *xp, int idx)
|
||||
}
|
||||
|
||||
|
||||
RgbValue name_to_color(uint8_t *name)
|
||||
{
|
||||
#define RGB(r, g, b) ((r << 16) | (g << 8) | b)
|
||||
static struct {
|
||||
char *name;
|
||||
RgbValue color;
|
||||
} color_name_table[] = {
|
||||
// Color names taken from
|
||||
// http://www.rapidtables.com/web/color/RGB_Color.htm
|
||||
{"Maroon", RGB(0x80, 0x00, 0x00)},
|
||||
{"DarkRed", RGB(0x8b, 0x00, 0x00)},
|
||||
{"Brown", RGB(0xa5, 0x2a, 0x2a)},
|
||||
{"Firebrick", RGB(0xb2, 0x22, 0x22)},
|
||||
{"Crimson", RGB(0xdc, 0x14, 0x3c)},
|
||||
{"Red", RGB(0xff, 0x00, 0x00)},
|
||||
{"Tomato", RGB(0xff, 0x63, 0x47)},
|
||||
{"Coral", RGB(0xff, 0x7f, 0x50)},
|
||||
{"IndianRed", RGB(0xcd, 0x5c, 0x5c)},
|
||||
{"LightCoral", RGB(0xf0, 0x80, 0x80)},
|
||||
{"DarkSalmon", RGB(0xe9, 0x96, 0x7a)},
|
||||
{"Salmon", RGB(0xfa, 0x80, 0x72)},
|
||||
{"LightSalmon", RGB(0xff, 0xa0, 0x7a)},
|
||||
{"OrangeRed", RGB(0xff, 0x45, 0x00)},
|
||||
{"DarkOrange", RGB(0xff, 0x8c, 0x00)},
|
||||
{"Orange", RGB(0xff, 0xa5, 0x00)},
|
||||
{"Gold", RGB(0xff, 0xd7, 0x00)},
|
||||
{"DarkGoldenRod", RGB(0xb8, 0x86, 0x0b)},
|
||||
{"GoldenRod", RGB(0xda, 0xa5, 0x20)},
|
||||
{"PaleGoldenRod", RGB(0xee, 0xe8, 0xaa)},
|
||||
{"DarkKhaki", RGB(0xbd, 0xb7, 0x6b)},
|
||||
{"Khaki", RGB(0xf0, 0xe6, 0x8c)},
|
||||
{"Olive", RGB(0x80, 0x80, 0x00)},
|
||||
{"Yellow", RGB(0xff, 0xff, 0x00)},
|
||||
{"YellowGreen", RGB(0x9a, 0xcd, 0x32)},
|
||||
{"DarkOliveGreen", RGB(0x55, 0x6b, 0x2f)},
|
||||
{"OliveDrab", RGB(0x6b, 0x8e, 0x23)},
|
||||
{"LawnGreen", RGB(0x7c, 0xfc, 0x00)},
|
||||
{"ChartReuse", RGB(0x7f, 0xff, 0x00)},
|
||||
{"GreenYellow", RGB(0xad, 0xff, 0x2f)},
|
||||
{"DarkGreen", RGB(0x00, 0x64, 0x00)},
|
||||
{"Green", RGB(0x00, 0x80, 0x00)},
|
||||
{"ForestGreen", RGB(0x22, 0x8b, 0x22)},
|
||||
{"Lime", RGB(0x00, 0xff, 0x00)},
|
||||
{"LimeGreen", RGB(0x32, 0xcd, 0x32)},
|
||||
{"LightGreen", RGB(0x90, 0xee, 0x90)},
|
||||
{"PaleGreen", RGB(0x98, 0xfb, 0x98)},
|
||||
{"DarkSeaGreen", RGB(0x8f, 0xbc, 0x8f)},
|
||||
{"MediumSpringGreen", RGB(0x00, 0xfa, 0x9a)},
|
||||
{"SpringGreen", RGB(0x00, 0xff, 0x7f)},
|
||||
{"SeaGreen", RGB(0x2e, 0x8b, 0x57)},
|
||||
{"MediumAquamarine", RGB(0x66, 0xcd, 0xaa)},
|
||||
{"MediumSeaGreen", RGB(0x3c, 0xb3, 0x71)},
|
||||
{"LightSeaGreen", RGB(0x20, 0xb2, 0xaa)},
|
||||
{"DarkSlateGray", RGB(0x2f, 0x4f, 0x4f)},
|
||||
{"Teal", RGB(0x00, 0x80, 0x80)},
|
||||
{"DarkCyan", RGB(0x00, 0x8b, 0x8b)},
|
||||
{"Aqua", RGB(0x00, 0xff, 0xff)},
|
||||
{"Cyan", RGB(0x00, 0xff, 0xff)},
|
||||
{"LightCyan", RGB(0xe0, 0xff, 0xff)},
|
||||
{"DarkTurquoise", RGB(0x00, 0xce, 0xd1)},
|
||||
{"Turquoise", RGB(0x40, 0xe0, 0xd0)},
|
||||
{"MediumTurquoise", RGB(0x48, 0xd1, 0xcc)},
|
||||
{"PaleTurquoise", RGB(0xaf, 0xee, 0xee)},
|
||||
{"Aquamarine", RGB(0x7f, 0xff, 0xd4)},
|
||||
{"PowderBlue", RGB(0xb0, 0xe0, 0xe6)},
|
||||
{"CadetBlue", RGB(0x5f, 0x9e, 0xa0)},
|
||||
{"SteelBlue", RGB(0x46, 0x82, 0xb4)},
|
||||
{"CornFlowerBlue", RGB(0x64, 0x95, 0xed)},
|
||||
{"DeepSkyBlue", RGB(0x00, 0xbf, 0xff)},
|
||||
{"DodgerBlue", RGB(0x1e, 0x90, 0xff)},
|
||||
{"LightBlue", RGB(0xad, 0xd8, 0xe6)},
|
||||
{"SkyBlue", RGB(0x87, 0xce, 0xeb)},
|
||||
{"LightSkyBlue", RGB(0x87, 0xce, 0xfa)},
|
||||
{"MidnightBlue", RGB(0x19, 0x19, 0x70)},
|
||||
{"Navy", RGB(0x00, 0x00, 0x80)},
|
||||
{"DarkBlue", RGB(0x00, 0x00, 0x8b)},
|
||||
{"MediumBlue", RGB(0x00, 0x00, 0xcd)},
|
||||
{"Blue", RGB(0x00, 0x00, 0xff)},
|
||||
{"RoyalBlue", RGB(0x41, 0x69, 0xe1)},
|
||||
{"BlueViolet", RGB(0x8a, 0x2b, 0xe2)},
|
||||
{"Indigo", RGB(0x4b, 0x00, 0x82)},
|
||||
{"DarkSlateBlue", RGB(0x48, 0x3d, 0x8b)},
|
||||
{"SlateBlue", RGB(0x6a, 0x5a, 0xcd)},
|
||||
{"MediumSlateBlue", RGB(0x7b, 0x68, 0xee)},
|
||||
{"MediumPurple", RGB(0x93, 0x70, 0xdb)},
|
||||
{"DarkMagenta", RGB(0x8b, 0x00, 0x8b)},
|
||||
{"DarkViolet", RGB(0x94, 0x00, 0xd3)},
|
||||
{"DarkOrchid", RGB(0x99, 0x32, 0xcc)},
|
||||
{"MediumOrchid", RGB(0xba, 0x55, 0xd3)},
|
||||
{"Purple", RGB(0x80, 0x00, 0x80)},
|
||||
{"Thistle", RGB(0xd8, 0xbf, 0xd8)},
|
||||
{"Plum", RGB(0xdd, 0xa0, 0xdd)},
|
||||
{"Violet", RGB(0xee, 0x82, 0xee)},
|
||||
{"Magenta", RGB(0xff, 0x00, 0xff)},
|
||||
{"Fuchsia", RGB(0xff, 0x00, 0xff)},
|
||||
{"Orchid", RGB(0xda, 0x70, 0xd6)},
|
||||
{"MediumVioletRed", RGB(0xc7, 0x15, 0x85)},
|
||||
{"PaleVioletRed", RGB(0xdb, 0x70, 0x93)},
|
||||
{"DeepPink", RGB(0xff, 0x14, 0x93)},
|
||||
{"HotPink", RGB(0xff, 0x69, 0xb4)},
|
||||
{"LightPink", RGB(0xff, 0xb6, 0xc1)},
|
||||
{"Pink", RGB(0xff, 0xc0, 0xcb)},
|
||||
{"AntiqueWhite", RGB(0xfa, 0xeb, 0xd7)},
|
||||
{"Beige", RGB(0xf5, 0xf5, 0xdc)},
|
||||
{"Bisque", RGB(0xff, 0xe4, 0xc4)},
|
||||
{"BlanchedAlmond", RGB(0xff, 0xeb, 0xcd)},
|
||||
{"Wheat", RGB(0xf5, 0xde, 0xb3)},
|
||||
{"Cornsilk", RGB(0xff, 0xf8, 0xdc)},
|
||||
{"LemonChiffon", RGB(0xff, 0xfa, 0xcd)},
|
||||
{"LightGoldenRodYellow", RGB(0xfa, 0xfa, 0xd2)},
|
||||
{"LightYellow", RGB(0xff, 0xff, 0xe0)},
|
||||
{"SaddleBrown", RGB(0x8b, 0x45, 0x13)},
|
||||
{"Sienna", RGB(0xa0, 0x52, 0x2d)},
|
||||
{"Chocolate", RGB(0xd2, 0x69, 0x1e)},
|
||||
{"Peru", RGB(0xcd, 0x85, 0x3f)},
|
||||
{"SandyBrown", RGB(0xf4, 0xa4, 0x60)},
|
||||
{"BurlyWood", RGB(0xde, 0xb8, 0x87)},
|
||||
{"Tan", RGB(0xd2, 0xb4, 0x8c)},
|
||||
{"RosyBrown", RGB(0xbc, 0x8f, 0x8f)},
|
||||
{"Moccasin", RGB(0xff, 0xe4, 0xb5)},
|
||||
{"NavajoWhite", RGB(0xff, 0xde, 0xad)},
|
||||
{"PeachPuff", RGB(0xff, 0xda, 0xb9)},
|
||||
{"MistyRose", RGB(0xff, 0xe4, 0xe1)},
|
||||
{"LavenderBlush", RGB(0xff, 0xf0, 0xf5)},
|
||||
{"Linen", RGB(0xfa, 0xf0, 0xe6)},
|
||||
{"Oldlace", RGB(0xfd, 0xf5, 0xe6)},
|
||||
{"PapayaWhip", RGB(0xff, 0xef, 0xd5)},
|
||||
{"SeaShell", RGB(0xff, 0xf5, 0xee)},
|
||||
{"MintCream", RGB(0xf5, 0xff, 0xfa)},
|
||||
{"SlateGray", RGB(0x70, 0x80, 0x90)},
|
||||
{"LightSlateGray", RGB(0x77, 0x88, 0x99)},
|
||||
{"LightSteelBlue", RGB(0xb0, 0xc4, 0xde)},
|
||||
{"Lavender", RGB(0xe6, 0xe6, 0xfa)},
|
||||
{"FloralWhite", RGB(0xff, 0xfa, 0xf0)},
|
||||
{"AliceBlue", RGB(0xf0, 0xf8, 0xff)},
|
||||
{"GhostWhite", RGB(0xf8, 0xf8, 0xff)},
|
||||
{"Honeydew", RGB(0xf0, 0xff, 0xf0)},
|
||||
{"Ivory", RGB(0xff, 0xff, 0xf0)},
|
||||
{"Azure", RGB(0xf0, 0xff, 0xff)},
|
||||
{"Snow", RGB(0xff, 0xfa, 0xfa)},
|
||||
{"Black", RGB(0x00, 0x00, 0x00)},
|
||||
{"DimGray", RGB(0x69, 0x69, 0x69)},
|
||||
{"DimGrey", RGB(0x69, 0x69, 0x69)},
|
||||
{"Gray", RGB(0x80, 0x80, 0x80)},
|
||||
{"Grey", RGB(0x80, 0x80, 0x80)},
|
||||
{"DarkGray", RGB(0xa9, 0xa9, 0xa9)},
|
||||
{"DarkGrey", RGB(0xa9, 0xa9, 0xa9)},
|
||||
{"Silver", RGB(0xc0, 0xc0, 0xc0)},
|
||||
{"LightGray", RGB(0xd3, 0xd3, 0xd3)},
|
||||
{"LightGrey", RGB(0xd3, 0xd3, 0xd3)},
|
||||
{"Gainsboro", RGB(0xdc, 0xdc, 0xdc)},
|
||||
{"WhiteSmoke", RGB(0xf5, 0xf5, 0xf5)},
|
||||
{"White", RGB(0xff, 0xff, 0xff)},
|
||||
// The color names below were taken from gui_x11.c in vim source
|
||||
{"LightRed", RGB(0xff, 0xbb, 0xbb)},
|
||||
{"LightMagenta",RGB(0xff, 0xbb, 0xff)},
|
||||
{"DarkYellow", RGB(0xbb, 0xbb, 0x00)},
|
||||
{"Gray10", RGB(0x1a, 0x1a, 0x1a)},
|
||||
{"Grey10", RGB(0x1a, 0x1a, 0x1a)},
|
||||
{"Gray20", RGB(0x33, 0x33, 0x33)},
|
||||
{"Grey20", RGB(0x33, 0x33, 0x33)},
|
||||
{"Gray30", RGB(0x4d, 0x4d, 0x4d)},
|
||||
{"Grey30", RGB(0x4d, 0x4d, 0x4d)},
|
||||
{"Gray40", RGB(0x66, 0x66, 0x66)},
|
||||
{"Grey40", RGB(0x66, 0x66, 0x66)},
|
||||
{"Gray50", RGB(0x7f, 0x7f, 0x7f)},
|
||||
{"Grey50", RGB(0x7f, 0x7f, 0x7f)},
|
||||
{"Gray60", RGB(0x99, 0x99, 0x99)},
|
||||
{"Grey60", RGB(0x99, 0x99, 0x99)},
|
||||
{"Gray70", RGB(0xb3, 0xb3, 0xb3)},
|
||||
{"Grey70", RGB(0xb3, 0xb3, 0xb3)},
|
||||
{"Gray80", RGB(0xcc, 0xcc, 0xcc)},
|
||||
{"Grey80", RGB(0xcc, 0xcc, 0xcc)},
|
||||
{"Gray90", RGB(0xe5, 0xe5, 0xe5)},
|
||||
{"Grey90", RGB(0xe5, 0xe5, 0xe5)},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
|
||||
&& isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5])
|
||||
&& isxdigit(name[6]) && name[7] == NUL) {
|
||||
// rgb hex string
|
||||
return strtol((char *)(name + 1), NULL, 16);
|
||||
}
|
||||
|
||||
for (int i = 0; color_name_table[i].name != NULL; i++) {
|
||||
if (!STRICMP(name, color_name_table[i].name)) {
|
||||
return color_name_table[i].color;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* End of Highlighting stuff *
|
||||
**************************************/
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include "nvim/buffer_defs.h"
|
||||
|
||||
typedef int guicolor_T;
|
||||
|
||||
/*
|
||||
* Terminal highlighting attribute bits.
|
||||
* Attributes above HL_ALL are used for syntax highlighting.
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "nvim/regexp_defs.h"
|
||||
|
||||
typedef int32_t RgbValue;
|
||||
|
||||
# define SST_MIN_ENTRIES 150 /* minimal size for state stack array */
|
||||
# define SST_MAX_ENTRIES 1000 /* maximal size for state stack array */
|
||||
# define SST_FIX_STATES 7 /* size of sst_stack[]. */
|
||||
@ -70,6 +72,7 @@ struct syn_state {
|
||||
*/
|
||||
typedef struct attr_entry {
|
||||
short ae_attr; /* HL_BOLD, etc. */
|
||||
RgbValue fg_color, bg_color;
|
||||
union {
|
||||
struct {
|
||||
char_u *start; /* start escape sequence */
|
||||
|
@ -161,6 +161,33 @@ static bool detected_8bit = false; // detected 8-bit terminal
|
||||
|
||||
static struct builtin_term builtin_termcaps[] =
|
||||
{
|
||||
// abstract UI pseudo termcap, based on vim's "builtin_gui" termcap
|
||||
{(int)KS_NAME, "abstract_ui"},
|
||||
{(int)KS_CE, "\033|$"},
|
||||
{(int)KS_AL, "\033|i"},
|
||||
{(int)KS_CAL, "\033|%p1%dI"},
|
||||
{(int)KS_DL, "\033|d"},
|
||||
{(int)KS_CDL, "\033|%p1%dD"},
|
||||
{(int)KS_CS, "\033|%p1%d;%p2%dR"},
|
||||
{(int)KS_CL, "\033|C"},
|
||||
// attributes switched on with 'h', off with * 'H'
|
||||
{(int)KS_ME, "\033|31H"}, // HL_ALL
|
||||
{(int)KS_MR, "\033|1h"}, // HL_INVERSE
|
||||
{(int)KS_MD, "\033|2h"}, // HL_BOLD
|
||||
{(int)KS_SE, "\033|16H"}, // HL_STANDOUT
|
||||
{(int)KS_SO, "\033|16h"}, // HL_STANDOUT
|
||||
{(int)KS_UE, "\033|8H"}, // HL_UNDERLINE
|
||||
{(int)KS_US, "\033|8h"}, // HL_UNDERLINE
|
||||
{(int)KS_CZR, "\033|4H"}, // HL_ITALIC
|
||||
{(int)KS_CZH, "\033|4h"}, // HL_ITALIC
|
||||
{(int)KS_VB, "\033|f"},
|
||||
{(int)KS_MS, "y"},
|
||||
{(int)KS_UT, "y"},
|
||||
{(int)KS_LE, "\b"}, // cursor-left = BS
|
||||
{(int)KS_ND, "\014"}, // cursor-right = CTRL-L
|
||||
{(int)KS_CM, "\033|%p1%d;%p2%dM"},
|
||||
// there are no key sequences here, for "abstract_ui" vim key codes are
|
||||
// parsed directly in input_enqueue()
|
||||
|
||||
|
||||
#ifndef NO_BUILTIN_TCAPS
|
||||
@ -1162,6 +1189,10 @@ int set_termname(char_u *term)
|
||||
if (silent_mode)
|
||||
return OK;
|
||||
|
||||
if (!STRCMP(term, "abstract_ui")) {
|
||||
abstract_ui = true;
|
||||
}
|
||||
|
||||
detected_8bit = false; // reset 8-bit detection
|
||||
|
||||
if (term_is_builtin(term)) {
|
||||
@ -1829,18 +1860,6 @@ void termcapinit(char_u *name)
|
||||
/// Write s[len] to the screen.
|
||||
void term_write(char_u *s, size_t len)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
// TODO(tarruda): This is a temporary hack to stop Neovim from writing
|
||||
// messages to stdout in embedded mode. In the future, embedded mode will
|
||||
// be the only possibility(GUIs will always start neovim with a msgpack-rpc
|
||||
// over stdio) and this function won't exist.
|
||||
//
|
||||
// The reason for this is because before Neovim fully migrates to a
|
||||
// msgpack-rpc-driven architecture, we must have a fully functional
|
||||
// UI working
|
||||
return;
|
||||
}
|
||||
|
||||
(void) fwrite(s, len, 1, stdout);
|
||||
|
||||
#ifdef UNIX
|
||||
@ -2296,7 +2315,7 @@ void shell_resized_check(void)
|
||||
*/
|
||||
void settmode(int tmode)
|
||||
{
|
||||
if (embedded_mode) {
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2340,7 +2359,7 @@ void starttermcap(void)
|
||||
out_flush();
|
||||
termcap_active = TRUE;
|
||||
screen_start(); /* don't know where cursor is now */
|
||||
{
|
||||
if (!abstract_ui) {
|
||||
may_req_termresponse();
|
||||
/* Immediately check for a response. If t_Co changes, we don't
|
||||
* want to redraw with wrong colors first. */
|
||||
@ -2356,7 +2375,7 @@ void stoptermcap(void)
|
||||
screen_stop_highlight();
|
||||
reset_cterm_colors();
|
||||
if (termcap_active) {
|
||||
{
|
||||
if (!abstract_ui) {
|
||||
/* May need to discard T_CRV or T_U7 response. */
|
||||
if (crv_status == CRV_SENT || u7_status == U7_SENT) {
|
||||
# ifdef UNIX
|
||||
@ -2545,6 +2564,11 @@ static int cursor_is_off = FALSE;
|
||||
*/
|
||||
void cursor_on(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
ui_cursor_on();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor_is_off) {
|
||||
out_str(T_VE);
|
||||
cursor_is_off = FALSE;
|
||||
@ -2556,6 +2580,11 @@ void cursor_on(void)
|
||||
*/
|
||||
void cursor_off(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
ui_cursor_off();
|
||||
return;
|
||||
}
|
||||
|
||||
if (full_screen) {
|
||||
if (!cursor_is_off)
|
||||
out_str(T_VI); /* disable cursor */
|
||||
@ -2852,6 +2881,11 @@ void set_mouse_topline(win_T *wp)
|
||||
*/
|
||||
int check_termcode(int max_offset, char_u *buf, int bufsize, int *buflen)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
// codes are parsed by input.c/input_enqueue
|
||||
return 0;
|
||||
}
|
||||
|
||||
char_u *tp;
|
||||
char_u *p;
|
||||
int slen = 0; /* init for GCC */
|
||||
@ -3883,6 +3917,10 @@ int find_term_bykeys(char_u *src)
|
||||
*/
|
||||
static void gather_termleader(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
|
||||
if (check_for_codes)
|
||||
|
423
src/nvim/ui.c
423
src/nvim/ui.c
@ -15,20 +15,24 @@
|
||||
* 3. Input buffer stuff.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/cursor.h"
|
||||
#include "nvim/diff.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/fold.h"
|
||||
#include "nvim/main.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/misc2.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/garray.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/move.h"
|
||||
@ -39,13 +43,61 @@
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/signal.h"
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/term.h"
|
||||
#include "nvim/window.h"
|
||||
|
||||
void ui_write(char_u *s, int len)
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ui.c.generated.h"
|
||||
#endif
|
||||
|
||||
#define MAX_UI_COUNT 16
|
||||
|
||||
static UI *uis[MAX_UI_COUNT];
|
||||
static size_t ui_count = 0;
|
||||
static int row, col;
|
||||
static struct {
|
||||
int top, bot, left, right;
|
||||
} sr;
|
||||
static int current_highlight_mask = 0;
|
||||
static HlAttrs current_attrs = {
|
||||
false, false, false, false, false, false, -1, -1
|
||||
};
|
||||
static bool cursor_enabled = true;
|
||||
static int height = INT_MAX, width = INT_MAX;
|
||||
|
||||
// This set of macros allow us to use UI_CALL to invoke any function on
|
||||
// registered UI instances. The functions can have 0-5 arguments(configurable
|
||||
// by SELECT_NTH)
|
||||
//
|
||||
// See http://stackoverflow.com/a/11172679 for a better explanation of how it
|
||||
// works.
|
||||
#define UI_CALL(...) \
|
||||
do { \
|
||||
for (size_t i = 0; i < ui_count; i++) { \
|
||||
UI *ui = uis[i]; \
|
||||
UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore)
|
||||
#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)
|
||||
|
||||
void ui_write(uint8_t *s, int len)
|
||||
{
|
||||
/* Don't output anything in silent mode ("ex -s") unless 'verbose' set */
|
||||
if (!(silent_mode && p_verbose == 0)) {
|
||||
if (silent_mode && !p_verbose) {
|
||||
// Don't output anything in silent mode ("ex -s") unless 'verbose' set
|
||||
return;
|
||||
}
|
||||
|
||||
if (abstract_ui) {
|
||||
parse_abstract_ui_codes(s, len);
|
||||
return;
|
||||
}
|
||||
|
||||
char_u *tofree = NULL;
|
||||
|
||||
if (output_conv.vc_type != CONV_NONE) {
|
||||
@ -60,7 +112,6 @@ void ui_write(char_u *s, int len)
|
||||
if (output_conv.vc_type != CONV_NONE)
|
||||
free(tofree);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the machine has job control, use it to suspend the program,
|
||||
@ -69,8 +120,12 @@ void ui_write(char_u *s, int len)
|
||||
*/
|
||||
void ui_suspend(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
UI_CALL(suspend);
|
||||
} else {
|
||||
mch_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to get the current Vim shell size. Put the result in Rows and Columns.
|
||||
@ -79,6 +134,10 @@ void ui_suspend(void)
|
||||
*/
|
||||
int ui_get_shellsize(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
int retval;
|
||||
|
||||
retval = mch_get_shellsize();
|
||||
@ -98,7 +157,363 @@ int ui_get_shellsize(void)
|
||||
*/
|
||||
void ui_cursor_shape(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
ui_change_mode();
|
||||
} else {
|
||||
term_cursor_shape();
|
||||
conceal_check_cursur_line();
|
||||
}
|
||||
}
|
||||
|
||||
void ui_resize(int width, int height)
|
||||
{
|
||||
sr.top = 0;
|
||||
sr.bot = height - 1;
|
||||
sr.left = 0;
|
||||
sr.right = width - 1;
|
||||
UI_CALL(resize, width, height);
|
||||
}
|
||||
|
||||
void ui_cursor_on(void)
|
||||
{
|
||||
if (!cursor_enabled) {
|
||||
UI_CALL(cursor_on);
|
||||
cursor_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ui_cursor_off(void)
|
||||
{
|
||||
if (full_screen) {
|
||||
if (cursor_enabled) {
|
||||
UI_CALL(cursor_off);
|
||||
}
|
||||
cursor_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ui_mouse_on(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
UI_CALL(mouse_on);
|
||||
} else {
|
||||
mch_setmouse(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_mouse_off(void)
|
||||
{
|
||||
if (abstract_ui) {
|
||||
UI_CALL(mouse_off);
|
||||
} else {
|
||||
mch_setmouse(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify that the current mode has changed. Can be used to change cursor
|
||||
// shape, for example.
|
||||
void ui_change_mode(void)
|
||||
{
|
||||
static int showing_insert_mode = MAYBE;
|
||||
|
||||
if (!full_screen)
|
||||
return;
|
||||
|
||||
if (State & INSERT) {
|
||||
if (showing_insert_mode != TRUE) {
|
||||
UI_CALL(insert_mode);
|
||||
}
|
||||
showing_insert_mode = TRUE;
|
||||
} else {
|
||||
if (showing_insert_mode != FALSE) {
|
||||
UI_CALL(normal_mode);
|
||||
}
|
||||
showing_insert_mode = FALSE;
|
||||
}
|
||||
conceal_check_cursur_line();
|
||||
}
|
||||
|
||||
void ui_attach(UI *ui)
|
||||
{
|
||||
if (ui_count == MAX_UI_COUNT) {
|
||||
abort();
|
||||
}
|
||||
|
||||
uis[ui_count++] = ui;
|
||||
resized(ui);
|
||||
}
|
||||
|
||||
void ui_detach(UI *ui)
|
||||
{
|
||||
size_t shift_index = MAX_UI_COUNT;
|
||||
|
||||
// Find the index that will be removed
|
||||
for (size_t i = 0; i < ui_count; i++) {
|
||||
if (uis[i] == ui) {
|
||||
shift_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shift_index == MAX_UI_COUNT) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// Shift UIs at "shift_index"
|
||||
while (shift_index < ui_count - 1) {
|
||||
uis[shift_index] = uis[shift_index + 1];
|
||||
shift_index++;
|
||||
}
|
||||
|
||||
ui_count--;
|
||||
|
||||
if (ui->width == width || ui->height == height) {
|
||||
// It is possible that the UI being detached had the smallest screen,
|
||||
// so check for the new minimum dimensions
|
||||
width = height = INT_MAX;
|
||||
for (size_t i = 0; i < ui_count; i++) {
|
||||
check_dimensions(uis[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ui_count) {
|
||||
screen_resize(width, height, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void highlight_start(int mask)
|
||||
{
|
||||
if (mask > HL_ALL) {
|
||||
// attribute code
|
||||
current_highlight_mask = mask;
|
||||
} else {
|
||||
// attribute mask
|
||||
current_highlight_mask |= mask;
|
||||
}
|
||||
|
||||
if (!ui_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_highlight_args(current_highlight_mask, ¤t_attrs);
|
||||
UI_CALL(highlight_set, current_attrs);
|
||||
}
|
||||
|
||||
static void highlight_stop(int mask)
|
||||
{
|
||||
if (mask > HL_ALL) {
|
||||
// attribute code
|
||||
current_highlight_mask = HL_NORMAL;
|
||||
} else {
|
||||
// attribute mask
|
||||
current_highlight_mask &= ~mask;
|
||||
}
|
||||
|
||||
set_highlight_args(current_highlight_mask, ¤t_attrs);
|
||||
UI_CALL(highlight_set, current_attrs);
|
||||
}
|
||||
|
||||
static void set_highlight_args(int mask, HlAttrs *attrs)
|
||||
{
|
||||
attrentry_T *aep = NULL;
|
||||
|
||||
if (mask > HL_ALL) {
|
||||
aep = syn_cterm_attr2entry(mask);
|
||||
mask = aep ? aep->ae_attr : 0;
|
||||
}
|
||||
|
||||
attrs->bold = mask & HL_BOLD;
|
||||
attrs->standout = mask & HL_STANDOUT;
|
||||
attrs->underline = mask & HL_UNDERLINE;
|
||||
attrs->undercurl = mask & HL_UNDERCURL;
|
||||
attrs->italic = mask & HL_ITALIC;
|
||||
attrs->reverse = mask & HL_INVERSE;
|
||||
attrs->foreground = aep && aep->fg_color >= 0 ? aep->fg_color : normal_fg;
|
||||
attrs->background = aep && aep->bg_color >= 0 ? aep->bg_color : normal_bg;
|
||||
}
|
||||
|
||||
static void parse_abstract_ui_codes(uint8_t *ptr, int len)
|
||||
{
|
||||
int arg1 = 0, arg2 = 0;
|
||||
uint8_t *end = ptr + len, *p, c;
|
||||
bool update_cursor = false;
|
||||
|
||||
while (ptr < end) {
|
||||
if (ptr < end - 1 && ptr[0] == ESC && ptr[1] == '|') {
|
||||
p = ptr + 2;
|
||||
assert(p != end);
|
||||
|
||||
if (VIM_ISDIGIT(*p)) {
|
||||
arg1 = (int)getdigits(&p);
|
||||
if (p >= end) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
arg2 = (int)getdigits(&p);
|
||||
if (p >= end)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (*p) {
|
||||
case 'C':
|
||||
UI_CALL(clear);
|
||||
break;
|
||||
case 'M':
|
||||
ui_cursor_goto(arg1, arg2);
|
||||
break;
|
||||
case 's':
|
||||
update_cursor = true;
|
||||
break;
|
||||
case 'R':
|
||||
if (arg1 < arg2) {
|
||||
sr.top = arg1;
|
||||
sr.bot = arg2;
|
||||
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
|
||||
} else {
|
||||
sr.top = arg2;
|
||||
sr.bot = arg1;
|
||||
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
|
||||
}
|
||||
break;
|
||||
case 'V':
|
||||
if (arg1 < arg2) {
|
||||
sr.left = arg1;
|
||||
sr.right = arg2;
|
||||
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
|
||||
} else {
|
||||
sr.left = arg2;
|
||||
sr.right = arg1;
|
||||
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
UI_CALL(scroll, 1);
|
||||
break;
|
||||
case 'D':
|
||||
UI_CALL(scroll, arg1);
|
||||
break;
|
||||
case 'i':
|
||||
UI_CALL(scroll, -1);
|
||||
break;
|
||||
case 'I':
|
||||
UI_CALL(scroll, -arg1);
|
||||
break;
|
||||
case '$':
|
||||
UI_CALL(eol_clear);
|
||||
break;
|
||||
case 'h':
|
||||
highlight_start(arg1);
|
||||
break;
|
||||
case 'H':
|
||||
highlight_stop(arg1);
|
||||
break;
|
||||
case 'f':
|
||||
UI_CALL(visual_bell);
|
||||
break;
|
||||
default:
|
||||
// Skip the ESC
|
||||
p = ptr + 1;
|
||||
break;
|
||||
}
|
||||
ptr = ++p;
|
||||
} else if ((c = *ptr) < 0x20) {
|
||||
// Ctrl character
|
||||
if (c == '\n') {
|
||||
ui_linefeed();
|
||||
} else if (c == '\r') {
|
||||
ui_carriage_return();
|
||||
} else if (c == '\b') {
|
||||
ui_cursor_left();
|
||||
} else if (c == Ctrl_L) { // cursor right
|
||||
ui_cursor_right();
|
||||
} else if (c == Ctrl_G) {
|
||||
UI_CALL(bell);
|
||||
}
|
||||
ptr++;
|
||||
} else {
|
||||
p = ptr;
|
||||
while (p < end && (*p >= 0x20)) {
|
||||
size_t clen = (size_t)mb_ptr2len(p);
|
||||
UI_CALL(put, p, (size_t)clen);
|
||||
col++;
|
||||
if (mb_ptr2cells(p) > 1) {
|
||||
// double cell character, blank the next cell
|
||||
UI_CALL(put, NULL, 0);
|
||||
col++;
|
||||
}
|
||||
p += clen;
|
||||
}
|
||||
ptr = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (update_cursor) {
|
||||
ui_cursor_shape();
|
||||
}
|
||||
|
||||
UI_CALL(flush);
|
||||
}
|
||||
|
||||
static void resized(UI *ui)
|
||||
{
|
||||
check_dimensions(ui);
|
||||
screen_resize(width, height, true);
|
||||
}
|
||||
|
||||
static void check_dimensions(UI *ui)
|
||||
{
|
||||
// The internal screen dimensions are always the minimum required to fit on
|
||||
// all connected screens
|
||||
if (ui->width < width) {
|
||||
width = ui->width;
|
||||
}
|
||||
|
||||
if (ui->height < height) {
|
||||
height = ui->height;
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_linefeed(void)
|
||||
{
|
||||
int new_col = 0;
|
||||
int new_row = row;
|
||||
if (new_row < sr.bot) {
|
||||
new_row++;
|
||||
} else {
|
||||
UI_CALL(scroll, 1);
|
||||
}
|
||||
ui_cursor_goto(new_row, new_col);
|
||||
}
|
||||
|
||||
static void ui_carriage_return(void)
|
||||
{
|
||||
int new_col = 0;
|
||||
ui_cursor_goto(row, new_col);
|
||||
}
|
||||
|
||||
static void ui_cursor_left(void)
|
||||
{
|
||||
int new_col = col - 1;
|
||||
assert(new_col >= 0);
|
||||
ui_cursor_goto(row, new_col);
|
||||
}
|
||||
|
||||
static void ui_cursor_right(void)
|
||||
{
|
||||
int new_col = col + 1;
|
||||
assert(new_col < width);
|
||||
ui_cursor_goto(row, new_col);
|
||||
}
|
||||
|
||||
static void ui_cursor_goto(int new_row, int new_col)
|
||||
{
|
||||
if (new_row == row && new_col == col) {
|
||||
return;
|
||||
}
|
||||
row = new_row;
|
||||
col = new_col;
|
||||
UI_CALL(cursor_goto, row, col);
|
||||
}
|
||||
|
@ -1,7 +1,39 @@
|
||||
#ifndef NVIM_UI_H
|
||||
#define NVIM_UI_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
bool bold, standout, underline, undercurl, italic, reverse;
|
||||
int foreground, background;
|
||||
} HlAttrs;
|
||||
|
||||
typedef struct ui_t UI;
|
||||
|
||||
struct ui_t {
|
||||
int width, height;
|
||||
void *data;
|
||||
void (*resize)(UI *ui, int rows, int columns);
|
||||
void (*clear)(UI *ui);
|
||||
void (*eol_clear)(UI *ui);
|
||||
void (*cursor_goto)(UI *ui, int row, int col);
|
||||
void (*cursor_on)(UI *ui);
|
||||
void (*cursor_off)(UI *ui);
|
||||
void (*mouse_on)(UI *ui);
|
||||
void (*mouse_off)(UI *ui);
|
||||
void (*insert_mode)(UI *ui);
|
||||
void (*normal_mode)(UI *ui);
|
||||
void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right);
|
||||
void (*scroll)(UI *ui, int count);
|
||||
void (*highlight_set)(UI *ui, HlAttrs attrs);
|
||||
void (*put)(UI *ui, uint8_t *str, size_t len);
|
||||
void (*bell)(UI *ui);
|
||||
void (*visual_bell)(UI *ui);
|
||||
void (*flush)(UI *ui);
|
||||
void (*suspend)(UI *ui);
|
||||
};
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ui.h.generated.h"
|
||||
|
@ -47,12 +47,6 @@ local function request(method, ...)
|
||||
error(rv[2])
|
||||
end
|
||||
end
|
||||
-- Make sure this will only return after all buffered characters have been
|
||||
-- processed
|
||||
if not loop_stopped then
|
||||
-- Except when the loop has been stopped by a notification triggered
|
||||
-- by the initial request, for example.
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
@ -70,23 +64,30 @@ local function call_and_stop_on_error(...)
|
||||
return result
|
||||
end
|
||||
|
||||
local function run(request_cb, notification_cb, setup_cb)
|
||||
local function run(request_cb, notification_cb, setup_cb, timeout)
|
||||
local on_request, on_notification, on_setup
|
||||
|
||||
local function on_request(method, args)
|
||||
if request_cb then
|
||||
function on_request(method, args)
|
||||
return call_and_stop_on_error(request_cb, method, args)
|
||||
end
|
||||
|
||||
local function on_notification(method, args)
|
||||
call_and_stop_on_error(notification_cb, method, args)
|
||||
end
|
||||
|
||||
local function on_setup()
|
||||
if notification_cb then
|
||||
function on_notification(method, args)
|
||||
call_and_stop_on_error(notification_cb, method, args)
|
||||
end
|
||||
end
|
||||
|
||||
if setup_cb then
|
||||
function on_setup()
|
||||
call_and_stop_on_error(setup_cb)
|
||||
end
|
||||
end
|
||||
|
||||
loop_stopped = false
|
||||
loop_running = true
|
||||
session:run(on_request, on_notification, on_setup)
|
||||
session:run(on_request, on_notification, on_setup, timeout)
|
||||
loop_running = false
|
||||
if last_error then
|
||||
local err = last_error
|
||||
@ -115,15 +116,6 @@ local function nvim_feed(input)
|
||||
end
|
||||
end
|
||||
|
||||
local function nvim_replace_termcodes(input)
|
||||
-- small hack to stop <C-@> from being replaced by the internal
|
||||
-- representation(which is different and won't work for vim_input)
|
||||
local temp_replacement = 'CCCCCCCCC@@@@@@@@@@'
|
||||
input = input:gsub('<[Cc][-]@>', temp_replacement)
|
||||
local rv = request('vim_replace_termcodes', input, false, true, true)
|
||||
return rv:gsub(temp_replacement, '\000')
|
||||
end
|
||||
|
||||
local function dedent(str)
|
||||
-- find minimum common indent across lines
|
||||
local indent = nil
|
||||
@ -148,7 +140,7 @@ end
|
||||
|
||||
local function feed(...)
|
||||
for _, v in ipairs({...}) do
|
||||
nvim_feed(nvim_replace_termcodes(dedent(v)))
|
||||
nvim_feed(dedent(v))
|
||||
end
|
||||
end
|
||||
|
||||
@ -172,8 +164,11 @@ end
|
||||
|
||||
local function insert(...)
|
||||
nvim_feed('i')
|
||||
rawfeed(...)
|
||||
nvim_feed(nvim_replace_termcodes('<ESC>'))
|
||||
for _, v in ipairs({...}) do
|
||||
local escaped = v:gsub('<', '<lt>')
|
||||
rawfeed(escaped)
|
||||
end
|
||||
nvim_feed('<ESC>')
|
||||
end
|
||||
|
||||
local function execute(...)
|
||||
@ -182,8 +177,8 @@ local function execute(...)
|
||||
-- not a search command, prefix with colon
|
||||
nvim_feed(':')
|
||||
end
|
||||
nvim_feed(v)
|
||||
nvim_feed(nvim_replace_termcodes('<CR>'))
|
||||
nvim_feed(v:gsub('<', '<lt>'))
|
||||
nvim_feed('<CR>')
|
||||
end
|
||||
end
|
||||
|
||||
|
184
test/functional/ui/highlight_spec.lua
Normal file
184
test/functional/ui/highlight_spec.lua
Normal file
@ -0,0 +1,184 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
|
||||
local execute = helpers.execute
|
||||
|
||||
describe('Default highlight groups', function()
|
||||
-- Test the default attributes for highlight groups shown by the :highlight
|
||||
-- command
|
||||
local screen, hlgroup_colors
|
||||
|
||||
setup(function()
|
||||
hlgroup_colors = {
|
||||
NonText = nvim('name_to_color', 'Blue'),
|
||||
Question = nvim('name_to_color', 'SeaGreen')
|
||||
}
|
||||
end)
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new()
|
||||
screen:attach()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
it('window status bar', function()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {reverse = true, bold = true}, -- StatusLine
|
||||
[2] = {reverse = true} -- StatusLineNC
|
||||
})
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
screen:expect([[
|
||||
^ {2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{1:[No Name] }{2:[No Name] [No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
-- navigate to verify that the attributes are properly moved
|
||||
feed('<c-w>j')
|
||||
screen:expect([[
|
||||
{2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] [No Name] [No Name] }|
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{1:[No Name] }|
|
||||
|
|
||||
]])
|
||||
-- note that when moving to a window with small width nvim will increase
|
||||
-- the width of the new active window at the expense of a inactive window
|
||||
-- (upstream vim has the same behavior)
|
||||
feed('<c-w>k<c-w>l')
|
||||
screen:expect([[
|
||||
{2:|}^ {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] }{1:[No Name] }{2:[No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
feed('<c-w>l')
|
||||
screen:expect([[
|
||||
{2:|} {2:|}^ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] [No Name] }{1:[No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
feed('<c-w>h<c-w>h')
|
||||
screen:expect([[
|
||||
^ {2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{1:[No Name] }{2:[No Name] [No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('insert mode text', function()
|
||||
feed('i')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{1:-- INSERT --} |
|
||||
]], {[1] = {bold = true}})
|
||||
end)
|
||||
|
||||
it('end of file markers', function()
|
||||
nvim('command', 'hi Normal guibg=black')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]], {[1] = {bold = true, foreground = hlgroup_colors.NonText}})
|
||||
end)
|
||||
|
||||
it('"wait return" text', function()
|
||||
feed(':ls<cr>')
|
||||
screen:expect([[
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls |
|
||||
1 %a "[No Name]" line 1 |
|
||||
{1:Press ENTER or type command to continue}^ |
|
||||
]], {[1] = {bold = true, foreground = hlgroup_colors.Question}})
|
||||
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
|
||||
end)
|
||||
end)
|
40
test/functional/ui/input_spec.lua
Normal file
40
test/functional/ui/input_spec.lua
Normal file
@ -0,0 +1,40 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim
|
||||
local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq
|
||||
|
||||
describe('mappings', function()
|
||||
local cid
|
||||
|
||||
local add_mapping = function(mapping, send)
|
||||
local str = 'mapped '..mapping
|
||||
local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '"
|
||||
..send:gsub('<', '<lt>').."')<cr>"
|
||||
execute(cmd)
|
||||
end
|
||||
|
||||
local check_mapping = function(mapping, expected)
|
||||
feed(mapping)
|
||||
eq({'notification', 'mapped', {expected}}, next_message())
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
cid = nvim('get_api_info')[1]
|
||||
add_mapping('<s-up>', '<s-up>')
|
||||
add_mapping('<s-up>', '<s-up>')
|
||||
add_mapping('<c-s-up>', '<c-s-up>')
|
||||
add_mapping('<c-s-a-up>', '<c-s-a-up>')
|
||||
end)
|
||||
|
||||
it('ok', function()
|
||||
check_mapping('<s-up>', '<s-up>')
|
||||
check_mapping('<c-s-up>', '<c-s-up>')
|
||||
check_mapping('<s-c-up>', '<c-s-up>')
|
||||
check_mapping('<c-s-a-up>', '<c-s-a-up>')
|
||||
check_mapping('<s-c-a-up>', '<c-s-a-up>')
|
||||
check_mapping('<c-a-s-up>', '<c-s-a-up>')
|
||||
check_mapping('<s-a-c-up>', '<c-s-a-up>')
|
||||
check_mapping('<a-c-s-up>', '<c-s-a-up>')
|
||||
check_mapping('<a-s-c-up>', '<c-s-a-up>')
|
||||
end)
|
||||
end)
|
380
test/functional/ui/screen.lua
Normal file
380
test/functional/ui/screen.lua
Normal file
@ -0,0 +1,380 @@
|
||||
-- This module contains the Screen class, a complete Nvim screen implementation
|
||||
-- designed for functional testing. The goal is to provide a simple and
|
||||
-- intuitive API for verifying screen state after a set of actions.
|
||||
--
|
||||
-- The screen class exposes a single assertion method, "Screen:expect". This
|
||||
-- method takes a string representing the expected screen state and an optional
|
||||
-- set of attribute identifiers for checking highlighted characters(more on
|
||||
-- this later).
|
||||
--
|
||||
-- The string passed to "expect" will be processed according to these rules:
|
||||
--
|
||||
-- - Each line of the string represents and is matched individually against
|
||||
-- a screen row.
|
||||
-- - The entire string is stripped of common indentation
|
||||
-- - Expected screen rows are stripped of the last character. The last
|
||||
-- character should be used to write pipes(|) that make clear where the
|
||||
-- screen ends
|
||||
-- - The last line is stripped, so the string must have (row count + 1)
|
||||
-- lines.
|
||||
--
|
||||
-- Example usage:
|
||||
--
|
||||
-- local screen = Screen.new(25, 10)
|
||||
-- -- attach the screen to the current Nvim instance
|
||||
-- screen:attach()
|
||||
-- --enter insert mode and type some text
|
||||
-- feed('ihello screen')
|
||||
-- -- declare an expectation for the eventual screen state
|
||||
-- screen:expect([[
|
||||
-- hello screen |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- -- INSERT -- |
|
||||
-- ]]) -- <- Last line is stripped
|
||||
--
|
||||
-- Since screen updates are received asynchronously, "expect" is actually
|
||||
-- specifying the eventual screen state. This is how "expect" works: It will
|
||||
-- start the event loop with a timeout of 5 seconds. Each time it receives an
|
||||
-- update the expected state will be checked against the updated state.
|
||||
--
|
||||
-- If the expected state matches the current state, the event loop will be
|
||||
-- stopped and "expect" will return. If the timeout expires, the last match
|
||||
-- error will be reported and the test will fail.
|
||||
--
|
||||
-- If the second argument is passed to "expect", the screen rows will be
|
||||
-- transformed before being matched against the string lines. The
|
||||
-- transformation rule is simple: Each substring "S" composed with characters
|
||||
-- having the exact same set of attributes will be substituted by "{K:S}",
|
||||
-- where K is a key associated the attribute set via the second argument of
|
||||
-- "expect".
|
||||
--
|
||||
-- Too illustrate how this works, let's say that in the above example we wanted
|
||||
-- to assert that the "-- INSERT --" string is highlighted with the bold
|
||||
-- attribute(which normally is), here's how the call to "expect" should look
|
||||
-- like:
|
||||
--
|
||||
-- screen:expect([[
|
||||
-- hello screen \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- {b:-- INSERT --} \
|
||||
-- ]], {b = {bold = true}})
|
||||
--
|
||||
-- In this case "b" is a string associated with the set composed of one
|
||||
-- attribute: bold. Note that since the {b:} markup is not a real part of the
|
||||
-- screen, the delimiter(|) had to be moved right
|
||||
local helpers = require('test.functional.helpers')
|
||||
local request, run, stop = helpers.request, helpers.run, helpers.stop
|
||||
local eq, dedent = helpers.eq, helpers.dedent
|
||||
|
||||
local Screen = {}
|
||||
Screen.__index = Screen
|
||||
|
||||
function Screen.new(width, height)
|
||||
if not width then
|
||||
width = 53
|
||||
end
|
||||
if not height then
|
||||
height = 14
|
||||
end
|
||||
return setmetatable({
|
||||
_default_attr_ids = nil,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_rows = new_cell_grid(width, height),
|
||||
_mode = 'normal',
|
||||
_mouse_enabled = true,
|
||||
_bell = false,
|
||||
_visual_bell = false,
|
||||
_suspended = true,
|
||||
_attrs = {},
|
||||
_cursor = {
|
||||
enabled = true, row = 1, col = 1
|
||||
},
|
||||
_scroll_region = {
|
||||
top = 1, bot = height, left = 1, right = width
|
||||
}
|
||||
}, Screen)
|
||||
end
|
||||
|
||||
function Screen:set_default_attr_ids(attr_ids)
|
||||
self._default_attr_ids = attr_ids
|
||||
end
|
||||
|
||||
function Screen:attach()
|
||||
request('attach_ui', self._width, self._height)
|
||||
self._suspended = false
|
||||
end
|
||||
|
||||
function Screen:detach()
|
||||
request('detach_ui')
|
||||
self._suspended = true
|
||||
end
|
||||
|
||||
function Screen:expect(expected, attr_ids)
|
||||
-- remove the last line and dedent
|
||||
expected = dedent(expected:gsub('\n[ ]+$', ''))
|
||||
local expected_rows = {}
|
||||
for row in expected:gmatch('[^\n]+') do
|
||||
-- the last character should be the screen delimiter
|
||||
row = row:sub(1, #row - 1)
|
||||
table.insert(expected_rows, row)
|
||||
end
|
||||
local ids = attr_ids or self._default_attr_ids
|
||||
self:_wait(function()
|
||||
for i = 1, self._height do
|
||||
local expected_row = expected_rows[i]
|
||||
local actual_row = self:_row_repr(self._rows[i], ids)
|
||||
if expected_row ~= actual_row then
|
||||
return 'Row '..tostring(i)..' didnt match.\nExpected: "'..
|
||||
expected_row..'"\nActual: "'..actual_row..'"'
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Screen:_wait(check, timeout)
|
||||
local err
|
||||
local function notification_cb(method, args)
|
||||
assert(method == 'redraw')
|
||||
self:_redraw(args)
|
||||
err = check()
|
||||
if not err then
|
||||
stop()
|
||||
end
|
||||
return true
|
||||
end
|
||||
run(nil, notification_cb, nil, timeout or 5000)
|
||||
if err then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_redraw(updates)
|
||||
for _, update in ipairs(updates) do
|
||||
-- print('--')
|
||||
-- print(require('inspect')(update))
|
||||
local method = update[1]
|
||||
for i = 2, #update do
|
||||
local handler = self['_handle_'..method]
|
||||
handler(self, unpack(update[i]))
|
||||
end
|
||||
-- print(self:_current_screen())
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_handle_resize(width, height)
|
||||
self._rows = new_cell_grid(width, height)
|
||||
end
|
||||
|
||||
function Screen:_handle_clear()
|
||||
self:_clear_block(1, self._height, 1, self._width)
|
||||
end
|
||||
|
||||
function Screen:_handle_eol_clear()
|
||||
local row, col = self._cursor.row, self._cursor.col
|
||||
self:_clear_block(row, 1, col, self._width - col)
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_goto(row, col)
|
||||
self._cursor.row = row + 1
|
||||
self._cursor.col = col + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_on()
|
||||
self._cursor.enabled = true
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_off()
|
||||
self._cursor.enabled = false
|
||||
end
|
||||
|
||||
function Screen:_handle_mouse_on()
|
||||
self._mouse_enabled = true
|
||||
end
|
||||
|
||||
function Screen:_handle_mouse_off()
|
||||
self._mouse_enabled = false
|
||||
end
|
||||
|
||||
function Screen:_handle_insert_mode()
|
||||
self._mode = 'insert'
|
||||
end
|
||||
|
||||
function Screen:_handle_normal_mode()
|
||||
self._mode = 'normal'
|
||||
end
|
||||
|
||||
function Screen:_handle_set_scroll_region(top, bot, left, right)
|
||||
self._scroll_region.top = top + 1
|
||||
self._scroll_region.bot = bot + 1
|
||||
self._scroll_region.left = left + 1
|
||||
self._scroll_region.right = right + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_scroll(count)
|
||||
local top = self._scroll_region.top
|
||||
local bot = self._scroll_region.bot
|
||||
local left = self._scroll_region.left
|
||||
local right = self._scroll_region.right
|
||||
local start, stop, step
|
||||
|
||||
if count > 0 then
|
||||
start = top
|
||||
stop = bot - count
|
||||
step = 1
|
||||
else
|
||||
start = bot
|
||||
stop = top - count
|
||||
step = -1
|
||||
end
|
||||
|
||||
-- shift scroll region
|
||||
for i = start, stop, step do
|
||||
local target = self._rows[i]
|
||||
local source = self._rows[i + count]
|
||||
self:_copy_row_section(target, source, left, right)
|
||||
end
|
||||
|
||||
-- clear invalid rows
|
||||
for i = stop + 1, stop + count, step do
|
||||
self:_clear_row_section(i, left, right)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_handle_highlight_set(attrs)
|
||||
self._attrs = attrs
|
||||
end
|
||||
|
||||
function Screen:_handle_put(str)
|
||||
local cell = self._rows[self._cursor.row][self._cursor.col]
|
||||
cell.text = str
|
||||
cell.attrs = self._attrs
|
||||
self._cursor.col = self._cursor.col + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_bell()
|
||||
self._bell = true
|
||||
end
|
||||
|
||||
function Screen:_handle_visual_bell()
|
||||
self._visual_bell = true
|
||||
end
|
||||
|
||||
function Screen:_handle_suspend()
|
||||
self._suspended = true
|
||||
end
|
||||
|
||||
function Screen:_clear_block(top, lines, left, columns)
|
||||
for i = top, top + lines - 1 do
|
||||
self:_clear_row_section(i, left, left + columns - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_clear_row_section(rownum, startcol, stopcol)
|
||||
local row = self._rows[rownum]
|
||||
for i = startcol, stopcol do
|
||||
row[i].text = ' '
|
||||
row[i].attrs = {}
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_copy_row_section(target, source, startcol, stopcol)
|
||||
for i = startcol, stopcol do
|
||||
target[i].text = source[i].text
|
||||
target[i].attrs = source[i].attrs
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_row_repr(row, attr_ids)
|
||||
local rv = {}
|
||||
local current_attr_id
|
||||
for i = 1, self._width do
|
||||
local attr_id = get_attr_id(attr_ids, row[i].attrs)
|
||||
if current_attr_id and attr_id ~= current_attr_id then
|
||||
-- close current attribute bracket, add it before any whitespace
|
||||
-- up to the current cell
|
||||
-- table.insert(rv, backward_find_meaningful(rv, i), '}')
|
||||
table.insert(rv, '}')
|
||||
current_attr_id = nil
|
||||
end
|
||||
if not current_attr_id and attr_id then
|
||||
-- open a new attribute bracket
|
||||
table.insert(rv, '{' .. attr_id .. ':')
|
||||
current_attr_id = attr_id
|
||||
end
|
||||
if self._rows[self._cursor.row] == row and self._cursor.col == i then
|
||||
table.insert(rv, '^')
|
||||
else
|
||||
table.insert(rv, row[i].text)
|
||||
end
|
||||
end
|
||||
if current_attr_id then
|
||||
table.insert(rv, '}')
|
||||
end
|
||||
-- return the line representation, but remove empty attribute brackets and
|
||||
-- trailing whitespace
|
||||
return table.concat(rv, '')--:gsub('%s+$', '')
|
||||
end
|
||||
|
||||
|
||||
function Screen:_current_screen()
|
||||
-- get a string that represents the current screen state(debugging helper)
|
||||
local rv = {}
|
||||
for i = 1, self._height do
|
||||
table.insert(rv, "'"..self:_row_repr(self._rows[i]).."'")
|
||||
end
|
||||
return table.concat(rv, '\n')
|
||||
end
|
||||
|
||||
function backward_find_meaningful(tbl, from)
|
||||
for i = from or #tbl, 1, -1 do
|
||||
if tbl[i] ~= ' ' then
|
||||
return i + 1
|
||||
end
|
||||
end
|
||||
return from
|
||||
end
|
||||
|
||||
function new_cell_grid(width, height)
|
||||
local rows = {}
|
||||
for i = 1, height do
|
||||
local cols = {}
|
||||
for j = 1, width do
|
||||
table.insert(cols, {text = ' ', attrs = {}})
|
||||
end
|
||||
table.insert(rows, cols)
|
||||
end
|
||||
return rows
|
||||
end
|
||||
|
||||
function get_attr_id(attr_ids, attrs)
|
||||
if not attr_ids then
|
||||
return
|
||||
end
|
||||
for id, a in pairs(attr_ids) do
|
||||
if a.bold == attrs.bold and a.standout == attrs.standout and
|
||||
a.underline == attrs.underline and a.undercurl == attrs.undercurl and
|
||||
a.italic == attrs.italic and a.reverse == attrs.reverse and
|
||||
a.foreground == attrs.foreground and
|
||||
a.background == attrs.background then
|
||||
return id
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return Screen
|
224
test/functional/ui/screen_basic_spec.lua
Normal file
224
test/functional/ui/screen_basic_spec.lua
Normal file
@ -0,0 +1,224 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute
|
||||
local insert = helpers.insert
|
||||
|
||||
describe('Screen', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new()
|
||||
screen:attach()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
describe('window', function()
|
||||
describe('split', function()
|
||||
it('horizontal', function()
|
||||
execute('sp')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
:sp |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('horizontal and resize', function()
|
||||
execute('sp')
|
||||
execute('resize 8')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
:resize 8 |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('horizontal and vertical', function()
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
screen:expect([[
|
||||
^ | | |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [No Name] [No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
]])
|
||||
insert('hello')
|
||||
screen:expect([[
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('tabnew', function()
|
||||
it('creates a new buffer', function()
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
insert('hello')
|
||||
screen:expect([[
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
execute('tabnew')
|
||||
insert('hello2')
|
||||
feed('h')
|
||||
screen:expect([[
|
||||
4+ [No Name] + [No Name] X|
|
||||
hell^2 |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
|
|
||||
]])
|
||||
execute('tabprevious')
|
||||
screen:expect([[
|
||||
4+ [No Name] + [No Name] X|
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('insert mode', function()
|
||||
it('move to next line with <cr>', function()
|
||||
feed('iline 1<cr>line 2<cr>')
|
||||
screen:expect([[
|
||||
line 1 |
|
||||
line 2 |
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
-- INSERT -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('command mode', function()
|
||||
it('typing commands', function()
|
||||
feed(':ls')
|
||||
screen:expect([[
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('execute command with multi-line output', function()
|
||||
feed(':ls<cr>')
|
||||
screen:expect([[
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls |
|
||||
1 %a "[No Name]" line 1 |
|
||||
Press ENTER or type command to continue^ |
|
||||
]])
|
||||
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user